@rubytech/create-sitedesk-code 0.1.517 → 0.1.519
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/dist/__tests__/edge-bind-template-wiring.test.js +37 -0
- package/dist/__tests__/samba-provision.test.js +103 -1
- package/dist/__tests__/tier-flag.test.js +10 -6
- package/dist/index.js +20 -5
- package/dist/samba-provision.js +72 -0
- package/dist/tier-flag.js +10 -5
- package/package.json +1 -1
- package/payload/platform/lib/entitlement/dist/index.d.ts +24 -0
- package/payload/platform/lib/entitlement/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/entitlement/dist/index.js +40 -2
- package/payload/platform/lib/entitlement/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +5 -9
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/admin/skills/admin-user-management/SKILL.md +1 -1
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +261 -8
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +12 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +44 -6
- package/payload/platform/plugins/docs/references/samba.md +2 -0
- package/payload/platform/plugins/whatsapp/references/channels-whatsapp.md +7 -3
- package/payload/platform/scripts/check-no-esm-require.mjs +5 -3
- package/payload/platform/scripts/generate-entitlement-fixture.mjs +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js +16 -1
- package/payload/platform/services/claude-session-manager/dist/rc-daemon.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js +15 -0
- package/payload/platform/services/claude-session-manager/dist/slice-memory-policy.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js +5 -3
- package/payload/platform/services/claude-session-manager/dist/webchat-channel-mcp.js.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/telegram-channel/dist/instructions.js +13 -10
- package/payload/platform/services/telegram-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts +6 -2
- package/payload/platform/services/webchat-channel/dist/instructions.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/instructions.js +66 -32
- package/payload/platform/services/webchat-channel/dist/instructions.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/notification.d.ts +2 -1
- package/payload/platform/services/webchat-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/notification.js +11 -4
- package/payload/platform/services/webchat-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/server.js +30 -14
- package/payload/platform/services/webchat-channel/dist/server.js.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.d.ts.map +1 -1
- package/payload/platform/services/webchat-channel/dist/turn-follow.js +11 -5
- package/payload/platform/services/webchat-channel/dist/turn-follow.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts +18 -4
- package/payload/platform/services/whatsapp-channel/dist/notification.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/notification.js +19 -7
- package/payload/platform/services/whatsapp-channel/dist/notification.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/server.js +23 -3
- package/payload/platform/services/whatsapp-channel/dist/server.js.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.d.ts.map +1 -1
- package/payload/platform/services/whatsapp-channel/dist/targets.js +8 -2
- package/payload/platform/services/whatsapp-channel/dist/targets.js.map +1 -1
- package/payload/platform/templates/systemd/edge.service.template +1 -1
- package/payload/server/chunk-PXNY2TAP.js +1980 -0
- package/payload/server/chunk-TIIWMF5G.js +4837 -0
- package/payload/server/{chunk-S6HYTIU3.js → chunk-UX2FRNO7.js} +72 -2031
- package/payload/server/manager-B6OFOXSW.js +61 -0
- package/payload/server/maxy-edge.js +5 -3
- package/payload/server/package.json +2 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-B5qKb3YK.js → AdminLoginScreens-BZPbCk8g.js} +1 -1
- package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-BZPbCk8g.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js +2 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.br +0 -0
- package/payload/server/public/assets/AdminShell-Bpo_wgxm.js.gz +0 -0
- package/payload/server/public/assets/{activity-DmnVUDYO.js → activity-DOsfKzT3.js} +1 -1
- package/payload/server/public/assets/activity-DOsfKzT3.js.br +0 -0
- package/payload/server/public/assets/activity-DOsfKzT3.js.gz +0 -0
- package/payload/server/public/assets/admin-COgD1z5a.js +1 -0
- package/payload/server/public/assets/admin-COgD1z5a.js.br +0 -0
- package/payload/server/public/assets/admin-COgD1z5a.js.gz +0 -0
- package/payload/server/public/assets/{agents-Bmq9X7P3.js → agents-DqDnZTH_.js} +1 -1
- package/payload/server/public/assets/agents-DqDnZTH_.js.br +0 -0
- package/payload/server/public/assets/agents-DqDnZTH_.js.gz +0 -0
- package/payload/server/public/assets/{browser-B1G-W80B.js → browser-FL17Dfp0.js} +1 -1
- package/payload/server/public/assets/browser-FL17Dfp0.js.br +0 -0
- package/payload/server/public/assets/browser-FL17Dfp0.js.gz +0 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js +1 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js.br +0 -0
- package/payload/server/public/assets/calendar-CLfLvsnV.js.gz +0 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js +1 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js.br +0 -0
- package/payload/server/public/assets/chat-DYq4FxMG.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-DWt5cdUo.js +1 -0
- package/payload/server/public/assets/chevron-right-B9suVrYy.js +1 -0
- package/payload/server/public/assets/chevron-right-B9suVrYy.js.br +0 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js +1 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js.br +0 -0
- package/payload/server/public/assets/clock-DW6qVXtv.js.gz +0 -0
- package/payload/server/public/assets/data-C8rbOgds.js +1 -0
- package/payload/server/public/assets/data-C8rbOgds.js.br +0 -0
- package/payload/server/public/assets/data-C8rbOgds.js.gz +0 -0
- package/payload/server/public/assets/{file-text-BVqqaDun.js → file-text-DNm0AzUz.js} +1 -1
- package/payload/server/public/assets/file-text-DNm0AzUz.js.br +0 -0
- package/payload/server/public/assets/file-text-DNm0AzUz.js.gz +0 -0
- package/payload/server/public/assets/{graph-CEcME0lp.js → graph-fAvmlVzh.js} +3 -3
- package/payload/server/public/assets/graph-fAvmlVzh.js.br +0 -0
- package/payload/server/public/assets/graph-fAvmlVzh.js.gz +0 -0
- package/payload/server/public/assets/{graph-labels-Zl32_AOH.js → graph-labels-CwdkCxfs.js} +1 -1
- package/payload/server/public/assets/graph-labels-CwdkCxfs.js.br +0 -0
- package/payload/server/public/assets/graph-labels-CwdkCxfs.js.gz +0 -0
- package/payload/server/public/assets/{maximize-2-rkkMyAhf.js → maximize-2-B6tAPhC2.js} +1 -1
- package/payload/server/public/assets/maximize-2-B6tAPhC2.js.br +0 -0
- package/payload/server/public/assets/maximize-2-B6tAPhC2.js.gz +0 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js +1 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js.br +0 -0
- package/payload/server/public/assets/operator-CTKvBVjk.js.gz +0 -0
- package/payload/server/public/assets/page-BWoNW3sa.js +32 -0
- package/payload/server/public/assets/page-BWoNW3sa.js.br +0 -0
- package/payload/server/public/assets/page-BWoNW3sa.js.gz +0 -0
- package/payload/server/public/assets/page-O5HkHQw4.js +1 -0
- package/payload/server/public/assets/page-O5HkHQw4.js.br +0 -0
- package/payload/server/public/assets/page-O5HkHQw4.js.gz +0 -0
- package/payload/server/public/assets/{public-Cqgfmo1D.js → public-BF1rnVPa.js} +1 -1
- package/payload/server/public/assets/public-BF1rnVPa.js.br +0 -0
- package/payload/server/public/assets/public-BF1rnVPa.js.gz +0 -0
- package/payload/server/public/assets/{rotate-ccw-O3RmqoTy.js → rotate-ccw-B8TqfCy4.js} +1 -1
- package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-B8TqfCy4.js.gz +0 -0
- package/payload/server/public/assets/{routines-DMYObMUy.js → routines-4R7BLZyH.js} +1 -1
- package/payload/server/public/assets/routines-4R7BLZyH.js.br +0 -0
- package/payload/server/public/assets/routines-4R7BLZyH.js.gz +0 -0
- package/payload/server/public/assets/{skills-DON1BKu5.js → skills-CQ9Qs2tg.js} +1 -1
- package/payload/server/public/assets/skills-CQ9Qs2tg.js.br +0 -0
- package/payload/server/public/assets/skills-CQ9Qs2tg.js.gz +0 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js +1 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js.br +0 -0
- package/payload/server/public/assets/tasks-DePyOrRk.js.gz +0 -0
- package/payload/server/public/assets/{time-entry-format-4zgmuQI0.js → time-entry-format-D9gtnG01.js} +1 -1
- package/payload/server/public/assets/time-entry-format-D9gtnG01.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-D9gtnG01.js.gz +0 -0
- package/payload/server/public/assets/{triangle-alert-By5D7wuI.js → triangle-alert-DAtm2mLL.js} +1 -1
- package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-DAtm2mLL.js.gz +0 -0
- package/payload/server/public/assets/{useCopyFeedback-Dx0vtm9s.js → useCopyFeedback-uMJT-p6i.js} +1 -1
- package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-uMJT-p6i.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js +14 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-BufCPDDH.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css +1 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-iCzsV96x.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js +2 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BFrPC-s7.js.gz +0 -0
- package/payload/server/public/assets/{wrench-CHfMwLza.js → wrench-CYgaN7K9.js} +1 -1
- package/payload/server/public/assets/wrench-CYgaN7K9.js.br +1 -0
- package/payload/server/public/assets/wrench-CYgaN7K9.js.gz +0 -0
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +7 -7
- package/payload/server/public/chat.html +13 -13
- package/payload/server/public/data.html +11 -11
- package/payload/server/public/graph.html +9 -9
- package/payload/server/public/index.html +14 -14
- package/payload/server/public/operator.html +14 -14
- package/payload/server/public/public.html +13 -13
- package/payload/server/public/routines.html +6 -6
- package/payload/server/public/skills.html +5 -5
- package/payload/server/public/tasks.html +6 -6
- package/payload/server/server.js +8156 -11969
- package/payload/server/public/assets/AdminLoginScreens-B5qKb3YK.js.br +0 -0
- package/payload/server/public/assets/AdminLoginScreens-B5qKb3YK.js.gz +0 -0
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js +0 -2
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js.br +0 -0
- package/payload/server/public/assets/AdminShell-BgJEq2zJ.js.gz +0 -0
- package/payload/server/public/assets/activity-DmnVUDYO.js.br +0 -0
- package/payload/server/public/assets/activity-DmnVUDYO.js.gz +0 -0
- package/payload/server/public/assets/admin-Dpge4OG9.js +0 -1
- package/payload/server/public/assets/admin-Dpge4OG9.js.br +0 -0
- package/payload/server/public/assets/admin-Dpge4OG9.js.gz +0 -0
- package/payload/server/public/assets/agents-Bmq9X7P3.js.br +0 -0
- package/payload/server/public/assets/agents-Bmq9X7P3.js.gz +0 -0
- package/payload/server/public/assets/browser-B1G-W80B.js.br +0 -0
- package/payload/server/public/assets/browser-B1G-W80B.js.gz +0 -0
- package/payload/server/public/assets/calendar-CHgrOFIN.js +0 -1
- package/payload/server/public/assets/calendar-CHgrOFIN.js.br +0 -0
- package/payload/server/public/assets/calendar-CHgrOFIN.js.gz +0 -0
- package/payload/server/public/assets/chat-CIwmmtwP.js +0 -1
- package/payload/server/public/assets/chat-CIwmmtwP.js.br +0 -0
- package/payload/server/public/assets/chat-CIwmmtwP.js.gz +0 -0
- package/payload/server/public/assets/chevron-left-EQjfaQ-n.js +0 -1
- package/payload/server/public/assets/chevron-left-EQjfaQ-n.js.br +0 -0
- package/payload/server/public/assets/chevron-right-Ct8jZUGP.js +0 -1
- package/payload/server/public/assets/chevron-right-Ct8jZUGP.js.br +0 -0
- package/payload/server/public/assets/clock-Cxz5ybck.js +0 -1
- package/payload/server/public/assets/clock-Cxz5ybck.js.br +0 -0
- package/payload/server/public/assets/clock-Cxz5ybck.js.gz +0 -0
- package/payload/server/public/assets/data-BQVXKVsJ.js +0 -1
- package/payload/server/public/assets/data-BQVXKVsJ.js.br +0 -1
- package/payload/server/public/assets/data-BQVXKVsJ.js.gz +0 -0
- package/payload/server/public/assets/file-text-BVqqaDun.js.br +0 -0
- package/payload/server/public/assets/file-text-BVqqaDun.js.gz +0 -0
- package/payload/server/public/assets/graph-CEcME0lp.js.br +0 -0
- package/payload/server/public/assets/graph-CEcME0lp.js.gz +0 -0
- package/payload/server/public/assets/graph-labels-Zl32_AOH.js.br +0 -0
- package/payload/server/public/assets/graph-labels-Zl32_AOH.js.gz +0 -0
- package/payload/server/public/assets/maximize-2-rkkMyAhf.js.br +0 -0
- package/payload/server/public/assets/maximize-2-rkkMyAhf.js.gz +0 -0
- package/payload/server/public/assets/operator-DlL023ch.js +0 -1
- package/payload/server/public/assets/operator-DlL023ch.js.br +0 -0
- package/payload/server/public/assets/operator-DlL023ch.js.gz +0 -0
- package/payload/server/public/assets/page-BAmYYqxp.js +0 -1
- package/payload/server/public/assets/page-BAmYYqxp.js.br +0 -0
- package/payload/server/public/assets/page-BAmYYqxp.js.gz +0 -0
- package/payload/server/public/assets/page-k0BhBkA2.js +0 -32
- package/payload/server/public/assets/page-k0BhBkA2.js.br +0 -0
- package/payload/server/public/assets/page-k0BhBkA2.js.gz +0 -0
- package/payload/server/public/assets/public-Cqgfmo1D.js.br +0 -0
- package/payload/server/public/assets/public-Cqgfmo1D.js.gz +0 -0
- package/payload/server/public/assets/rotate-ccw-O3RmqoTy.js.br +0 -0
- package/payload/server/public/assets/rotate-ccw-O3RmqoTy.js.gz +0 -0
- package/payload/server/public/assets/routines-DMYObMUy.js.br +0 -0
- package/payload/server/public/assets/routines-DMYObMUy.js.gz +0 -0
- package/payload/server/public/assets/skills-DON1BKu5.js.br +0 -0
- package/payload/server/public/assets/skills-DON1BKu5.js.gz +0 -0
- package/payload/server/public/assets/tasks-C2PhUSrf.js +0 -1
- package/payload/server/public/assets/tasks-C2PhUSrf.js.br +0 -0
- package/payload/server/public/assets/tasks-C2PhUSrf.js.gz +0 -0
- package/payload/server/public/assets/time-entry-format-4zgmuQI0.js.br +0 -0
- package/payload/server/public/assets/time-entry-format-4zgmuQI0.js.gz +0 -0
- package/payload/server/public/assets/triangle-alert-By5D7wuI.js.br +0 -0
- package/payload/server/public/assets/triangle-alert-By5D7wuI.js.gz +0 -0
- package/payload/server/public/assets/useCopyFeedback-Dx0vtm9s.js.br +0 -0
- package/payload/server/public/assets/useCopyFeedback-Dx0vtm9s.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js +0 -9
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-CRWoQXuU.js.gz +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css.br +0 -0
- package/payload/server/public/assets/useSubAccountSwitcher-UQQDcj0B.css.gz +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js +0 -2
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js.br +0 -0
- package/payload/server/public/assets/useVoiceRecorder-BgwOCCwj.js.gz +0 -0
- package/payload/server/public/assets/wrench-CHfMwLza.js.br +0 -0
- package/payload/server/public/assets/wrench-CHfMwLza.js.gz +0 -0
|
@@ -1,4 +1,17 @@
|
|
|
1
1
|
import { createRequire as __createRequire } from 'node:module'; const require = __createRequire(import.meta.url);
|
|
2
|
+
import {
|
|
3
|
+
BRAND_NAME,
|
|
4
|
+
BUNDLE_ALLOWLIST,
|
|
5
|
+
BUNDLE_MODE,
|
|
6
|
+
LOG_DIR,
|
|
7
|
+
MAXY_DIR,
|
|
8
|
+
PLATFORM_ROOT,
|
|
9
|
+
REMOTE_PASSWORD_FILE,
|
|
10
|
+
REMOTE_SESSION_SECRET_FILE,
|
|
11
|
+
USERS_FILE,
|
|
12
|
+
getConversationOwner,
|
|
13
|
+
isValidAccountId
|
|
14
|
+
} from "./chunk-PXNY2TAP.js";
|
|
2
15
|
|
|
3
16
|
// node_modules/hono/dist/compose.js
|
|
4
17
|
var compose = (middleware, onError, onNotFound) => {
|
|
@@ -2514,7 +2527,7 @@ var responseViaResponseObject = async (res, outgoing, options = {}) => {
|
|
|
2514
2527
|
});
|
|
2515
2528
|
if (!chunk) {
|
|
2516
2529
|
if (i === 1) {
|
|
2517
|
-
await new Promise((
|
|
2530
|
+
await new Promise((resolve4) => setTimeout(resolve4));
|
|
2518
2531
|
maxReadCount = 3;
|
|
2519
2532
|
continue;
|
|
2520
2533
|
}
|
|
@@ -2657,396 +2670,6 @@ var serve = (options, listeningListener) => {
|
|
|
2657
2670
|
return server;
|
|
2658
2671
|
};
|
|
2659
2672
|
|
|
2660
|
-
// app/lib/paths.ts
|
|
2661
|
-
import { homedir } from "os";
|
|
2662
|
-
import { resolve, join } from "path";
|
|
2663
|
-
import { existsSync, readFileSync } from "fs";
|
|
2664
|
-
var configDirName = ".maxy";
|
|
2665
|
-
var commercialMode = false;
|
|
2666
|
-
var bundleMode = "none";
|
|
2667
|
-
var bundleAllowlist = [];
|
|
2668
|
-
var vncDisplayNum = 99;
|
|
2669
|
-
var rfbPortNum = 5900;
|
|
2670
|
-
var websockifyPortNum = 6080;
|
|
2671
|
-
var cdpPortNum = 9222;
|
|
2672
|
-
var portSource = "dev-default";
|
|
2673
|
-
var platformRoot = process.env.MAXY_PLATFORM_ROOT;
|
|
2674
|
-
if (platformRoot) {
|
|
2675
|
-
const brandPath = join(platformRoot, "config", "brand.json");
|
|
2676
|
-
if (existsSync(brandPath)) {
|
|
2677
|
-
let brand;
|
|
2678
|
-
try {
|
|
2679
|
-
brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
2680
|
-
} catch (err) {
|
|
2681
|
-
const detail = (err instanceof Error ? err.message : String(err)).slice(0, 160);
|
|
2682
|
-
console.error(`[paths] error reason=brand-config-missing path=${brandPath} detail="parse failed: ${detail.replace(/"/g, "'")}"`);
|
|
2683
|
-
throw err;
|
|
2684
|
-
}
|
|
2685
|
-
if (typeof brand.configDir === "string") configDirName = brand.configDir;
|
|
2686
|
-
if (brand.commercialMode === true) commercialMode = true;
|
|
2687
|
-
if (brand.shipsPremiumBundles === true) {
|
|
2688
|
-
bundleMode = "all";
|
|
2689
|
-
} else if (Array.isArray(brand.shipsPremiumBundles)) {
|
|
2690
|
-
const arr = brand.shipsPremiumBundles;
|
|
2691
|
-
for (const entry of arr) {
|
|
2692
|
-
if (typeof entry !== "string" || entry.length === 0) {
|
|
2693
|
-
throw new Error(
|
|
2694
|
-
`brand.json at ${brandPath} shipsPremiumBundles array contains invalid entry: ${JSON.stringify(entry)}`
|
|
2695
|
-
);
|
|
2696
|
-
}
|
|
2697
|
-
}
|
|
2698
|
-
bundleMode = "allowlist";
|
|
2699
|
-
bundleAllowlist = arr;
|
|
2700
|
-
}
|
|
2701
|
-
if (typeof brand.vncDisplay === "number") vncDisplayNum = brand.vncDisplay;
|
|
2702
|
-
const brandLabel = configDirName.replace(/^\./, "");
|
|
2703
|
-
const required = [
|
|
2704
|
-
["rfbPort", brand.rfbPort],
|
|
2705
|
-
["websockifyPort", brand.websockifyPort],
|
|
2706
|
-
["cdpPort", brand.cdpPort]
|
|
2707
|
-
];
|
|
2708
|
-
for (const [field, value] of required) {
|
|
2709
|
-
if (typeof value !== "number") {
|
|
2710
|
-
const keys = Object.keys(brand).join(",");
|
|
2711
|
-
console.error(`[paths] error reason=cdp-port-unresolved brand=${brandLabel} path=${brandPath} field=${field} json_keys=${keys}`);
|
|
2712
|
-
throw new Error(`brand.json at ${brandPath} missing required field: ${field}`);
|
|
2713
|
-
}
|
|
2714
|
-
}
|
|
2715
|
-
rfbPortNum = brand.rfbPort;
|
|
2716
|
-
websockifyPortNum = brand.websockifyPort;
|
|
2717
|
-
cdpPortNum = brand.cdpPort;
|
|
2718
|
-
portSource = "brand.json";
|
|
2719
|
-
}
|
|
2720
|
-
}
|
|
2721
|
-
var MAXY_DIR = resolve(homedir(), configDirName);
|
|
2722
|
-
var BRAND_NAME = configDirName.replace(/^\./, "");
|
|
2723
|
-
var COMMERCIAL_MODE = commercialMode;
|
|
2724
|
-
var BUNDLE_MODE = bundleMode;
|
|
2725
|
-
var BUNDLE_ALLOWLIST = bundleAllowlist;
|
|
2726
|
-
var VNC_DISPLAY = `:${vncDisplayNum}`;
|
|
2727
|
-
var RFB_PORT = rfbPortNum;
|
|
2728
|
-
var WEBSOCKIFY_PORT = websockifyPortNum;
|
|
2729
|
-
var CDP_PORT = cdpPortNum;
|
|
2730
|
-
console.log(
|
|
2731
|
-
`[paths] brand=${configDirName.replace(/^\./, "")} vncDisplay=${vncDisplayNum} rfbPort=${RFB_PORT} websockifyPort=${WEBSOCKIFY_PORT} cdpPort=${CDP_PORT} source=${portSource}`
|
|
2732
|
-
);
|
|
2733
|
-
var CHROMIUM_PROFILE_DIR = resolve(homedir(), configDirName, "chromium-profile");
|
|
2734
|
-
var PLATFORM_ROOT = process.env.MAXY_PLATFORM_ROOT ?? resolve(process.cwd(), "..");
|
|
2735
|
-
var USERS_FILE = resolve(MAXY_DIR, "users.json");
|
|
2736
|
-
var LID_MAP_FILE = resolve(MAXY_DIR, "whatsapp-lid-map.json");
|
|
2737
|
-
var LOG_DIR = resolve(MAXY_DIR, "logs");
|
|
2738
|
-
var USERS_AUDIT_LOG = resolve(LOG_DIR, "users-audit.log");
|
|
2739
|
-
var BIN_DIR = resolve(MAXY_DIR, "bin");
|
|
2740
|
-
var REMOTE_PASSWORD_FILE = resolve(MAXY_DIR, ".remote-password");
|
|
2741
|
-
var REMOTE_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "remote-session-secret");
|
|
2742
|
-
var ADMIN_SESSION_SECRET_FILE = resolve(MAXY_DIR, "credentials", "admin-session-secret");
|
|
2743
|
-
var INSTALL_OWNER_FILE = resolve(MAXY_DIR, ".install-owner");
|
|
2744
|
-
var TELEGRAM_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-webhook-secret");
|
|
2745
|
-
var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webhook-secret");
|
|
2746
|
-
var VISITOR_TOKEN_SECRET_FILE = resolve(MAXY_DIR, "credentials", "visitor-token-secret");
|
|
2747
|
-
var CLAUDE_CREDENTIALS_FILE = resolve(MAXY_DIR, ".claude", ".credentials.json");
|
|
2748
|
-
|
|
2749
|
-
// app/lib/system-path-slugs.ts
|
|
2750
|
-
var SYSTEM_PATH_SLUGS = ["public", "public-chat", "chat", "graph", "data", "browser"];
|
|
2751
|
-
|
|
2752
|
-
// app/lib/claude-agent/account.ts
|
|
2753
|
-
import { resolve as resolve2 } from "path";
|
|
2754
|
-
import { readFileSync as readFileSync3, readdirSync, existsSync as existsSync3, statSync } from "fs";
|
|
2755
|
-
|
|
2756
|
-
// ../lib/brand-templating/src/index.ts
|
|
2757
|
-
import { join as join2 } from "path";
|
|
2758
|
-
import { existsSync as existsSync2, readFileSync as readFileSync2 } from "fs";
|
|
2759
|
-
var PLACEHOLDER = "{{productName}}";
|
|
2760
|
-
var cachedProductName = null;
|
|
2761
|
-
function brandJsonPath() {
|
|
2762
|
-
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
2763
|
-
if (!platformRoot2) {
|
|
2764
|
-
throw new Error(
|
|
2765
|
-
"[skill-loader] MAXY_PLATFORM_ROOT not set \u2014 cannot resolve brand.json"
|
|
2766
|
-
);
|
|
2767
|
-
}
|
|
2768
|
-
return join2(platformRoot2, "config", "brand.json");
|
|
2769
|
-
}
|
|
2770
|
-
function getBrandProductName() {
|
|
2771
|
-
if (cachedProductName !== null) return cachedProductName;
|
|
2772
|
-
const path = brandJsonPath();
|
|
2773
|
-
if (!existsSync2(path)) {
|
|
2774
|
-
throw new Error(`[skill-loader] brand.json missing at ${path}`);
|
|
2775
|
-
}
|
|
2776
|
-
let parsed;
|
|
2777
|
-
try {
|
|
2778
|
-
parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
2779
|
-
} catch (err) {
|
|
2780
|
-
throw new Error(
|
|
2781
|
-
`[skill-loader] brand.json unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
2782
|
-
);
|
|
2783
|
-
}
|
|
2784
|
-
const value = parsed.productName;
|
|
2785
|
-
if (typeof value !== "string" || value.trim() === "") {
|
|
2786
|
-
throw new Error(
|
|
2787
|
-
`[skill-loader] brand.json at ${path} has missing or empty productName`
|
|
2788
|
-
);
|
|
2789
|
-
}
|
|
2790
|
-
cachedProductName = value;
|
|
2791
|
-
return value;
|
|
2792
|
-
}
|
|
2793
|
-
function substituteBrandPlaceholders(content, sourcePath) {
|
|
2794
|
-
if (!content.includes(PLACEHOLDER)) return content;
|
|
2795
|
-
let productName;
|
|
2796
|
-
try {
|
|
2797
|
-
productName = getBrandProductName();
|
|
2798
|
-
} catch (err) {
|
|
2799
|
-
console.error(
|
|
2800
|
-
`[skill-loader] ERROR: brand.json missing \u2014 cannot resolve productName for skill ${sourcePath}`
|
|
2801
|
-
);
|
|
2802
|
-
throw err;
|
|
2803
|
-
}
|
|
2804
|
-
const occurrences = content.split(PLACEHOLDER).length - 1;
|
|
2805
|
-
const substituted = content.split(PLACEHOLDER).join(productName);
|
|
2806
|
-
console.log(
|
|
2807
|
-
`[skill-loader] brand-substituted productName=${productName} skill=${sourcePath} occurrences=${occurrences}`
|
|
2808
|
-
);
|
|
2809
|
-
return substituted;
|
|
2810
|
-
}
|
|
2811
|
-
|
|
2812
|
-
// app/lib/claude-agent/account.ts
|
|
2813
|
-
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve2(process.cwd(), "..");
|
|
2814
|
-
var ACCOUNTS_DIR = resolve2(PLATFORM_ROOT2, "..", "data/accounts");
|
|
2815
|
-
if (!existsSync3(PLATFORM_ROOT2)) {
|
|
2816
|
-
throw new Error(
|
|
2817
|
-
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT2}
|
|
2818
|
-
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
2819
|
-
);
|
|
2820
|
-
}
|
|
2821
|
-
var _stubDirsLoggedThisBoot = /* @__PURE__ */ new Set();
|
|
2822
|
-
function listValidAccounts() {
|
|
2823
|
-
if (!existsSync3(ACCOUNTS_DIR)) return [];
|
|
2824
|
-
const out = [];
|
|
2825
|
-
const entries2 = readdirSync(ACCOUNTS_DIR, { withFileTypes: true });
|
|
2826
|
-
for (const entry of entries2) {
|
|
2827
|
-
if (!entry.isDirectory()) continue;
|
|
2828
|
-
if (entry.name.startsWith(".")) continue;
|
|
2829
|
-
const accountDir = resolve2(ACCOUNTS_DIR, entry.name);
|
|
2830
|
-
const configPath = resolve2(accountDir, "account.json");
|
|
2831
|
-
if (!existsSync3(configPath)) {
|
|
2832
|
-
if (!_stubDirsLoggedThisBoot.has(entry.name)) {
|
|
2833
|
-
console.error(
|
|
2834
|
-
`[platform] accounts-state STUB-DIR id=${entry.name} \u2014 onboarding leak; remove or repair`
|
|
2835
|
-
);
|
|
2836
|
-
_stubDirsLoggedThisBoot.add(entry.name);
|
|
2837
|
-
}
|
|
2838
|
-
continue;
|
|
2839
|
-
}
|
|
2840
|
-
let config;
|
|
2841
|
-
try {
|
|
2842
|
-
config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
2843
|
-
} catch {
|
|
2844
|
-
console.error(
|
|
2845
|
-
`[platform] accounts-state CORRUPT-JSON id=${entry.name} \u2014 account.json failed to parse; treating as stub`
|
|
2846
|
-
);
|
|
2847
|
-
continue;
|
|
2848
|
-
}
|
|
2849
|
-
out.push({ accountId: config.accountId, accountDir, config });
|
|
2850
|
-
}
|
|
2851
|
-
return out;
|
|
2852
|
-
}
|
|
2853
|
-
function isValidAccountId(id) {
|
|
2854
|
-
return listValidAccounts().some((a) => a.accountId === id);
|
|
2855
|
-
}
|
|
2856
|
-
function resolveAccount() {
|
|
2857
|
-
if (!existsSync3(ACCOUNTS_DIR)) return null;
|
|
2858
|
-
let usersJsonUserId = null;
|
|
2859
|
-
if (existsSync3(USERS_FILE)) {
|
|
2860
|
-
try {
|
|
2861
|
-
const raw2 = readFileSync3(USERS_FILE, "utf-8").trim();
|
|
2862
|
-
if (raw2) {
|
|
2863
|
-
const users = JSON.parse(raw2);
|
|
2864
|
-
if (users.length > 0) {
|
|
2865
|
-
usersJsonUserId = users[0].userId;
|
|
2866
|
-
}
|
|
2867
|
-
}
|
|
2868
|
-
} catch {
|
|
2869
|
-
}
|
|
2870
|
-
}
|
|
2871
|
-
const entries2 = readdirSync(ACCOUNTS_DIR, { withFileTypes: true });
|
|
2872
|
-
let fallback = null;
|
|
2873
|
-
for (const entry of entries2) {
|
|
2874
|
-
if (!entry.isDirectory()) continue;
|
|
2875
|
-
const configPath = resolve2(ACCOUNTS_DIR, entry.name, "account.json");
|
|
2876
|
-
if (!existsSync3(configPath)) continue;
|
|
2877
|
-
const raw2 = readFileSync3(configPath, "utf-8");
|
|
2878
|
-
let config;
|
|
2879
|
-
try {
|
|
2880
|
-
config = JSON.parse(raw2);
|
|
2881
|
-
} catch {
|
|
2882
|
-
console.error(`[maxy] account.json is corrupt at ${configPath} \u2014 skipping`);
|
|
2883
|
-
continue;
|
|
2884
|
-
}
|
|
2885
|
-
if (!config.adminModel || !config.publicModel) {
|
|
2886
|
-
throw new Error(
|
|
2887
|
-
`[maxy] account.json at ${configPath} is missing required model fields (adminModel / publicModel). Update account.json with valid model identifiers.`
|
|
2888
|
-
);
|
|
2889
|
-
}
|
|
2890
|
-
const result = {
|
|
2891
|
-
accountId: config.accountId,
|
|
2892
|
-
accountDir: resolve2(ACCOUNTS_DIR, entry.name),
|
|
2893
|
-
config
|
|
2894
|
-
};
|
|
2895
|
-
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
2896
|
-
return result;
|
|
2897
|
-
}
|
|
2898
|
-
if (!fallback) {
|
|
2899
|
-
fallback = result;
|
|
2900
|
-
}
|
|
2901
|
-
}
|
|
2902
|
-
if (usersJsonUserId && fallback) {
|
|
2903
|
-
console.warn(
|
|
2904
|
-
`[maxy] resolveAccount: no account matches users.json userId ${usersJsonUserId} \u2014 falling back to ${fallback.accountId}`
|
|
2905
|
-
);
|
|
2906
|
-
}
|
|
2907
|
-
return fallback;
|
|
2908
|
-
}
|
|
2909
|
-
function readAgentFile(accountDir, agentName, filename) {
|
|
2910
|
-
const filePath = resolve2(accountDir, "agents", agentName, filename);
|
|
2911
|
-
if (!existsSync3(filePath)) return null;
|
|
2912
|
-
const raw2 = readFileSync3(filePath, "utf-8");
|
|
2913
|
-
if (filename.endsWith(".md")) {
|
|
2914
|
-
return substituteBrandPlaceholders(raw2, filePath);
|
|
2915
|
-
}
|
|
2916
|
-
return raw2;
|
|
2917
|
-
}
|
|
2918
|
-
var RESERVED_SLUGS = /* @__PURE__ */ new Set(["admin", "api", "assets", "agent-assets", "brand", "bot", "privacy"]);
|
|
2919
|
-
var SHADOWED_SLUGS = new Set(SYSTEM_PATH_SLUGS);
|
|
2920
|
-
var SLUG_PATTERN = /^[a-z][a-z0-9-]{2,49}$/;
|
|
2921
|
-
function validateAgentSlug(slug) {
|
|
2922
|
-
if (!SLUG_PATTERN.test(slug)) return false;
|
|
2923
|
-
if (RESERVED_SLUGS.has(slug)) {
|
|
2924
|
-
console.error(`[agent-slug] op=reject slug=${slug} reason=reserved`);
|
|
2925
|
-
return false;
|
|
2926
|
-
}
|
|
2927
|
-
if (SHADOWED_SLUGS.has(slug)) {
|
|
2928
|
-
console.error(`[agent-slug] op=reject slug=${slug} reason=shadowed`);
|
|
2929
|
-
return false;
|
|
2930
|
-
}
|
|
2931
|
-
return true;
|
|
2932
|
-
}
|
|
2933
|
-
function resolveDefaultAgentSlug(accountDir) {
|
|
2934
|
-
const configPath = resolve2(accountDir, "account.json");
|
|
2935
|
-
if (!existsSync3(configPath)) {
|
|
2936
|
-
console.error("[agent-resolve] account.json not found \u2014 cannot resolve defaultAgent");
|
|
2937
|
-
return null;
|
|
2938
|
-
}
|
|
2939
|
-
let config;
|
|
2940
|
-
try {
|
|
2941
|
-
config = JSON.parse(readFileSync3(configPath, "utf-8"));
|
|
2942
|
-
} catch (err) {
|
|
2943
|
-
console.error("[agent-resolve] failed to read account.json:", err);
|
|
2944
|
-
return null;
|
|
2945
|
-
}
|
|
2946
|
-
if (!config.defaultAgent) {
|
|
2947
|
-
console.error("[agent-resolve] defaultAgent not configured in account.json \u2014 set it via the connect-whatsapp skill");
|
|
2948
|
-
return null;
|
|
2949
|
-
}
|
|
2950
|
-
const agentConfigPath = resolve2(accountDir, "agents", config.defaultAgent, "config.json");
|
|
2951
|
-
if (!existsSync3(agentConfigPath)) {
|
|
2952
|
-
console.error(`[agent-resolve] defaultAgent="${config.defaultAgent}" has no config.json at ${agentConfigPath}`);
|
|
2953
|
-
return null;
|
|
2954
|
-
}
|
|
2955
|
-
return config.defaultAgent;
|
|
2956
|
-
}
|
|
2957
|
-
function estimateTokens(text) {
|
|
2958
|
-
return Math.ceil(text.length / 4);
|
|
2959
|
-
}
|
|
2960
|
-
function resolveAgentConfig(accountDir, agentName) {
|
|
2961
|
-
let model = null;
|
|
2962
|
-
let status = null;
|
|
2963
|
-
let displayName = null;
|
|
2964
|
-
let image = null;
|
|
2965
|
-
let imageShape = null;
|
|
2966
|
-
let showAgentName = false;
|
|
2967
|
-
let knowledgeKeywords = null;
|
|
2968
|
-
let accessMode = "open";
|
|
2969
|
-
const MAX_KNOWLEDGE_KEYWORDS = 5;
|
|
2970
|
-
const configRaw = readAgentFile(accountDir, agentName, "config.json");
|
|
2971
|
-
if (configRaw) {
|
|
2972
|
-
let parsed;
|
|
2973
|
-
try {
|
|
2974
|
-
parsed = JSON.parse(configRaw);
|
|
2975
|
-
} catch {
|
|
2976
|
-
console.warn(`[agent-config] ${agentName}/config.json: invalid JSON \u2014 using defaults`);
|
|
2977
|
-
parsed = {};
|
|
2978
|
-
}
|
|
2979
|
-
model = typeof parsed.model === "string" ? parsed.model : null;
|
|
2980
|
-
status = typeof parsed.status === "string" ? parsed.status : null;
|
|
2981
|
-
displayName = typeof parsed.displayName === "string" ? parsed.displayName : null;
|
|
2982
|
-
image = typeof parsed.image === "string" ? parsed.image : null;
|
|
2983
|
-
if (typeof parsed.imageShape === "string" && ["circle", "rounded"].includes(parsed.imageShape)) {
|
|
2984
|
-
imageShape = parsed.imageShape;
|
|
2985
|
-
}
|
|
2986
|
-
if (parsed.showAgentName === true) {
|
|
2987
|
-
showAgentName = true;
|
|
2988
|
-
} else if (parsed.showAgentName === "none") {
|
|
2989
|
-
showAgentName = "none";
|
|
2990
|
-
}
|
|
2991
|
-
if (image || imageShape || showAgentName) {
|
|
2992
|
-
console.log(`[agent-config] ${agentName}: image=${image || "(none)"} imageShape=${imageShape || "(none)"} showAgentName=${showAgentName}`);
|
|
2993
|
-
}
|
|
2994
|
-
if (typeof parsed.accessMode === "string" && ["gated", "paid"].includes(parsed.accessMode)) {
|
|
2995
|
-
accessMode = parsed.accessMode;
|
|
2996
|
-
}
|
|
2997
|
-
if (Array.isArray(parsed.knowledgeKeywords) && parsed.knowledgeKeywords.length > 0) {
|
|
2998
|
-
const filtered = parsed.knowledgeKeywords.filter((k) => typeof k === "string" && k.trim()).map((k) => k.replace(/,/g, "").trim().toLowerCase()).filter(Boolean);
|
|
2999
|
-
if (filtered.length > MAX_KNOWLEDGE_KEYWORDS) {
|
|
3000
|
-
console.warn(`[agent-config] ${agentName}: knowledgeKeywords has ${filtered.length} entries \u2014 capping at ${MAX_KNOWLEDGE_KEYWORDS}`);
|
|
3001
|
-
}
|
|
3002
|
-
knowledgeKeywords = filtered.length > 0 ? filtered.slice(0, MAX_KNOWLEDGE_KEYWORDS) : null;
|
|
3003
|
-
}
|
|
3004
|
-
}
|
|
3005
|
-
let knowledge = null;
|
|
3006
|
-
let knowledgeBaked = false;
|
|
3007
|
-
const agentDir = resolve2(accountDir, "agents", agentName);
|
|
3008
|
-
const knowledgePath = resolve2(agentDir, "KNOWLEDGE.md");
|
|
3009
|
-
const summaryPath = resolve2(agentDir, "KNOWLEDGE-SUMMARY.md");
|
|
3010
|
-
const hasKnowledge = existsSync3(knowledgePath);
|
|
3011
|
-
const hasSummary = existsSync3(summaryPath);
|
|
3012
|
-
if (hasKnowledge && hasSummary) {
|
|
3013
|
-
const knowledgeMtime = statSync(knowledgePath).mtimeMs;
|
|
3014
|
-
const summaryMtime = statSync(summaryPath).mtimeMs;
|
|
3015
|
-
if (summaryMtime >= knowledgeMtime) {
|
|
3016
|
-
knowledge = readFileSync3(summaryPath, "utf-8");
|
|
3017
|
-
} else {
|
|
3018
|
-
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
3019
|
-
knowledge = readFileSync3(knowledgePath, "utf-8");
|
|
3020
|
-
}
|
|
3021
|
-
knowledgeBaked = true;
|
|
3022
|
-
} else if (hasKnowledge) {
|
|
3023
|
-
knowledge = readFileSync3(knowledgePath, "utf-8");
|
|
3024
|
-
knowledgeBaked = true;
|
|
3025
|
-
}
|
|
3026
|
-
let budget = null;
|
|
3027
|
-
const identityRaw = readAgentFile(accountDir, agentName, "IDENTITY.md");
|
|
3028
|
-
const soulRaw = readAgentFile(accountDir, agentName, "SOUL.md");
|
|
3029
|
-
if (identityRaw || soulRaw || knowledge) {
|
|
3030
|
-
const identityTokens = identityRaw ? estimateTokens(identityRaw) : 0;
|
|
3031
|
-
const soulTokens = soulRaw ? estimateTokens(soulRaw) : 0;
|
|
3032
|
-
const knowledgeTokens = knowledge ? estimateTokens(knowledge) : 0;
|
|
3033
|
-
budget = {
|
|
3034
|
-
identity: identityTokens,
|
|
3035
|
-
soul: soulTokens,
|
|
3036
|
-
knowledge: knowledgeTokens,
|
|
3037
|
-
plugins: 0,
|
|
3038
|
-
total: identityTokens + soulTokens + knowledgeTokens
|
|
3039
|
-
};
|
|
3040
|
-
}
|
|
3041
|
-
return { model, status, displayName, image, imageShape, showAgentName, knowledge, knowledgeBaked, knowledgeKeywords, budget, accessMode };
|
|
3042
|
-
}
|
|
3043
|
-
function isActiveAgentSlug(accountDir, slug) {
|
|
3044
|
-
return resolveAgentConfig(accountDir, slug).status === "active";
|
|
3045
|
-
}
|
|
3046
|
-
function getDefaultAccountId() {
|
|
3047
|
-
return resolveAccount()?.accountId ?? null;
|
|
3048
|
-
}
|
|
3049
|
-
|
|
3050
2673
|
// ../lib/require-port-env/src/index.ts
|
|
3051
2674
|
var MissingPortEnvError = class extends Error {
|
|
3052
2675
|
envName;
|
|
@@ -3143,7 +2766,7 @@ function getSessionKeyBySessionId(_sessionId) {
|
|
|
3143
2766
|
}
|
|
3144
2767
|
|
|
3145
2768
|
// server/loop-registry.ts
|
|
3146
|
-
import { readFileSync
|
|
2769
|
+
import { readFileSync } from "fs";
|
|
3147
2770
|
var TAG = "[loop-registry]";
|
|
3148
2771
|
var STALL_INTERVALS = 3;
|
|
3149
2772
|
var entries = /* @__PURE__ */ new Map();
|
|
@@ -3167,7 +2790,7 @@ function parsePidStatCpuUs(raw2) {
|
|
|
3167
2790
|
function readPidCpuUs(pid) {
|
|
3168
2791
|
if (process.platform !== "linux") return null;
|
|
3169
2792
|
try {
|
|
3170
|
-
return parsePidStatCpuUs(
|
|
2793
|
+
return parsePidStatCpuUs(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
3171
2794
|
} catch {
|
|
3172
2795
|
return null;
|
|
3173
2796
|
}
|
|
@@ -3368,8 +2991,8 @@ function armedCount() {
|
|
|
3368
2991
|
|
|
3369
2992
|
// app/lib/vnc-logger.ts
|
|
3370
2993
|
import { appendFileSync, mkdirSync } from "fs";
|
|
3371
|
-
import { resolve
|
|
3372
|
-
var VNC_LOG_FILE =
|
|
2994
|
+
import { resolve } from "path";
|
|
2995
|
+
var VNC_LOG_FILE = resolve(LOG_DIR, "vnc-boot.log");
|
|
3373
2996
|
try {
|
|
3374
2997
|
mkdirSync(LOG_DIR, { recursive: true });
|
|
3375
2998
|
} catch (err) {
|
|
@@ -3412,7 +3035,7 @@ function websockifyLog(event, fields = {}) {
|
|
|
3412
3035
|
|
|
3413
3036
|
// ../lib/admin-access-password/src/index.ts
|
|
3414
3037
|
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
3415
|
-
import { existsSync
|
|
3038
|
+
import { existsSync, readFileSync as readFileSync2, writeFileSync, renameSync, unlinkSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3416
3039
|
import { dirname } from "path";
|
|
3417
3040
|
var SCRYPT_N = 16384;
|
|
3418
3041
|
var SCRYPT_R = 8;
|
|
@@ -3440,10 +3063,10 @@ function appendUsersAuditLine(audit, fields) {
|
|
|
3440
3063
|
}
|
|
3441
3064
|
}
|
|
3442
3065
|
function scryptAsync(password, salt) {
|
|
3443
|
-
return new Promise((
|
|
3066
|
+
return new Promise((resolve4, reject) => {
|
|
3444
3067
|
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
3445
3068
|
if (err) reject(err);
|
|
3446
|
-
else
|
|
3069
|
+
else resolve4(key);
|
|
3447
3070
|
});
|
|
3448
3071
|
});
|
|
3449
3072
|
}
|
|
@@ -3467,8 +3090,8 @@ async function matchesHash(password, stored) {
|
|
|
3467
3090
|
return timingSafeEqual(computed, storedHash);
|
|
3468
3091
|
}
|
|
3469
3092
|
function readUsers(usersFile) {
|
|
3470
|
-
if (!
|
|
3471
|
-
const raw2 =
|
|
3093
|
+
if (!existsSync(usersFile)) return [];
|
|
3094
|
+
const raw2 = readFileSync2(usersFile, "utf-8").trim();
|
|
3472
3095
|
if (!raw2) return [];
|
|
3473
3096
|
const parsed = JSON.parse(raw2);
|
|
3474
3097
|
if (!Array.isArray(parsed)) throw new Error("users.json is not an array");
|
|
@@ -3530,7 +3153,7 @@ function anyAccessPasswordConfigured(usersFile) {
|
|
|
3530
3153
|
return users.some((u) => typeof u.accessHash === "string" && u.accessHash.length > 0);
|
|
3531
3154
|
}
|
|
3532
3155
|
function accessStoreUnreadable(usersFile) {
|
|
3533
|
-
if (!
|
|
3156
|
+
if (!existsSync(usersFile)) return false;
|
|
3534
3157
|
try {
|
|
3535
3158
|
readUsers(usersFile);
|
|
3536
3159
|
return false;
|
|
@@ -3539,8 +3162,8 @@ function accessStoreUnreadable(usersFile) {
|
|
|
3539
3162
|
}
|
|
3540
3163
|
}
|
|
3541
3164
|
function migrateLegacyRemotePassword(usersFile, legacyFile, ownerUserId, audit) {
|
|
3542
|
-
if (!
|
|
3543
|
-
const legacy =
|
|
3165
|
+
if (!existsSync(legacyFile)) return "noop-no-legacy";
|
|
3166
|
+
const legacy = readFileSync2(legacyFile, "utf-8").trim();
|
|
3544
3167
|
const users = readUsers(usersFile);
|
|
3545
3168
|
const idx = users.findIndex((u) => u.userId === ownerUserId);
|
|
3546
3169
|
if (idx === -1) return "noop-no-owner";
|
|
@@ -3561,8 +3184,8 @@ function migrateLegacyRemotePassword(usersFile, legacyFile, ownerUserId, audit)
|
|
|
3561
3184
|
return "migrated";
|
|
3562
3185
|
}
|
|
3563
3186
|
function readLegacyRemoteHash(legacyFile) {
|
|
3564
|
-
if (!
|
|
3565
|
-
const raw2 =
|
|
3187
|
+
if (!existsSync(legacyFile)) return null;
|
|
3188
|
+
const raw2 = readFileSync2(legacyFile, "utf-8").trim();
|
|
3566
3189
|
return raw2.includes(":") ? raw2 : null;
|
|
3567
3190
|
}
|
|
3568
3191
|
|
|
@@ -3581,17 +3204,17 @@ function isPasswordValid(password) {
|
|
|
3581
3204
|
|
|
3582
3205
|
// app/lib/remote-auth.ts
|
|
3583
3206
|
import { scrypt as scrypt2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, createHmac } from "crypto";
|
|
3584
|
-
import { readFileSync as
|
|
3207
|
+
import { readFileSync as readFileSync3, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3585
3208
|
import { dirname as dirname2 } from "path";
|
|
3586
3209
|
var SCRYPT_N2 = 16384;
|
|
3587
3210
|
var SCRYPT_R2 = 8;
|
|
3588
3211
|
var SCRYPT_P2 = 1;
|
|
3589
3212
|
var SCRYPT_KEYLEN2 = 64;
|
|
3590
3213
|
function scryptAsync2(password, salt) {
|
|
3591
|
-
return new Promise((
|
|
3214
|
+
return new Promise((resolve4, reject) => {
|
|
3592
3215
|
scrypt2(password, salt, SCRYPT_KEYLEN2, { N: SCRYPT_N2, r: SCRYPT_R2, p: SCRYPT_P2 }, (err, key) => {
|
|
3593
3216
|
if (err) reject(err);
|
|
3594
|
-
else
|
|
3217
|
+
else resolve4(key);
|
|
3595
3218
|
});
|
|
3596
3219
|
});
|
|
3597
3220
|
}
|
|
@@ -3627,7 +3250,7 @@ var cachedSecret = null;
|
|
|
3627
3250
|
function getSecret() {
|
|
3628
3251
|
if (cachedSecret) return cachedSecret;
|
|
3629
3252
|
try {
|
|
3630
|
-
const hex2 =
|
|
3253
|
+
const hex2 = readFileSync3(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
|
|
3631
3254
|
if (hex2.length === SECRET_BYTES * 2) {
|
|
3632
3255
|
cachedSecret = Buffer.from(hex2, "hex");
|
|
3633
3256
|
return cachedSecret;
|
|
@@ -3640,7 +3263,7 @@ function getSecret() {
|
|
|
3640
3263
|
writeFileSync2(REMOTE_SESSION_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
|
|
3641
3264
|
} catch {
|
|
3642
3265
|
}
|
|
3643
|
-
const hex =
|
|
3266
|
+
const hex = readFileSync3(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
|
|
3644
3267
|
cachedSecret = Buffer.from(hex, "hex");
|
|
3645
3268
|
return cachedSecret;
|
|
3646
3269
|
}
|
|
@@ -4308,13 +3931,13 @@ function classifyHost(host, operatorDomains2, isPublicHost) {
|
|
|
4308
3931
|
import { watchFile } from "fs";
|
|
4309
3932
|
|
|
4310
3933
|
// app/lib/operator-domains.ts
|
|
4311
|
-
import { existsSync as
|
|
4312
|
-
import { dirname as dirname3, resolve as
|
|
4313
|
-
var OPERATOR_DOMAINS_PATH =
|
|
3934
|
+
import { existsSync as existsSync2, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync3 } from "fs";
|
|
3935
|
+
import { dirname as dirname3, resolve as resolve2 } from "path";
|
|
3936
|
+
var OPERATOR_DOMAINS_PATH = resolve2(MAXY_DIR, "operator-domains.json");
|
|
4314
3937
|
function loadOperatorDomains() {
|
|
4315
|
-
if (!
|
|
3938
|
+
if (!existsSync2(OPERATOR_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
4316
3939
|
try {
|
|
4317
|
-
const parsed = JSON.parse(
|
|
3940
|
+
const parsed = JSON.parse(readFileSync4(OPERATOR_DOMAINS_PATH, "utf-8"));
|
|
4318
3941
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
4319
3942
|
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
4320
3943
|
} catch {
|
|
@@ -4340,20 +3963,20 @@ function getOperatorDomains() {
|
|
|
4340
3963
|
}
|
|
4341
3964
|
|
|
4342
3965
|
// app/lib/claude-agent/plugin-manifest.ts
|
|
4343
|
-
import { resolve as
|
|
3966
|
+
import { resolve as resolve3, join } from "path";
|
|
4344
3967
|
import {
|
|
4345
|
-
readFileSync as
|
|
3968
|
+
readFileSync as readFileSync5,
|
|
4346
3969
|
writeFileSync as writeFileSync4,
|
|
4347
|
-
readdirSync
|
|
4348
|
-
existsSync as
|
|
4349
|
-
statSync
|
|
3970
|
+
readdirSync,
|
|
3971
|
+
existsSync as existsSync3,
|
|
3972
|
+
statSync,
|
|
4350
3973
|
rmSync
|
|
4351
3974
|
} from "fs";
|
|
4352
3975
|
import { createHash as createHash2 } from "crypto";
|
|
4353
3976
|
function isMissingPluginDir(pluginDir) {
|
|
4354
|
-
const pluginRoot =
|
|
4355
|
-
if (
|
|
4356
|
-
if (
|
|
3977
|
+
const pluginRoot = resolve3(PLATFORM_ROOT, "plugins", pluginDir);
|
|
3978
|
+
if (existsSync3(resolve3(pluginRoot, "PLUGIN.md"))) return false;
|
|
3979
|
+
if (existsSync3(resolve3(pluginRoot, "mcp/dist/index.js"))) return false;
|
|
4357
3980
|
return true;
|
|
4358
3981
|
}
|
|
4359
3982
|
function findMissingPlugins(enabledPlugins) {
|
|
@@ -4361,10 +3984,10 @@ function findMissingPlugins(enabledPlugins) {
|
|
|
4361
3984
|
return enabledPlugins.filter(isMissingPluginDir);
|
|
4362
3985
|
}
|
|
4363
3986
|
function readBundleSubPlugins(bundlePath) {
|
|
4364
|
-
if (!
|
|
3987
|
+
if (!existsSync3(bundlePath)) return [];
|
|
4365
3988
|
let raw2;
|
|
4366
3989
|
try {
|
|
4367
|
-
raw2 =
|
|
3990
|
+
raw2 = readFileSync5(bundlePath, "utf-8");
|
|
4368
3991
|
} catch {
|
|
4369
3992
|
return [];
|
|
4370
3993
|
}
|
|
@@ -4406,24 +4029,24 @@ function walkPremiumBundles() {
|
|
|
4406
4029
|
return kept;
|
|
4407
4030
|
}
|
|
4408
4031
|
function walkPremiumBundlesOnDisk() {
|
|
4409
|
-
const stagingRoot =
|
|
4410
|
-
if (!
|
|
4032
|
+
const stagingRoot = resolve3(PLATFORM_ROOT, "../premium-plugins");
|
|
4033
|
+
if (!existsSync3(stagingRoot)) return [];
|
|
4411
4034
|
let entries2;
|
|
4412
4035
|
try {
|
|
4413
|
-
entries2 =
|
|
4036
|
+
entries2 = readdirSync(stagingRoot);
|
|
4414
4037
|
} catch {
|
|
4415
4038
|
return [];
|
|
4416
4039
|
}
|
|
4417
4040
|
const result = [];
|
|
4418
4041
|
for (const bundle of entries2) {
|
|
4419
4042
|
if (bundle.startsWith(".")) continue;
|
|
4420
|
-
const bundleDir =
|
|
4043
|
+
const bundleDir = resolve3(stagingRoot, bundle);
|
|
4421
4044
|
try {
|
|
4422
|
-
if (!
|
|
4045
|
+
if (!statSync(bundleDir).isDirectory()) continue;
|
|
4423
4046
|
} catch {
|
|
4424
4047
|
continue;
|
|
4425
4048
|
}
|
|
4426
|
-
const declared = readBundleSubPlugins(
|
|
4049
|
+
const declared = readBundleSubPlugins(join(bundleDir, "BUNDLE.md"));
|
|
4427
4050
|
result.push({ bundle, subs: declared.length > 0 ? declared : [bundle] });
|
|
4428
4051
|
}
|
|
4429
4052
|
return result;
|
|
@@ -4431,14 +4054,14 @@ function walkPremiumBundlesOnDisk() {
|
|
|
4431
4054
|
function reapDuplicateFlatPremiumSubs(bundles, stagingRoot, pluginsDir) {
|
|
4432
4055
|
const TAG2 = "[premium-auto-deliver]";
|
|
4433
4056
|
for (const { bundle, subs } of bundles) {
|
|
4434
|
-
const isBundle =
|
|
4057
|
+
const isBundle = existsSync3(join(stagingRoot, bundle, "BUNDLE.md"));
|
|
4435
4058
|
for (const sub of subs) {
|
|
4436
|
-
const premiumManifest = isBundle ?
|
|
4437
|
-
const flatDir =
|
|
4438
|
-
const flatManifest =
|
|
4439
|
-
if (!
|
|
4440
|
-
const premiumSha = createHash2("sha256").update(
|
|
4441
|
-
const flatSha = createHash2("sha256").update(
|
|
4059
|
+
const premiumManifest = isBundle ? resolve3(stagingRoot, bundle, "plugins", sub, "PLUGIN.md") : resolve3(stagingRoot, bundle, "PLUGIN.md");
|
|
4060
|
+
const flatDir = resolve3(pluginsDir, sub);
|
|
4061
|
+
const flatManifest = resolve3(flatDir, "PLUGIN.md");
|
|
4062
|
+
if (!existsSync3(premiumManifest) || !existsSync3(flatManifest)) continue;
|
|
4063
|
+
const premiumSha = createHash2("sha256").update(readFileSync5(premiumManifest)).digest("hex");
|
|
4064
|
+
const flatSha = createHash2("sha256").update(readFileSync5(flatManifest)).digest("hex");
|
|
4442
4065
|
if (isBundle) {
|
|
4443
4066
|
if (premiumSha === flatSha) {
|
|
4444
4067
|
try {
|
|
@@ -4464,24 +4087,24 @@ function reapDuplicateFlatPremiumSubs(bundles, stagingRoot, pluginsDir) {
|
|
|
4464
4087
|
function autoDeliverPremiumPlugins() {
|
|
4465
4088
|
const bundles = walkPremiumBundles();
|
|
4466
4089
|
if (bundles.length === 0) return;
|
|
4467
|
-
const stagingRoot =
|
|
4468
|
-
const pluginsDir =
|
|
4090
|
+
const stagingRoot = resolve3(PLATFORM_ROOT, "../premium-plugins");
|
|
4091
|
+
const pluginsDir = resolve3(PLATFORM_ROOT, "plugins");
|
|
4469
4092
|
reapDuplicateFlatPremiumSubs(bundles, stagingRoot, pluginsDir);
|
|
4470
4093
|
}
|
|
4471
4094
|
function reconcileEnabledPlugins(accountDir, config, accountId) {
|
|
4472
4095
|
const TAG2 = "[premium-auto-deliver]";
|
|
4473
4096
|
if (!accountDir || !config) return;
|
|
4474
4097
|
const bundles = walkPremiumBundles();
|
|
4475
|
-
const stagingRoot =
|
|
4098
|
+
const stagingRoot = resolve3(PLATFORM_ROOT, "../premium-plugins");
|
|
4476
4099
|
const bundleNames = [];
|
|
4477
4100
|
const allSubs = [];
|
|
4478
4101
|
for (const { bundle, subs } of bundles) {
|
|
4479
4102
|
bundleNames.push(bundle);
|
|
4480
|
-
const bundlePath =
|
|
4481
|
-
const isBundle =
|
|
4103
|
+
const bundlePath = join(stagingRoot, bundle, "BUNDLE.md");
|
|
4104
|
+
const isBundle = existsSync3(bundlePath);
|
|
4482
4105
|
for (const sub of subs) {
|
|
4483
|
-
const subManifest = isBundle ?
|
|
4484
|
-
if (!
|
|
4106
|
+
const subManifest = isBundle ? resolve3(stagingRoot, bundle, "plugins", sub, "PLUGIN.md") : resolve3(stagingRoot, bundle, "PLUGIN.md");
|
|
4107
|
+
if (!existsSync3(subManifest)) continue;
|
|
4485
4108
|
allSubs.push(sub);
|
|
4486
4109
|
}
|
|
4487
4110
|
}
|
|
@@ -4498,9 +4121,9 @@ function reconcileEnabledPlugins(accountDir, config, accountId) {
|
|
|
4498
4121
|
);
|
|
4499
4122
|
if (added.length === 0) return;
|
|
4500
4123
|
const merged = [...currentSet];
|
|
4501
|
-
const configPath =
|
|
4124
|
+
const configPath = resolve3(accountDir, "account.json");
|
|
4502
4125
|
try {
|
|
4503
|
-
const raw2 =
|
|
4126
|
+
const raw2 = readFileSync5(configPath, "utf-8");
|
|
4504
4127
|
const parsed = JSON.parse(raw2);
|
|
4505
4128
|
parsed.enabledPlugins = merged;
|
|
4506
4129
|
writeFileSync4(configPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
@@ -4534,9 +4157,9 @@ function cleanupLeakedPremiumSubs(accountDir, config, accountId) {
|
|
|
4534
4157
|
else kept.push(name);
|
|
4535
4158
|
}
|
|
4536
4159
|
if (removed.length === 0) return;
|
|
4537
|
-
const configPath =
|
|
4160
|
+
const configPath = resolve3(accountDir, "account.json");
|
|
4538
4161
|
try {
|
|
4539
|
-
const raw2 =
|
|
4162
|
+
const raw2 = readFileSync5(configPath, "utf-8");
|
|
4540
4163
|
const parsed = JSON.parse(raw2);
|
|
4541
4164
|
parsed.enabledPlugins = kept;
|
|
4542
4165
|
writeFileSync4(configPath, JSON.stringify(parsed, null, 2) + "\n");
|
|
@@ -4553,1531 +4176,6 @@ var emitMissingOnResolve = (..._args) => null;
|
|
|
4553
4176
|
// app/lib/claude-agent/summary-helpers.ts
|
|
4554
4177
|
var stripAttachmentMetaSuffix = (..._args) => null;
|
|
4555
4178
|
|
|
4556
|
-
// app/lib/neo4j-store.ts
|
|
4557
|
-
import neo4j from "neo4j-driver";
|
|
4558
|
-
import { randomUUID } from "crypto";
|
|
4559
|
-
import { readFileSync as readFileSync9, readdirSync as readdirSync3, existsSync as existsSync7, openSync, readSync, closeSync, statSync as statSync3 } from "fs";
|
|
4560
|
-
import { resolve as resolve6 } from "path";
|
|
4561
|
-
var PLATFORM_ROOT3 = process.env.MAXY_PLATFORM_ROOT ?? resolve6(process.cwd(), "..");
|
|
4562
|
-
var driver = null;
|
|
4563
|
-
function readPassword() {
|
|
4564
|
-
if (process.env.NEO4J_PASSWORD) return process.env.NEO4J_PASSWORD;
|
|
4565
|
-
const passwordFile = resolve6(PLATFORM_ROOT3, "config/.neo4j-password");
|
|
4566
|
-
try {
|
|
4567
|
-
return readFileSync9(passwordFile, "utf-8").trim();
|
|
4568
|
-
} catch {
|
|
4569
|
-
throw new Error(
|
|
4570
|
-
`Neo4j password not found. Expected at ${passwordFile} or in NEO4J_PASSWORD env var.`
|
|
4571
|
-
);
|
|
4572
|
-
}
|
|
4573
|
-
}
|
|
4574
|
-
function getDriver() {
|
|
4575
|
-
if (!driver) {
|
|
4576
|
-
const uri = process.env.NEO4J_URI;
|
|
4577
|
-
if (!uri) {
|
|
4578
|
-
throw new Error(
|
|
4579
|
-
"[ui/neo4j-store] NEO4J_URI unset \u2014 refusing to default to bolt://localhost:7687"
|
|
4580
|
-
);
|
|
4581
|
-
}
|
|
4582
|
-
const user = process.env.NEO4J_USER ?? "neo4j";
|
|
4583
|
-
const password = readPassword();
|
|
4584
|
-
console.error(`[ui/neo4j-store] resolved neo4j_uri=${uri}`);
|
|
4585
|
-
driver = neo4j.driver(uri, neo4j.auth.basic(user, password), {
|
|
4586
|
-
maxConnectionPoolSize: 5
|
|
4587
|
-
});
|
|
4588
|
-
}
|
|
4589
|
-
return driver;
|
|
4590
|
-
}
|
|
4591
|
-
function getSession() {
|
|
4592
|
-
return getDriver().session();
|
|
4593
|
-
}
|
|
4594
|
-
async function runAdminUserSelfHeal(args) {
|
|
4595
|
-
const { selfHealAdminUser } = await import("./adminuser-self-heal-LC7HZAC6.js");
|
|
4596
|
-
return selfHealAdminUser({ driver: getDriver(), ...args });
|
|
4597
|
-
}
|
|
4598
|
-
async function runUserProfileReconcile() {
|
|
4599
|
-
const { reconcileUserProfiles } = await import("./userprofile-reconcile-QHVLY2AH.js");
|
|
4600
|
-
return reconcileUserProfiles({ driver: getDriver() });
|
|
4601
|
-
}
|
|
4602
|
-
process.on("SIGINT", async () => {
|
|
4603
|
-
if (driver) {
|
|
4604
|
-
await driver.close();
|
|
4605
|
-
driver = null;
|
|
4606
|
-
}
|
|
4607
|
-
});
|
|
4608
|
-
var OLLAMA_URL = process.env.OLLAMA_URL ?? "http://localhost:11434";
|
|
4609
|
-
var EMBED_MODEL = process.env.EMBED_MODEL ?? "nomic-embed-text";
|
|
4610
|
-
function envInt(name, def) {
|
|
4611
|
-
const raw2 = process.env[name];
|
|
4612
|
-
if (raw2 === void 0 || raw2 === "") return def;
|
|
4613
|
-
const n = Number(raw2);
|
|
4614
|
-
return Number.isFinite(n) ? n : def;
|
|
4615
|
-
}
|
|
4616
|
-
var EMBED_MODEL_CONTEXT_TOKENS = Math.max(1, Math.floor(envInt("EMBED_MODEL_CONTEXT_TOKENS", 2048)));
|
|
4617
|
-
var EMBED_CONTEXT_MARGIN_TOKENS = 48;
|
|
4618
|
-
var EMBED_WORST_CASE_CHARS_PER_TOKEN = 2;
|
|
4619
|
-
var EMBED_INPUT_MAX_CHARS = Math.max(
|
|
4620
|
-
1,
|
|
4621
|
-
Math.floor(
|
|
4622
|
-
envInt(
|
|
4623
|
-
"EMBED_INPUT_MAX_CHARS",
|
|
4624
|
-
(EMBED_MODEL_CONTEXT_TOKENS - EMBED_CONTEXT_MARGIN_TOKENS) * EMBED_WORST_CASE_CHARS_PER_TOKEN
|
|
4625
|
-
)
|
|
4626
|
-
)
|
|
4627
|
-
);
|
|
4628
|
-
var EMBED_KEEP_ALIVE_RAW = process.env.EMBED_KEEP_ALIVE === void 0 || process.env.EMBED_KEEP_ALIVE === "" ? "-1" : process.env.EMBED_KEEP_ALIVE;
|
|
4629
|
-
var EMBED_KEEP_ALIVE = /^-?\d+$/.test(EMBED_KEEP_ALIVE_RAW) ? Number(EMBED_KEEP_ALIVE_RAW) : EMBED_KEEP_ALIVE_RAW;
|
|
4630
|
-
var EMBED_INTERACTIVE_TIMEOUT_MS = 5e3;
|
|
4631
|
-
var EMBED_PRELOAD_TIMEOUT_MS = 6e4;
|
|
4632
|
-
async function embed(text, timeoutMs = EMBED_INTERACTIVE_TIMEOUT_MS) {
|
|
4633
|
-
const input = text.length > EMBED_INPUT_MAX_CHARS ? text.slice(0, EMBED_INPUT_MAX_CHARS) : text;
|
|
4634
|
-
const startedAt = Date.now();
|
|
4635
|
-
const target = `${OLLAMA_URL}/api/embed`;
|
|
4636
|
-
let res;
|
|
4637
|
-
try {
|
|
4638
|
-
res = await fetch(target, {
|
|
4639
|
-
method: "POST",
|
|
4640
|
-
headers: { "Content-Type": "application/json" },
|
|
4641
|
-
body: JSON.stringify({ model: EMBED_MODEL, input, keep_alive: EMBED_KEEP_ALIVE }),
|
|
4642
|
-
signal: AbortSignal.timeout(timeoutMs)
|
|
4643
|
-
});
|
|
4644
|
-
} catch (err) {
|
|
4645
|
-
const name = err instanceof Error ? err.name : "";
|
|
4646
|
-
if (name === "TimeoutError" || name === "AbortError") {
|
|
4647
|
-
throw new Error(
|
|
4648
|
-
`Ollama embed request timed out after ${timeoutMs}ms (inputChars=${input.length}) at ${target}; ollama did not respond in time (slow, cold-loading, or CPU-saturated).`
|
|
4649
|
-
);
|
|
4650
|
-
}
|
|
4651
|
-
let detail = err instanceof Error ? err.message : String(err);
|
|
4652
|
-
const cause = err instanceof Error ? err.cause : void 0;
|
|
4653
|
-
if (cause !== void 0) {
|
|
4654
|
-
detail += `: ${cause instanceof Error ? cause.message : String(cause)}`;
|
|
4655
|
-
}
|
|
4656
|
-
throw new Error(
|
|
4657
|
-
`Ollama embed request failed (inputChars=${input.length}) at ${target}: ${detail}`
|
|
4658
|
-
);
|
|
4659
|
-
}
|
|
4660
|
-
if (!res.ok) {
|
|
4661
|
-
const body = await res.text();
|
|
4662
|
-
throw new Error(
|
|
4663
|
-
`Ollama embedding failed (HTTP ${res.status}, inputChars=${input.length}): ${body}`
|
|
4664
|
-
);
|
|
4665
|
-
}
|
|
4666
|
-
const data = await res.json();
|
|
4667
|
-
const ms = Date.now() - startedAt;
|
|
4668
|
-
console.error(`[embed-query] warmth=${ms > 1e3 ? "cold" : "warm"} ms=${ms}`);
|
|
4669
|
-
return data.embeddings[0];
|
|
4670
|
-
}
|
|
4671
|
-
async function preloadEmbedModel() {
|
|
4672
|
-
try {
|
|
4673
|
-
await embed("warm", EMBED_PRELOAD_TIMEOUT_MS);
|
|
4674
|
-
console.error("[embed-query] op=preload result=ok");
|
|
4675
|
-
} catch (err) {
|
|
4676
|
-
console.error(
|
|
4677
|
-
`[embed-query] op=preload result=skip err="${err instanceof Error ? err.message : String(err)}"`
|
|
4678
|
-
);
|
|
4679
|
-
}
|
|
4680
|
-
}
|
|
4681
|
-
var sessionStoreRef = null;
|
|
4682
|
-
function getCachedSessionId(cacheKey2) {
|
|
4683
|
-
return sessionStoreRef?.get(cacheKey2)?.sessionId;
|
|
4684
|
-
}
|
|
4685
|
-
function cacheSessionId(cacheKey2, sessionId) {
|
|
4686
|
-
const session = sessionStoreRef?.get(cacheKey2);
|
|
4687
|
-
if (session) {
|
|
4688
|
-
session.sessionId = sessionId;
|
|
4689
|
-
}
|
|
4690
|
-
}
|
|
4691
|
-
var GREETING_DIRECTIVE = "[New session. Greet the visitor.]";
|
|
4692
|
-
async function ensureConversation(accountId, agentType, cacheKey2, visitorId, agentSlug, userId, channelOverride, channelAddressOverride) {
|
|
4693
|
-
const cached = getCachedSessionId(cacheKey2);
|
|
4694
|
-
if (cached) return { sessionId: cached, created: false };
|
|
4695
|
-
const sessionId = randomUUID();
|
|
4696
|
-
const channel = channelOverride ?? (cacheKey2.startsWith("whatsapp:") ? "whatsapp" : cacheKey2.startsWith("telegram:") ? "telegram" : "webchat");
|
|
4697
|
-
const channelAddress = channelAddressOverride ?? (cacheKey2.startsWith("whatsapp:") ? cacheKey2.slice("whatsapp:".length) : cacheKey2.startsWith("telegram:") ? cacheKey2.slice("telegram:".length) : sessionId);
|
|
4698
|
-
const conversationSublabel = agentType === "admin" ? "AdminConversation" : "PublicConversation";
|
|
4699
|
-
const handlerSlug = agentSlug ?? (agentType === "admin" ? "admin" : null);
|
|
4700
|
-
const wantsHandledByEdge = !!handlerSlug;
|
|
4701
|
-
const handledByClause = wantsHandledByEdge ? `WITH c, created
|
|
4702
|
-
OPTIONAL MATCH (a:Agent {accountId: $accountId, slug: $handlerSlug})
|
|
4703
|
-
FOREACH (_ IN CASE WHEN a IS NULL THEN [] ELSE [1] END | MERGE (c)-[:HANDLED_BY]->(a))
|
|
4704
|
-
RETURN c.sessionId AS sessionId, created AS created, a IS NOT NULL AS handledBy` : `RETURN c.sessionId AS sessionId, created AS created, false AS handledBy`;
|
|
4705
|
-
const session = getSession();
|
|
4706
|
-
try {
|
|
4707
|
-
const result = await session.run(
|
|
4708
|
-
`OPTIONAL MATCH (prior:Conversation {accountId: $accountId, channel: $channel, channelAddress: $channelAddress})
|
|
4709
|
-
WITH prior, coalesce(prior.sessionId, $sessionId) AS finalConvId, prior IS NULL AS created
|
|
4710
|
-
MERGE (c:Conversation {sessionId: finalConvId})
|
|
4711
|
-
ON CREATE SET
|
|
4712
|
-
c:${conversationSublabel},
|
|
4713
|
-
c.accountId = $accountId,
|
|
4714
|
-
c.agentType = $agentType,
|
|
4715
|
-
c.channel = $channel,
|
|
4716
|
-
c.channelAddress = $channelAddress,
|
|
4717
|
-
${visitorId ? "c.visitorId = $visitorId," : ""}
|
|
4718
|
-
${agentSlug ? "c.agentSlug = $agentSlug," : ""}
|
|
4719
|
-
${userId ? "c.userId = $userId," : ""}
|
|
4720
|
-
c.createdAt = datetime(),
|
|
4721
|
-
c.updatedAt = datetime()
|
|
4722
|
-
ON MATCH SET
|
|
4723
|
-
c.updatedAt = datetime()
|
|
4724
|
-
${handledByClause}`,
|
|
4725
|
-
{
|
|
4726
|
-
sessionId,
|
|
4727
|
-
accountId,
|
|
4728
|
-
agentType,
|
|
4729
|
-
channel,
|
|
4730
|
-
channelAddress,
|
|
4731
|
-
...visitorId ? { visitorId } : {},
|
|
4732
|
-
...agentSlug ? { agentSlug } : {},
|
|
4733
|
-
...handlerSlug ? { handlerSlug } : {},
|
|
4734
|
-
...userId ? { userId } : {}
|
|
4735
|
-
}
|
|
4736
|
-
);
|
|
4737
|
-
const id = result.records[0]?.get("sessionId");
|
|
4738
|
-
const created = result.records[0]?.get("created") === true;
|
|
4739
|
-
if (id) {
|
|
4740
|
-
cacheSessionId(cacheKey2, id);
|
|
4741
|
-
console.error(`[session] conversation attributed: sessionId=${id.slice(0, 8)}\u2026 userId=${userId ?? "none"} ${agentType}/${accountId.slice(0, 8)}\u2026 sublabel=${conversationSublabel}`);
|
|
4742
|
-
if (wantsHandledByEdge) {
|
|
4743
|
-
const handled = result.records[0]?.get("handledBy");
|
|
4744
|
-
const id82 = id.slice(0, 8);
|
|
4745
|
-
if (handled === true) {
|
|
4746
|
-
console.error(`[agent-graph] handled-by-write sessionId=${id82} slug=${handlerSlug}`);
|
|
4747
|
-
} else {
|
|
4748
|
-
console.error(`[agent-graph] handled-by-skip reason=no-agent-node sessionId=${id82} slug=${handlerSlug}`);
|
|
4749
|
-
}
|
|
4750
|
-
}
|
|
4751
|
-
}
|
|
4752
|
-
return { sessionId: id ?? null, created };
|
|
4753
|
-
} catch (err) {
|
|
4754
|
-
console.error(`[persist] ensureConversation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4755
|
-
return { sessionId: null, created: false };
|
|
4756
|
-
} finally {
|
|
4757
|
-
await session.close();
|
|
4758
|
-
}
|
|
4759
|
-
}
|
|
4760
|
-
async function ensureSessionConversation(input) {
|
|
4761
|
-
const t0 = Date.now();
|
|
4762
|
-
const sublabel = input.agentType === "admin" ? "AdminConversation" : "PublicConversation";
|
|
4763
|
-
const session = getSession();
|
|
4764
|
-
try {
|
|
4765
|
-
const result = await session.run(
|
|
4766
|
-
`OPTIONAL MATCH (prior:Conversation {sessionId: $sessionId})
|
|
4767
|
-
WITH prior IS NULL AS created
|
|
4768
|
-
MERGE (c:Conversation {sessionId: $sessionId})
|
|
4769
|
-
ON CREATE SET
|
|
4770
|
-
c:${sublabel},
|
|
4771
|
-
c.accountId = $accountId,
|
|
4772
|
-
c.agentType = $agentType,
|
|
4773
|
-
c.channel = $channel,
|
|
4774
|
-
c.channelAddress = $channelAddress,
|
|
4775
|
-
c.createdAt = datetime(),
|
|
4776
|
-
c.updatedAt = datetime()
|
|
4777
|
-
ON MATCH SET
|
|
4778
|
-
c.updatedAt = datetime()
|
|
4779
|
-
RETURN created`,
|
|
4780
|
-
{
|
|
4781
|
-
sessionId: input.sessionId,
|
|
4782
|
-
accountId: input.accountId,
|
|
4783
|
-
agentType: input.agentType,
|
|
4784
|
-
channel: input.channel,
|
|
4785
|
-
channelAddress: input.channelAddress
|
|
4786
|
-
}
|
|
4787
|
-
);
|
|
4788
|
-
const created = result.records[0]?.get("created") === true;
|
|
4789
|
-
console.error(
|
|
4790
|
-
`[wa-session] op=conversation-ensure sessionId=${input.sessionId} accountId=${input.accountId} channel=${input.channel} sublabel=${sublabel} created=${created} ms=${Date.now() - t0}`
|
|
4791
|
-
);
|
|
4792
|
-
return { created };
|
|
4793
|
-
} catch (err) {
|
|
4794
|
-
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
4795
|
-
console.error(
|
|
4796
|
-
`[wa-session] op=conversation-ensure FAIL sessionId=${input.sessionId} accountId=${input.accountId} reason=${reason} ms=${Date.now() - t0}`
|
|
4797
|
-
);
|
|
4798
|
-
return null;
|
|
4799
|
-
} finally {
|
|
4800
|
-
await session.close();
|
|
4801
|
-
}
|
|
4802
|
-
}
|
|
4803
|
-
async function findRecentConversation(visitorId, accountId, agentSlug, maxAgeHours = 24) {
|
|
4804
|
-
const session = getSession();
|
|
4805
|
-
try {
|
|
4806
|
-
const result = await session.run(
|
|
4807
|
-
`MATCH (c:Conversation {visitorId: $visitorId, accountId: $accountId, agentType: 'public'})
|
|
4808
|
-
WHERE c.agentSlug = $agentSlug
|
|
4809
|
-
AND c.updatedAt > datetime() - duration({hours: $maxAgeHours})
|
|
4810
|
-
AND NOT c:Trashed
|
|
4811
|
-
RETURN c.sessionId AS sessionId
|
|
4812
|
-
ORDER BY c.updatedAt DESC
|
|
4813
|
-
LIMIT 1`,
|
|
4814
|
-
{ visitorId, accountId, agentSlug, maxAgeHours: neo4j.int(maxAgeHours) },
|
|
4815
|
-
{ timeout: 2e3 }
|
|
4816
|
-
);
|
|
4817
|
-
const record = result.records[0];
|
|
4818
|
-
if (!record) return null;
|
|
4819
|
-
const sessionId = record.get("sessionId");
|
|
4820
|
-
if (!sessionId) return null;
|
|
4821
|
-
console.log(`[persist] found recent conversation ${sessionId.slice(0, 8)}\u2026 for visitor ${visitorId.slice(0, 8)}\u2026`);
|
|
4822
|
-
return { sessionId };
|
|
4823
|
-
} catch (err) {
|
|
4824
|
-
console.error(`[persist] findRecentConversation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4825
|
-
return null;
|
|
4826
|
-
} finally {
|
|
4827
|
-
await session.close();
|
|
4828
|
-
}
|
|
4829
|
-
}
|
|
4830
|
-
async function createNewAdminConversation(userId, accountId, cacheKey2, preMintedSessionId) {
|
|
4831
|
-
if (!userId || !cacheKey2 || !accountId) {
|
|
4832
|
-
console.error(`[admin/conversation-write] schema-violation userId=${userId ? "set" : "EMPTY"} cacheKey=${cacheKey2 ? "set" : "EMPTY"} accountId=${accountId ? "set" : "EMPTY"}`);
|
|
4833
|
-
return null;
|
|
4834
|
-
}
|
|
4835
|
-
const sessionId = preMintedSessionId ?? randomUUID();
|
|
4836
|
-
const session = getSession();
|
|
4837
|
-
try {
|
|
4838
|
-
const result = await session.run(
|
|
4839
|
-
`OPTIONAL MATCH (existing:AdminUser {userId: $userId})
|
|
4840
|
-
WITH existing IS NULL AS adminUserCreated
|
|
4841
|
-
MERGE (au:AdminUser {userId: $userId})
|
|
4842
|
-
ON CREATE SET au.accountId = $accountId,
|
|
4843
|
-
au.createdAt = datetime(),
|
|
4844
|
-
au.scope = 'admin'
|
|
4845
|
-
CREATE (c:Conversation:AdminConversation {
|
|
4846
|
-
sessionId: $sessionId,
|
|
4847
|
-
accountId: $accountId,
|
|
4848
|
-
agentType: 'admin',
|
|
4849
|
-
channel: 'webchat',
|
|
4850
|
-
channelAddress: $sessionId,
|
|
4851
|
-
userId: $userId,
|
|
4852
|
-
createdBySource: 'admin-conversation',
|
|
4853
|
-
createdAt: datetime(),
|
|
4854
|
-
updatedAt: datetime()
|
|
4855
|
-
})-[:STARTED_BY]->(au)
|
|
4856
|
-
RETURN adminUserCreated`,
|
|
4857
|
-
{ sessionId, accountId, userId }
|
|
4858
|
-
);
|
|
4859
|
-
const adminUserCreated = result.records[0]?.get("adminUserCreated") === true;
|
|
4860
|
-
cacheSessionId(cacheKey2, sessionId);
|
|
4861
|
-
console.log(`[session] conversation created: sessionId=${sessionId.slice(0, 8)}\u2026 cacheKey=${cacheKey2.slice(0, 8)}\u2026 accountId=${accountId.slice(0, 8)}\u2026 userId=${userId} agentType=admin createdBySource=admin-conversation createdAt=set updatedAt=set sublabel=AdminConversation adminUserCreated=${adminUserCreated}`);
|
|
4862
|
-
return sessionId;
|
|
4863
|
-
} catch (err) {
|
|
4864
|
-
console.error(`[session] createNewAdminConversation failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
4865
|
-
return null;
|
|
4866
|
-
} finally {
|
|
4867
|
-
await session.close();
|
|
4868
|
-
}
|
|
4869
|
-
}
|
|
4870
|
-
var HEX_COLOR_RE = /^#[0-9a-fA-F]{3,8}$/;
|
|
4871
|
-
async function fetchBranding(accountId) {
|
|
4872
|
-
const session = getSession();
|
|
4873
|
-
try {
|
|
4874
|
-
const result = await session.run(
|
|
4875
|
-
`MATCH (b:LocalBusiness {accountId: $accountId})
|
|
4876
|
-
OPTIONAL MATCH (b)-[:HAS_BRAND_ASSET]->(logo:ImageObject {purpose: "logo"})
|
|
4877
|
-
OPTIONAL MATCH (b)-[:HAS_BRAND_ASSET]->(icon:ImageObject {purpose: "icon"})
|
|
4878
|
-
RETURN b.name AS name,
|
|
4879
|
-
b.primaryColor AS primaryColor,
|
|
4880
|
-
b.accentColor AS accentColor,
|
|
4881
|
-
b.backgroundColor AS backgroundColor,
|
|
4882
|
-
b.tagline AS tagline,
|
|
4883
|
-
logo.contentUrl AS logoUrl,
|
|
4884
|
-
icon.contentUrl AS faviconUrl`,
|
|
4885
|
-
{ accountId },
|
|
4886
|
-
{ timeout: 2e3 }
|
|
4887
|
-
);
|
|
4888
|
-
const record = result.records[0];
|
|
4889
|
-
if (!record) return null;
|
|
4890
|
-
const name = record.get("name");
|
|
4891
|
-
if (!name) return null;
|
|
4892
|
-
const primaryColor = record.get("primaryColor");
|
|
4893
|
-
const accentColor = record.get("accentColor");
|
|
4894
|
-
const backgroundColor = record.get("backgroundColor");
|
|
4895
|
-
const tagline = record.get("tagline");
|
|
4896
|
-
const logoUrl = record.get("logoUrl");
|
|
4897
|
-
const faviconUrl = record.get("faviconUrl");
|
|
4898
|
-
const hasBranding = primaryColor || accentColor || backgroundColor || tagline || logoUrl || faviconUrl;
|
|
4899
|
-
if (!hasBranding) return null;
|
|
4900
|
-
const branding = { name };
|
|
4901
|
-
if (primaryColor && HEX_COLOR_RE.test(primaryColor)) branding.primaryColor = primaryColor;
|
|
4902
|
-
if (accentColor && HEX_COLOR_RE.test(accentColor)) branding.accentColor = accentColor;
|
|
4903
|
-
if (backgroundColor && HEX_COLOR_RE.test(backgroundColor)) branding.backgroundColor = backgroundColor;
|
|
4904
|
-
if (tagline) branding.tagline = tagline;
|
|
4905
|
-
if (logoUrl) branding.logoUrl = logoUrl;
|
|
4906
|
-
if (faviconUrl) branding.faviconUrl = faviconUrl;
|
|
4907
|
-
console.error(`[branding] resolved for accountId=${accountId.slice(0, 8)}\u2026: primary=${branding.primaryColor ?? "\u2013"} logo=${branding.logoUrl ? "yes" : "no"}`);
|
|
4908
|
-
return branding;
|
|
4909
|
-
} catch (err) {
|
|
4910
|
-
console.error(`[branding] fetchBranding failed for accountId=${accountId.slice(0, 8)}\u2026: ${err instanceof Error ? err.message : String(err)}`);
|
|
4911
|
-
return null;
|
|
4912
|
-
} finally {
|
|
4913
|
-
await session.close();
|
|
4914
|
-
}
|
|
4915
|
-
}
|
|
4916
|
-
async function fetchAccountName(accountId) {
|
|
4917
|
-
const session = getSession();
|
|
4918
|
-
try {
|
|
4919
|
-
const result = await session.run(
|
|
4920
|
-
`MATCH (b:LocalBusiness {accountId: $accountId}) RETURN b.name AS name`,
|
|
4921
|
-
{ accountId },
|
|
4922
|
-
{ timeout: 2e3 }
|
|
4923
|
-
);
|
|
4924
|
-
const name = result.records[0]?.get("name")?.trim();
|
|
4925
|
-
if (!name) {
|
|
4926
|
-
console.error(`[branding] account-name-fallback accountId=${accountId.slice(0, 8)}\u2026 reason=no-name`);
|
|
4927
|
-
return null;
|
|
4928
|
-
}
|
|
4929
|
-
return name;
|
|
4930
|
-
} catch (err) {
|
|
4931
|
-
console.error(`[branding] fetchAccountName failed for accountId=${accountId.slice(0, 8)}\u2026: ${err instanceof Error ? err.message : String(err)}`);
|
|
4932
|
-
return null;
|
|
4933
|
-
} finally {
|
|
4934
|
-
await session.close();
|
|
4935
|
-
}
|
|
4936
|
-
}
|
|
4937
|
-
var SUMMARY_MAX_LEN = 200;
|
|
4938
|
-
var persistMessageLocks = /* @__PURE__ */ new Map();
|
|
4939
|
-
async function persistMessage(sessionId, role, content, accountId, tokens, createdAt, sender, components, attachments) {
|
|
4940
|
-
if (!content) return null;
|
|
4941
|
-
const messageId = randomUUID();
|
|
4942
|
-
const summary = role === "user" ? content.slice(0, SUMMARY_MAX_LEN).trim() : "";
|
|
4943
|
-
let embedding = null;
|
|
4944
|
-
try {
|
|
4945
|
-
embedding = await embed(content);
|
|
4946
|
-
} catch (err) {
|
|
4947
|
-
console.error(`[persist] Embedding failed, storing without: ${err instanceof Error ? err.message : String(err)}`);
|
|
4948
|
-
}
|
|
4949
|
-
const prev = persistMessageLocks.get(sessionId);
|
|
4950
|
-
const waited = prev !== void 0;
|
|
4951
|
-
let release;
|
|
4952
|
-
const mine = new Promise((resolve7) => {
|
|
4953
|
-
release = resolve7;
|
|
4954
|
-
});
|
|
4955
|
-
const chained = (prev ?? Promise.resolve()).then(() => mine);
|
|
4956
|
-
persistMessageLocks.set(sessionId, chained);
|
|
4957
|
-
await prev;
|
|
4958
|
-
const session = getSession();
|
|
4959
|
-
try {
|
|
4960
|
-
const result = await session.run(
|
|
4961
|
-
`MATCH (c:Conversation {sessionId: $sessionId})
|
|
4962
|
-
OPTIONAL MATCH (tail:Message)-[:PART_OF]->(c)
|
|
4963
|
-
WHERE NOT (tail)-[:NEXT]->(:Message)
|
|
4964
|
-
AND (tail:UserMessage OR tail:AssistantMessage)
|
|
4965
|
-
// sublabel-scope the tail to the agent-path's own sublabels.
|
|
4966
|
-
// Without this, a Task-857 :WhatsAppMessage row (no outgoing :NEXT)
|
|
4967
|
-
// would be picked as tail and the agent-path would chain
|
|
4968
|
-
// :WhatsAppMessage\u2192:UserMessage, crossing the live-channel chain.
|
|
4969
|
-
// The live writer's tail predicate is sublabel-scoped to
|
|
4970
|
-
// :WhatsAppMessage; this clause is the matching defence on the
|
|
4971
|
-
// agent side.
|
|
4972
|
-
// Capture whether THIS write's guard will fire. Read c.summary
|
|
4973
|
-
// before the SET so the boolean reflects the pre-state, not the
|
|
4974
|
-
// post-state \u2014 a false positive otherwise when a new user message
|
|
4975
|
-
// happens to equal an already-set summary (verbatim retry, short
|
|
4976
|
-
// catchphrase) and fooled a post-state equality check.
|
|
4977
|
-
WITH c, tail, (c.summary IS NULL AND $role = 'user' AND $summary <> '') AS summarySetThisWrite
|
|
4978
|
-
CREATE (m:Message:${role === "user" ? "UserMessage" : "AssistantMessage"} {
|
|
4979
|
-
messageId: $messageId,
|
|
4980
|
-
sessionId: $sessionId,
|
|
4981
|
-
accountId: $accountId,
|
|
4982
|
-
role: $role,
|
|
4983
|
-
content: $content,
|
|
4984
|
-
createdAt: ${createdAt ? "datetime($createdAt)" : "datetime()"}
|
|
4985
|
-
${embedding ? ", embedding: $embedding" : ""}
|
|
4986
|
-
${sender ? ", senderVisitorId: $senderVisitorId, senderName: $senderName" : ""}
|
|
4987
|
-
${tokens?.inputTokens != null ? ", inputTokens: $inputTokens" : ""}
|
|
4988
|
-
${tokens?.outputTokens != null ? ", outputTokens: $outputTokens" : ""}
|
|
4989
|
-
${tokens?.cacheReadTokens != null ? ", cacheReadTokens: $cacheReadTokens" : ""}
|
|
4990
|
-
${tokens?.cacheCreationTokens != null ? ", cacheCreationTokens: $cacheCreationTokens" : ""}
|
|
4991
|
-
})
|
|
4992
|
-
SET c.updatedAt = datetime()
|
|
4993
|
-
CREATE (m)-[:PART_OF]->(c)
|
|
4994
|
-
FOREACH (prev IN CASE WHEN tail IS NULL THEN [] ELSE [tail] END |
|
|
4995
|
-
CREATE (prev)-[:NEXT]->(m)
|
|
4996
|
-
)
|
|
4997
|
-
FOREACH (_ IN CASE WHEN summarySetThisWrite THEN [1] ELSE [] END |
|
|
4998
|
-
SET c.summary = $summary
|
|
4999
|
-
)
|
|
5000
|
-
FOREACH (comp IN $components |
|
|
5001
|
-
CREATE (m)-[:HAS_COMPONENT]->(:Component {
|
|
5002
|
-
componentId: comp.componentId,
|
|
5003
|
-
sessionId: $sessionId,
|
|
5004
|
-
accountId: $accountId,
|
|
5005
|
-
messageId: $messageId,
|
|
5006
|
-
name: comp.name,
|
|
5007
|
-
data: comp.data,
|
|
5008
|
-
ordinal: comp.ordinal,
|
|
5009
|
-
textOffset: comp.textOffset,
|
|
5010
|
-
submitted: false,
|
|
5011
|
-
createdAt: datetime(),
|
|
5012
|
-
// store the artefact attachmentId on the:Component
|
|
5013
|
-
// itself when this is a PERSISTENT_COMPONENTS write whose disk
|
|
5014
|
-
// write succeeded. Field is null for non-persistent / mime-skip
|
|
5015
|
-
// / disk-fail components. Lets the audit + backfill scripts
|
|
5016
|
-
// read the attachmentId directly off the :Component row instead
|
|
5017
|
-
// of re-deriving (the live + heal paths use different sources \u2014
|
|
5018
|
-
// sha256(block.id) live, componentId-fallback backfill \u2014 and
|
|
5019
|
-
// the audit must match either).
|
|
5020
|
-
attachmentId: comp.artefactAttachmentId
|
|
5021
|
-
})
|
|
5022
|
-
)
|
|
5023
|
-
// for PERSISTENT_COMPONENTS whose disk write succeeded
|
|
5024
|
-
// (artefactAttachmentId is non-null), MERGE the sibling
|
|
5025
|
-
// :KnowledgeDocument projection AND a :HAS_KNOWLEDGE_DOCUMENT edge
|
|
5026
|
-
// from the parent :Message in the same Cypher tx. ON CREATE stamps
|
|
5027
|
-
// name+encodingFormat from the live render; ON MATCH only bumps
|
|
5028
|
-
// updatedAt so a heal-on-resume re-run cannot clobber an operator's
|
|
5029
|
-
// subsequent rename via the knowledge-documents API (eng-review \xA72).
|
|
5030
|
-
// The edge makes the projection graph-discoverable from the
|
|
5031
|
-
// conversation timeline so file-delete-cascade and conversation
|
|
5032
|
-
// cleanup can reach it. FOREACH on the filtered list collapses to a
|
|
5033
|
-
// no-op when no component has an attachmentId, preserving the
|
|
5034
|
-
// text-only-turn fast path.
|
|
5035
|
-
FOREACH (kd IN $knowledgeDocs |
|
|
5036
|
-
MERGE (k:KnowledgeDocument {accountId: $accountId, attachmentId: kd.attachmentId})
|
|
5037
|
-
ON CREATE SET k.name = kd.title,
|
|
5038
|
-
k.encodingFormat = kd.mimeType,
|
|
5039
|
-
k.createdAt = datetime(),
|
|
5040
|
-
k.updatedAt = datetime()
|
|
5041
|
-
ON MATCH SET k.updatedAt = datetime()
|
|
5042
|
-
MERGE (m)-[:HAS_KNOWLEDGE_DOCUMENT]->(k)
|
|
5043
|
-
)
|
|
5044
|
-
FOREACH (att IN $attachments |
|
|
5045
|
-
CREATE (m)-[:HAS_ATTACHMENT]->(:Attachment {
|
|
5046
|
-
attachmentId: att.attachmentId,
|
|
5047
|
-
sessionId: $sessionId,
|
|
5048
|
-
accountId: $accountId,
|
|
5049
|
-
messageId: $messageId,
|
|
5050
|
-
filename: att.filename,
|
|
5051
|
-
mimeType: att.mimeType,
|
|
5052
|
-
sizeBytes: att.sizeBytes,
|
|
5053
|
-
storagePath: att.storagePath,
|
|
5054
|
-
ordinal: att.ordinal,
|
|
5055
|
-
createdAt: datetime()
|
|
5056
|
-
})
|
|
5057
|
-
)
|
|
5058
|
-
RETURN tail.messageId AS prevMessageId,
|
|
5059
|
-
summarySetThisWrite,
|
|
5060
|
-
size([(m2:Message)-[:PART_OF]->(c) | m2]) AS chainLen`,
|
|
5061
|
-
{
|
|
5062
|
-
messageId,
|
|
5063
|
-
sessionId,
|
|
5064
|
-
accountId,
|
|
5065
|
-
role,
|
|
5066
|
-
content,
|
|
5067
|
-
summary,
|
|
5068
|
-
...createdAt ? { createdAt } : {},
|
|
5069
|
-
...embedding ? { embedding } : {},
|
|
5070
|
-
...sender ? { senderVisitorId: sender.visitorId, senderName: sender.displayName } : {},
|
|
5071
|
-
...tokens?.inputTokens != null ? { inputTokens: neo4j.int(tokens.inputTokens) } : {},
|
|
5072
|
-
...tokens?.outputTokens != null ? { outputTokens: neo4j.int(tokens.outputTokens) } : {},
|
|
5073
|
-
...tokens?.cacheReadTokens != null ? { cacheReadTokens: neo4j.int(tokens.cacheReadTokens) } : {},
|
|
5074
|
-
...tokens?.cacheCreationTokens != null ? { cacheCreationTokens: neo4j.int(tokens.cacheCreationTokens) } : {},
|
|
5075
|
-
components: (components ?? []).map((comp) => ({
|
|
5076
|
-
componentId: comp.componentId,
|
|
5077
|
-
name: comp.name,
|
|
5078
|
-
data: comp.data,
|
|
5079
|
-
ordinal: neo4j.int(comp.ordinal),
|
|
5080
|
-
textOffset: neo4j.int(comp.textOffset),
|
|
5081
|
-
// null for non-persistent / mime-skip / disk-fail
|
|
5082
|
-
// components; the FOREACH stamps this directly onto the
|
|
5083
|
-
// :Component row so audit + backfill read attachmentId without
|
|
5084
|
-
// re-deriving from componentId.
|
|
5085
|
-
artefactAttachmentId: comp.artefactAttachmentId ?? null
|
|
5086
|
-
})),
|
|
5087
|
-
//:KnowledgeDocument MERGE list. One row per
|
|
5088
|
-
// PERSISTENT_COMPONENTS component whose disk write succeeded;
|
|
5089
|
-
// text-only / mime-skip / disk-fail components are absent so
|
|
5090
|
-
// the FOREACH no-ops on them. attachmentId here matches the
|
|
5091
|
-
// value the stream-parser stamped after writeAttachment.
|
|
5092
|
-
knowledgeDocs: (components ?? []).filter((comp) => typeof comp.artefactAttachmentId === "string").map((comp) => ({
|
|
5093
|
-
attachmentId: comp.artefactAttachmentId,
|
|
5094
|
-
title: comp.artefactTitle ?? "",
|
|
5095
|
-
mimeType: comp.artefactMimeType ?? ""
|
|
5096
|
-
})),
|
|
5097
|
-
attachments: (attachments ?? []).map((att) => ({
|
|
5098
|
-
attachmentId: att.attachmentId,
|
|
5099
|
-
filename: att.filename,
|
|
5100
|
-
mimeType: att.mimeType,
|
|
5101
|
-
sizeBytes: neo4j.int(att.sizeBytes),
|
|
5102
|
-
storagePath: att.storagePath,
|
|
5103
|
-
ordinal: neo4j.int(att.ordinal)
|
|
5104
|
-
}))
|
|
5105
|
-
}
|
|
5106
|
-
);
|
|
5107
|
-
if (result.records.length === 0) {
|
|
5108
|
-
console.error(`[persist] Neo4j write skipped \u2014 conversation not found: ${sessionId.slice(0, 8)}\u2026`);
|
|
5109
|
-
return null;
|
|
5110
|
-
}
|
|
5111
|
-
const record = result.records[0];
|
|
5112
|
-
const prevMessageId = record.get("prevMessageId") ?? null;
|
|
5113
|
-
const summarySetThisWrite = record.get("summarySetThisWrite") === true;
|
|
5114
|
-
const chainLenRaw = record.get("chainLen");
|
|
5115
|
-
const chainLen = typeof chainLenRaw === "bigint" ? Number(chainLenRaw) : typeof chainLenRaw?.toNumber === "function" ? chainLenRaw.toNumber() : Number(chainLenRaw ?? 0);
|
|
5116
|
-
const messageSublabel = role === "user" ? "UserMessage" : "AssistantMessage";
|
|
5117
|
-
console.error(`[neo4j-store] append-message sessionId=${sessionId.slice(0, 8)}\u2026 messageId=${messageId.slice(0, 8)}\u2026 prev=${prevMessageId ? prevMessageId.slice(0, 8) + "\u2026" : "null"} chainLen=${chainLen} waited=${waited} sublabel=${messageSublabel}`);
|
|
5118
|
-
if (summarySetThisWrite) {
|
|
5119
|
-
console.error(`[neo4j-store] conversation-summary-set sessionId=${sessionId.slice(0, 8)}\u2026 len=${summary.length}`);
|
|
5120
|
-
}
|
|
5121
|
-
const componentList = components ?? [];
|
|
5122
|
-
if (componentList.length > 0) {
|
|
5123
|
-
const relsCreated = result.summary.counters.updates().relationshipsCreated;
|
|
5124
|
-
const expectedComponentEdges = componentList.length;
|
|
5125
|
-
const baseEdges = relsCreated - expectedComponentEdges;
|
|
5126
|
-
if (baseEdges < 1) {
|
|
5127
|
-
console.error(`[neo4j-store] persist-component WARN sessionId=${sessionId.slice(0, 8)}\u2026 messageId=${messageId.slice(0, 8)}\u2026 relsCreated=${relsCreated} expected\u2265${1 + expectedComponentEdges} \u2014 component edges may not have been created`);
|
|
5128
|
-
}
|
|
5129
|
-
for (const comp of componentList) {
|
|
5130
|
-
console.error(`[neo4j-store] persist-component sessionId=${sessionId.slice(0, 8)}\u2026 messageId=${messageId.slice(0, 8)}\u2026 componentId=${comp.componentId.slice(0, 8)}\u2026 name=${comp.name} dataLen=${comp.data.length} ordinal=${comp.ordinal} textOffset=${comp.textOffset}`);
|
|
5131
|
-
}
|
|
5132
|
-
}
|
|
5133
|
-
const attachmentList = attachments ?? [];
|
|
5134
|
-
if (attachmentList.length > 0) {
|
|
5135
|
-
const relsCreated = result.summary.counters.updates().relationshipsCreated;
|
|
5136
|
-
const expectedAttachmentEdges = attachmentList.length;
|
|
5137
|
-
const expectedComponentEdges = (components ?? []).length;
|
|
5138
|
-
const baseEdges = relsCreated - expectedComponentEdges - expectedAttachmentEdges;
|
|
5139
|
-
if (baseEdges < 1) {
|
|
5140
|
-
console.error(`[neo4j-store] persist-attachment WARN sessionId=${sessionId.slice(0, 8)}\u2026 messageId=${messageId.slice(0, 8)}\u2026 relsCreated=${relsCreated} expected\u2265${1 + expectedComponentEdges + expectedAttachmentEdges} \u2014 attachment edges may not have been created`);
|
|
5141
|
-
}
|
|
5142
|
-
for (const att of attachmentList) {
|
|
5143
|
-
console.error(`[neo4j-store] persist-attachment sessionId=${sessionId.slice(0, 8)}\u2026 messageId=${messageId.slice(0, 8)}\u2026 attachmentId=${att.attachmentId.slice(0, 8)}\u2026 filename=${att.filename} mimeType=${att.mimeType} sizeBytes=${att.sizeBytes} ordinal=${att.ordinal}`);
|
|
5144
|
-
}
|
|
5145
|
-
}
|
|
5146
|
-
console.error(`[persist] sessionId=${sessionId.slice(0, 8)}\u2026 role=${role} len=${content.length}${sender ? ` sender=${sender.displayName}` : ""}`);
|
|
5147
|
-
return messageId;
|
|
5148
|
-
} catch (err) {
|
|
5149
|
-
console.error(`[persist] Neo4j write failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5150
|
-
return null;
|
|
5151
|
-
} finally {
|
|
5152
|
-
release();
|
|
5153
|
-
if (persistMessageLocks.get(sessionId) === chained) {
|
|
5154
|
-
persistMessageLocks.delete(sessionId);
|
|
5155
|
-
}
|
|
5156
|
-
await session.close();
|
|
5157
|
-
}
|
|
5158
|
-
}
|
|
5159
|
-
async function getAgentSessionIdForConversation(sessionId) {
|
|
5160
|
-
const session = getSession();
|
|
5161
|
-
try {
|
|
5162
|
-
const result = await session.run(
|
|
5163
|
-
`MATCH (c:Conversation {sessionId: $sessionId})
|
|
5164
|
-
RETURN c.agentSessionId AS agentSessionId`,
|
|
5165
|
-
{ sessionId }
|
|
5166
|
-
);
|
|
5167
|
-
const value = result.records[0]?.get("agentSessionId");
|
|
5168
|
-
return typeof value === "string" && value.length > 0 ? value : null;
|
|
5169
|
-
} catch (err) {
|
|
5170
|
-
console.error(`[persist] agent-session-id read failed convId=${sessionId.slice(0, 8)}\u2026 error=${err instanceof Error ? err.message : String(err)}`);
|
|
5171
|
-
return null;
|
|
5172
|
-
} finally {
|
|
5173
|
-
await session.close();
|
|
5174
|
-
}
|
|
5175
|
-
}
|
|
5176
|
-
async function getRecentMessages(sessionId, limit = 50) {
|
|
5177
|
-
const session = getSession();
|
|
5178
|
-
try {
|
|
5179
|
-
const result = await session.run(
|
|
5180
|
-
`MATCH (tail:Message {sessionId: $sessionId})
|
|
5181
|
-
WHERE NOT (tail)-[:NEXT]->(:Message)
|
|
5182
|
-
WITH tail ORDER BY tail.createdAt DESC LIMIT 1
|
|
5183
|
-
MATCH path = (m:Message)-[:NEXT*0..]->(tail)
|
|
5184
|
-
WHERE m.sessionId = $sessionId
|
|
5185
|
-
AND length(path) < $limit
|
|
5186
|
-
WITH m, length(path) AS depthFromTail
|
|
5187
|
-
OPTIONAL MATCH (m)-[:HAS_COMPONENT]->(c:Component)
|
|
5188
|
-
WITH m, depthFromTail, c ORDER BY c.ordinal ASC
|
|
5189
|
-
WITH m, depthFromTail,
|
|
5190
|
-
[comp IN collect(c) WHERE comp IS NOT NULL | comp {.*}] AS components
|
|
5191
|
-
OPTIONAL MATCH (m)-[:HAS_ATTACHMENT]->(a:Attachment)
|
|
5192
|
-
WITH m, depthFromTail, components, a ORDER BY a.ordinal ASC
|
|
5193
|
-
WITH m, depthFromTail, components,
|
|
5194
|
-
[att IN collect(a) WHERE att IS NOT NULL | att {.*}] AS attachments
|
|
5195
|
-
RETURN m.messageId AS messageId, m.role AS role, m.content AS content,
|
|
5196
|
-
m.createdAt AS createdAt, components, attachments
|
|
5197
|
-
ORDER BY depthFromTail DESC`,
|
|
5198
|
-
{ sessionId, limit: neo4j.int(limit) }
|
|
5199
|
-
);
|
|
5200
|
-
return result.records.map((r) => {
|
|
5201
|
-
const rawComponents = r.get("components") ?? [];
|
|
5202
|
-
const components = rawComponents.map((c) => {
|
|
5203
|
-
const rawAttachmentId = c.attachmentId;
|
|
5204
|
-
const attachmentId = typeof rawAttachmentId === "string" && rawAttachmentId.length > 0 ? rawAttachmentId : void 0;
|
|
5205
|
-
return {
|
|
5206
|
-
componentId: String(c.componentId ?? ""),
|
|
5207
|
-
name: String(c.name ?? ""),
|
|
5208
|
-
data: String(c.data ?? ""),
|
|
5209
|
-
ordinal: typeof c.ordinal?.toNumber === "function" ? c.ordinal.toNumber() : Number(c.ordinal ?? 0),
|
|
5210
|
-
textOffset: typeof c.textOffset?.toNumber === "function" ? c.textOffset.toNumber() : Number(c.textOffset ?? 0),
|
|
5211
|
-
submitted: c.submitted === true,
|
|
5212
|
-
...attachmentId ? { attachmentId } : {}
|
|
5213
|
-
};
|
|
5214
|
-
});
|
|
5215
|
-
const rawAttachments = r.get("attachments") ?? [];
|
|
5216
|
-
const attachments = rawAttachments.map((a) => ({
|
|
5217
|
-
attachmentId: String(a.attachmentId ?? ""),
|
|
5218
|
-
filename: String(a.filename ?? ""),
|
|
5219
|
-
mimeType: String(a.mimeType ?? ""),
|
|
5220
|
-
sizeBytes: typeof a.sizeBytes?.toNumber === "function" ? a.sizeBytes.toNumber() : Number(a.sizeBytes ?? 0),
|
|
5221
|
-
storagePath: String(a.storagePath ?? ""),
|
|
5222
|
-
ordinal: typeof a.ordinal?.toNumber === "function" ? a.ordinal.toNumber() : Number(a.ordinal ?? 0),
|
|
5223
|
-
accountId: String(a.accountId ?? "")
|
|
5224
|
-
}));
|
|
5225
|
-
return {
|
|
5226
|
-
messageId: r.get("messageId"),
|
|
5227
|
-
role: r.get("role"),
|
|
5228
|
-
content: r.get("content"),
|
|
5229
|
-
createdAt: String(r.get("createdAt")),
|
|
5230
|
-
components,
|
|
5231
|
-
attachments
|
|
5232
|
-
};
|
|
5233
|
-
});
|
|
5234
|
-
} catch (err) {
|
|
5235
|
-
console.error(`[persist] getRecentMessages failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5236
|
-
return [];
|
|
5237
|
-
} finally {
|
|
5238
|
-
await session.close();
|
|
5239
|
-
}
|
|
5240
|
-
}
|
|
5241
|
-
async function verifyConversationOwnership(sessionId, accountId) {
|
|
5242
|
-
const session = getSession();
|
|
5243
|
-
try {
|
|
5244
|
-
const result = await session.run(
|
|
5245
|
-
`MATCH (c:Conversation {sessionId: $sessionId, accountId: $accountId})
|
|
5246
|
-
RETURN c.sessionId AS id LIMIT 1`,
|
|
5247
|
-
{ sessionId, accountId }
|
|
5248
|
-
);
|
|
5249
|
-
return result.records.length > 0;
|
|
5250
|
-
} catch (err) {
|
|
5251
|
-
console.error(`[persist] verifyConversationOwnership failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5252
|
-
return false;
|
|
5253
|
-
} finally {
|
|
5254
|
-
await session.close();
|
|
5255
|
-
}
|
|
5256
|
-
}
|
|
5257
|
-
async function getConversationOwner(sessionId) {
|
|
5258
|
-
const session = getSession();
|
|
5259
|
-
try {
|
|
5260
|
-
const result = await session.run(
|
|
5261
|
-
`MATCH (c:Conversation {sessionId: $sessionId})
|
|
5262
|
-
RETURN c.accountId AS accountId, c.userId AS userId LIMIT 1`,
|
|
5263
|
-
{ sessionId }
|
|
5264
|
-
);
|
|
5265
|
-
const record = result.records[0];
|
|
5266
|
-
if (!record) return null;
|
|
5267
|
-
const accountId = record.get("accountId");
|
|
5268
|
-
const userId = record.get("userId");
|
|
5269
|
-
if (!accountId) return null;
|
|
5270
|
-
return { accountId, userId: userId ?? null };
|
|
5271
|
-
} catch (err) {
|
|
5272
|
-
console.error(`[persist] getConversationOwner failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5273
|
-
return null;
|
|
5274
|
-
} finally {
|
|
5275
|
-
await session.close();
|
|
5276
|
-
}
|
|
5277
|
-
}
|
|
5278
|
-
async function verifyAndGetConversationUpdatedAt(sessionId, accountId) {
|
|
5279
|
-
const session = getSession();
|
|
5280
|
-
try {
|
|
5281
|
-
const result = await session.run(
|
|
5282
|
-
`MATCH (c:Conversation {sessionId: $sessionId, accountId: $accountId})
|
|
5283
|
-
RETURN toString(c.updatedAt) AS updatedAt LIMIT 1`,
|
|
5284
|
-
{ sessionId, accountId }
|
|
5285
|
-
);
|
|
5286
|
-
const record = result.records[0];
|
|
5287
|
-
return record ? record.get("updatedAt") : null;
|
|
5288
|
-
} catch (err) {
|
|
5289
|
-
console.error(`[persist] verifyAndGetConversationUpdatedAt failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5290
|
-
return null;
|
|
5291
|
-
} finally {
|
|
5292
|
-
await session.close();
|
|
5293
|
-
}
|
|
5294
|
-
}
|
|
5295
|
-
async function listAdminSessions(accountId, userId, limit = 20) {
|
|
5296
|
-
const session = getSession();
|
|
5297
|
-
try {
|
|
5298
|
-
const result = await session.run(
|
|
5299
|
-
`MATCH (c:Conversation {accountId: $accountId, agentType: 'admin', userId: $userId})
|
|
5300
|
-
WHERE NOT c:Trashed
|
|
5301
|
-
RETURN c.sessionId AS sessionId,
|
|
5302
|
-
c.name AS name,
|
|
5303
|
-
c.updatedAt AS updatedAt,
|
|
5304
|
-
c.channel AS channel
|
|
5305
|
-
ORDER BY c.updatedAt DESC
|
|
5306
|
-
LIMIT $limit`,
|
|
5307
|
-
{ accountId, userId, limit: neo4j.int(limit) }
|
|
5308
|
-
);
|
|
5309
|
-
return result.records.map((r) => ({
|
|
5310
|
-
sessionId: r.get("sessionId"),
|
|
5311
|
-
name: r.get("name"),
|
|
5312
|
-
updatedAt: String(r.get("updatedAt")),
|
|
5313
|
-
channel: r.get("channel")
|
|
5314
|
-
}));
|
|
5315
|
-
} catch (err) {
|
|
5316
|
-
console.error(`[persist] listAdminSessions failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5317
|
-
return [];
|
|
5318
|
-
} finally {
|
|
5319
|
-
await session.close();
|
|
5320
|
-
}
|
|
5321
|
-
}
|
|
5322
|
-
async function deleteConversation(sessionId) {
|
|
5323
|
-
const session = getSession();
|
|
5324
|
-
try {
|
|
5325
|
-
const result = await session.run(
|
|
5326
|
-
`MATCH (c:Conversation {sessionId: $sessionId})
|
|
5327
|
-
OPTIONAL MATCH (m:Message)-[:PART_OF]->(c)
|
|
5328
|
-
OPTIONAL MATCH (m)-[:HAS_COMPONENT]->(comp:Component)
|
|
5329
|
-
OPTIONAL MATCH (m)-[:HAS_ATTACHMENT]->(att:Attachment)
|
|
5330
|
-
OPTIONAL MATCH (c)-[:HAS_TRANSCRIPT]->(t:ClaudeSession)
|
|
5331
|
-
DETACH DELETE att, comp, m, t, c
|
|
5332
|
-
RETURN count(c) AS deleted`,
|
|
5333
|
-
{ sessionId }
|
|
5334
|
-
);
|
|
5335
|
-
const deleted = result.records[0]?.get("deleted");
|
|
5336
|
-
const count = typeof deleted === "object" && deleted !== null ? Number(deleted) : Number(deleted ?? 0);
|
|
5337
|
-
const found = count > 0;
|
|
5338
|
-
console.error(`[persist] deleteConversation ${sessionId.slice(0, 8)}\u2026: ${found ? "deleted" : "not found"}`);
|
|
5339
|
-
return { found };
|
|
5340
|
-
} finally {
|
|
5341
|
-
await session.close();
|
|
5342
|
-
}
|
|
5343
|
-
}
|
|
5344
|
-
async function linkConversationTranscript(sessionId, claudeSessionId, accountId) {
|
|
5345
|
-
if (!sessionId || !claudeSessionId || !accountId) {
|
|
5346
|
-
console.error(`[transcript-link] schema-violation sessionId=${sessionId ? "set" : "EMPTY"} claudeSessionId=${claudeSessionId ? "set" : "EMPTY"} accountId=${accountId ? "set" : "EMPTY"}`);
|
|
5347
|
-
return { linked: false };
|
|
5348
|
-
}
|
|
5349
|
-
const session = getSession();
|
|
5350
|
-
try {
|
|
5351
|
-
const result = await session.run(
|
|
5352
|
-
`MATCH (c:Conversation {sessionId: $sessionId, accountId: $accountId})
|
|
5353
|
-
MERGE (c)-[:HAS_TRANSCRIPT]->(t:ClaudeSession {claudeSessionId: $claudeSessionId})
|
|
5354
|
-
ON CREATE SET
|
|
5355
|
-
t.accountId = $accountId,
|
|
5356
|
-
t.createdAt = datetime()
|
|
5357
|
-
RETURN count(t) AS linked`,
|
|
5358
|
-
{ sessionId, claudeSessionId, accountId }
|
|
5359
|
-
);
|
|
5360
|
-
const linked = Number(result.records[0]?.get("linked") ?? 0) > 0;
|
|
5361
|
-
if (!linked) {
|
|
5362
|
-
console.error(`[transcript-link] convId=${sessionId.slice(0, 8)}\u2026 claudeSessionId=${claudeSessionId.slice(0, 8)}\u2026 status=skipped reason=conversation-not-found`);
|
|
5363
|
-
}
|
|
5364
|
-
return { linked };
|
|
5365
|
-
} catch (err) {
|
|
5366
|
-
console.error(`[transcript-link] convId=${sessionId.slice(0, 8)}\u2026 claudeSessionId=${claudeSessionId.slice(0, 8)}\u2026 status=failed error=${err instanceof Error ? err.message : String(err)}`);
|
|
5367
|
-
return { linked: false };
|
|
5368
|
-
} finally {
|
|
5369
|
-
await session.close();
|
|
5370
|
-
}
|
|
5371
|
-
}
|
|
5372
|
-
async function getConversationClaudeSessionIds(sessionId, accountId) {
|
|
5373
|
-
const session = getSession();
|
|
5374
|
-
try {
|
|
5375
|
-
const result = await session.run(
|
|
5376
|
-
`MATCH (c:Conversation {sessionId: $sessionId, accountId: $accountId})-[:HAS_TRANSCRIPT]->(t:ClaudeSession)
|
|
5377
|
-
RETURN t.claudeSessionId AS claudeSessionId
|
|
5378
|
-
ORDER BY t.createdAt ASC`,
|
|
5379
|
-
{ sessionId, accountId }
|
|
5380
|
-
);
|
|
5381
|
-
return result.records.map((r) => r.get("claudeSessionId")).filter((v) => typeof v === "string" && v.length > 0);
|
|
5382
|
-
} finally {
|
|
5383
|
-
await session.close();
|
|
5384
|
-
}
|
|
5385
|
-
}
|
|
5386
|
-
async function renameConversation(sessionId, label) {
|
|
5387
|
-
let embedding = null;
|
|
5388
|
-
try {
|
|
5389
|
-
embedding = await embed(label);
|
|
5390
|
-
} catch (err) {
|
|
5391
|
-
console.error(`[persist] manual-label: embedding failed, persisting without: ${err instanceof Error ? err.message : String(err)}`);
|
|
5392
|
-
}
|
|
5393
|
-
const session = getSession();
|
|
5394
|
-
try {
|
|
5395
|
-
await session.run(
|
|
5396
|
-
`MATCH (c:Conversation {sessionId: $sessionId})
|
|
5397
|
-
SET c.name = $label, c.updatedAt = datetime()
|
|
5398
|
-
${embedding ? ", c.embedding = $embedding" : ""}`,
|
|
5399
|
-
{ sessionId, label, ...embedding ? { embedding } : {} }
|
|
5400
|
-
);
|
|
5401
|
-
console.error(`[persist] manual-label: renamed ${sessionId} to "${label}"${embedding ? " (embedded)" : ""}`);
|
|
5402
|
-
} finally {
|
|
5403
|
-
await session.close();
|
|
5404
|
-
}
|
|
5405
|
-
}
|
|
5406
|
-
var DECAY_THRESHOLD_DAYS = 14;
|
|
5407
|
-
var DECAY_RATE_PER_DAY = 0.05;
|
|
5408
|
-
var INJECTION_THRESHOLD = 0.4;
|
|
5409
|
-
var MAX_SUMMARY_PREFERENCES = 30;
|
|
5410
|
-
async function getUserTimezone(accountId, userId) {
|
|
5411
|
-
const session = getSession();
|
|
5412
|
-
try {
|
|
5413
|
-
const query = userId ? `MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
|
|
5414
|
-
RETURN up.timezone AS timezone` : `MATCH (up:UserProfile {accountId: $accountId})
|
|
5415
|
-
RETURN up.timezone AS timezone ORDER BY up.createdAt LIMIT 1`;
|
|
5416
|
-
const result = await session.run(query, { accountId, userId: userId ?? "" });
|
|
5417
|
-
if (result.records.length === 0) return null;
|
|
5418
|
-
const tz = result.records[0].get("timezone");
|
|
5419
|
-
return tz && tz.trim().length > 0 ? tz : null;
|
|
5420
|
-
} catch (err) {
|
|
5421
|
-
console.error(`[datetime] getUserTimezone failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5422
|
-
return null;
|
|
5423
|
-
} finally {
|
|
5424
|
-
await session.close();
|
|
5425
|
-
}
|
|
5426
|
-
}
|
|
5427
|
-
async function loadAdminUserName(userId) {
|
|
5428
|
-
const session = getSession();
|
|
5429
|
-
try {
|
|
5430
|
-
const result = await session.run(
|
|
5431
|
-
`MATCH (au:AdminUser {userId: $userId})-[:OWNS]->(p:Person)
|
|
5432
|
-
WHERE p.accountId = au.accountId
|
|
5433
|
-
RETURN p.givenName AS givenName, p.familyName AS familyName, p.avatar AS avatar
|
|
5434
|
-
LIMIT 1`,
|
|
5435
|
-
{ userId }
|
|
5436
|
-
);
|
|
5437
|
-
if (result.records.length === 0) {
|
|
5438
|
-
return { source: "fallback", reason: "no-person-node" };
|
|
5439
|
-
}
|
|
5440
|
-
const givenName = result.records[0].get("givenName");
|
|
5441
|
-
const familyName = result.records[0].get("familyName");
|
|
5442
|
-
const avatar = result.records[0].get("avatar");
|
|
5443
|
-
if (!givenName || givenName.trim().length === 0) {
|
|
5444
|
-
return { source: "fallback", reason: "no-givenName" };
|
|
5445
|
-
}
|
|
5446
|
-
const trimmedFamily = familyName && familyName.trim().length > 0 ? familyName.trim() : null;
|
|
5447
|
-
const trimmedAvatar = avatar && avatar.trim().length > 0 ? avatar.trim() : null;
|
|
5448
|
-
const joined = trimmedFamily ? `${givenName.trim()} ${trimmedFamily}` : givenName.trim();
|
|
5449
|
-
return { source: "neo4j", joined, givenName: givenName.trim(), familyName: trimmedFamily, avatar: trimmedAvatar };
|
|
5450
|
-
} catch (err) {
|
|
5451
|
-
console.error(`[admin-identity] loadAdminUserName failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5452
|
-
return { source: "fallback", reason: "neo4j-unreachable" };
|
|
5453
|
-
} finally {
|
|
5454
|
-
await session.close();
|
|
5455
|
-
}
|
|
5456
|
-
}
|
|
5457
|
-
async function loadPersonNameByElementId(accountId, personId) {
|
|
5458
|
-
const session = getSession();
|
|
5459
|
-
try {
|
|
5460
|
-
const result = await session.run(
|
|
5461
|
-
`MATCH (p:Person {accountId: $accountId})
|
|
5462
|
-
WHERE elementId(p) = $personId
|
|
5463
|
-
RETURN p.givenName AS givenName, p.familyName AS familyName
|
|
5464
|
-
LIMIT 1`,
|
|
5465
|
-
{ accountId, personId },
|
|
5466
|
-
{ timeout: 2e3 }
|
|
5467
|
-
);
|
|
5468
|
-
if (result.records.length === 0) return null;
|
|
5469
|
-
const given = result.records[0].get("givenName");
|
|
5470
|
-
const family = result.records[0].get("familyName");
|
|
5471
|
-
if (!given || given.trim().length === 0) return null;
|
|
5472
|
-
const fam = family && family.trim().length > 0 ? family.trim() : null;
|
|
5473
|
-
return fam ? `${given.trim()} ${fam}` : given.trim();
|
|
5474
|
-
} catch (err) {
|
|
5475
|
-
console.error(
|
|
5476
|
-
`[admin-sessions-list] loadPersonNameByElementId failed: ${err instanceof Error ? err.message : String(err)}`
|
|
5477
|
-
);
|
|
5478
|
-
return null;
|
|
5479
|
-
} finally {
|
|
5480
|
-
await session.close();
|
|
5481
|
-
}
|
|
5482
|
-
}
|
|
5483
|
-
async function writeAdminUserAndPerson(params) {
|
|
5484
|
-
const { userId, fullName, accountId } = params;
|
|
5485
|
-
const trimmed = fullName.trim();
|
|
5486
|
-
if (!trimmed) {
|
|
5487
|
-
throw new Error("writeAdminUserAndPerson: fullName cannot be empty");
|
|
5488
|
-
}
|
|
5489
|
-
const firstSpace = trimmed.search(/\s/);
|
|
5490
|
-
const givenName = firstSpace === -1 ? trimmed : trimmed.slice(0, firstSpace).trim();
|
|
5491
|
-
const familyName = firstSpace === -1 ? null : trimmed.slice(firstSpace + 1).trim() || null;
|
|
5492
|
-
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
5493
|
-
let personEmbedding = null;
|
|
5494
|
-
try {
|
|
5495
|
-
personEmbedding = await embed(
|
|
5496
|
-
`[Person] ${[givenName, familyName].filter(Boolean).join(" ")} role: admin-personal`
|
|
5497
|
-
);
|
|
5498
|
-
} catch (err) {
|
|
5499
|
-
process.stderr.write(
|
|
5500
|
-
`[graph-write] warn reason=embed-failed labels=Person agent=admin-bootstrap detail=${err instanceof Error ? err.message : String(err)}
|
|
5501
|
-
`
|
|
5502
|
-
);
|
|
5503
|
-
}
|
|
5504
|
-
const serverTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5505
|
-
const session = getSession();
|
|
5506
|
-
try {
|
|
5507
|
-
const result = await session.run(
|
|
5508
|
-
`MERGE (au:AdminUser {userId: $userId})
|
|
5509
|
-
// Task 669 \u2014 stamp accountId/role at creation so the account-scoped
|
|
5510
|
-
// graph-write gate (MATCH (au:AdminUser {accountId:$accountId})) matches
|
|
5511
|
-
// on the operator's first memory write. Mirrors seed-neo4j.sh:175-181
|
|
5512
|
-
// and the sibling writers (neo4j-store.ts:537,:931). COALESCE on MATCH
|
|
5513
|
-
// preserves an already-stamped node.
|
|
5514
|
-
ON CREATE SET au.createdAt = $now,
|
|
5515
|
-
au.accountId = $accountId, au.role = 'owner'
|
|
5516
|
-
ON MATCH SET au.updatedAt = $now,
|
|
5517
|
-
au.accountId = COALESCE(au.accountId, $accountId),
|
|
5518
|
-
au.role = COALESCE(au.role, 'owner')
|
|
5519
|
-
WITH au
|
|
5520
|
-
|
|
5521
|
-
// Task 1150 \u2014 UserProfile guaranteed off the just-MERGEd AdminUser.
|
|
5522
|
-
MERGE (au)-[:HAS_PROFILE]->(up:UserProfile {accountId: $accountId, userId: $userId})
|
|
5523
|
-
ON CREATE SET
|
|
5524
|
-
up.createdAt = $now,
|
|
5525
|
-
up.updatedAt = $now,
|
|
5526
|
-
up.profileVersion = 0,
|
|
5527
|
-
up.scope = 'admin',
|
|
5528
|
-
up.timezone = $serverTimezone,
|
|
5529
|
-
up.timezoneSource = 'server-default'
|
|
5530
|
-
ON MATCH SET
|
|
5531
|
-
up.timezoneSource = CASE WHEN up.timezone IS NULL OR trim(up.timezone) = '' THEN 'server-default' ELSE up.timezoneSource END,
|
|
5532
|
-
up.timezone = CASE WHEN up.timezone IS NULL OR trim(up.timezone) = '' THEN $serverTimezone ELSE up.timezone END
|
|
5533
|
-
WITH au, (up.createdAt = $now) AS profileIsNew
|
|
5534
|
-
|
|
5535
|
-
// Case-insensitive Person reuse: same accountId, same givenName, same familyName
|
|
5536
|
-
// (null/empty familyName collapsed to '' so 'Joel' does NOT match 'Joel Smalley').
|
|
5537
|
-
OPTIONAL MATCH (existingPerson:Person {accountId: $accountId})
|
|
5538
|
-
WHERE toLower(existingPerson.givenName) = toLower($givenName)
|
|
5539
|
-
AND coalesce(toLower(existingPerson.familyName), '') = coalesce(toLower($familyName), '')
|
|
5540
|
-
WITH au, profileIsNew, existingPerson
|
|
5541
|
-
|
|
5542
|
-
CALL {
|
|
5543
|
-
WITH au, existingPerson
|
|
5544
|
-
WITH au, existingPerson WHERE existingPerson IS NOT NULL
|
|
5545
|
-
MERGE (au)-[:OWNS]->(existingPerson)
|
|
5546
|
-
RETURN existingPerson AS p, true AS reused
|
|
5547
|
-
UNION
|
|
5548
|
-
WITH au, existingPerson
|
|
5549
|
-
WITH au WHERE existingPerson IS NULL
|
|
5550
|
-
CREATE (newPerson:Person {
|
|
5551
|
-
accountId: $accountId,
|
|
5552
|
-
givenName: $givenName,
|
|
5553
|
-
familyName: $familyName,
|
|
5554
|
-
role: 'admin-personal',
|
|
5555
|
-
scope: 'admin',
|
|
5556
|
-
embedding: $personEmbedding,
|
|
5557
|
-
createdAt: $now
|
|
5558
|
-
})
|
|
5559
|
-
MERGE (au)-[:OWNS]->(newPerson)
|
|
5560
|
-
RETURN newPerson AS p, false AS reused
|
|
5561
|
-
}
|
|
5562
|
-
RETURN reused, profileIsNew, elementId(p) AS personElementId,
|
|
5563
|
-
p.givenName AS givenName, p.familyName AS familyName`,
|
|
5564
|
-
{ userId, accountId, givenName, familyName, now, personEmbedding, serverTimezone }
|
|
5565
|
-
);
|
|
5566
|
-
if (result.records.length === 0) {
|
|
5567
|
-
throw new Error("writeAdminUserAndPerson: no record returned");
|
|
5568
|
-
}
|
|
5569
|
-
const record = result.records[0];
|
|
5570
|
-
if (record.get("profileIsNew")) {
|
|
5571
|
-
console.error(
|
|
5572
|
-
`[profile] provisioned at admin-bootstrap userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 timezone=${serverTimezone}`
|
|
5573
|
-
);
|
|
5574
|
-
}
|
|
5575
|
-
return {
|
|
5576
|
-
personReused: record.get("reused"),
|
|
5577
|
-
personElementId: record.get("personElementId"),
|
|
5578
|
-
givenName: record.get("givenName"),
|
|
5579
|
-
familyName: record.get("familyName")
|
|
5580
|
-
};
|
|
5581
|
-
} finally {
|
|
5582
|
-
await session.close();
|
|
5583
|
-
}
|
|
5584
|
-
}
|
|
5585
|
-
var PROFILE_CATEGORIES = [
|
|
5586
|
-
"communication",
|
|
5587
|
-
"scheduling",
|
|
5588
|
-
"decision",
|
|
5589
|
-
"workflow",
|
|
5590
|
-
"content",
|
|
5591
|
-
"interaction"
|
|
5592
|
-
];
|
|
5593
|
-
var PROFILE_FIELD_TEMPLATES = {
|
|
5594
|
-
communication: [
|
|
5595
|
-
"preferredChannel",
|
|
5596
|
-
"quietHours",
|
|
5597
|
-
"responseLatencyExpectation",
|
|
5598
|
-
"formalityLevel",
|
|
5599
|
-
"escalationPath"
|
|
5600
|
-
],
|
|
5601
|
-
scheduling: [
|
|
5602
|
-
"workdayStartTime",
|
|
5603
|
-
"workdayEndTime",
|
|
5604
|
-
"weekendAvailability",
|
|
5605
|
-
"meetingBlockPreference",
|
|
5606
|
-
"focusBlockTiming"
|
|
5607
|
-
],
|
|
5608
|
-
decision: [
|
|
5609
|
-
"riskTolerance",
|
|
5610
|
-
"autonomyBoundary",
|
|
5611
|
-
"evidenceThreshold",
|
|
5612
|
-
"reversibilityPreference",
|
|
5613
|
-
"stakeholderConsultation"
|
|
5614
|
-
],
|
|
5615
|
-
workflow: [
|
|
5616
|
-
"batchingPreference",
|
|
5617
|
-
"taskGranularity",
|
|
5618
|
-
"deepWorkBlocks",
|
|
5619
|
-
"statusUpdateCadence",
|
|
5620
|
-
"toolingChoices"
|
|
5621
|
-
],
|
|
5622
|
-
content: [
|
|
5623
|
-
"outputFormat",
|
|
5624
|
-
"lengthPreference",
|
|
5625
|
-
"citationStyle",
|
|
5626
|
-
"tonality"
|
|
5627
|
-
],
|
|
5628
|
-
interaction: [
|
|
5629
|
-
"addressForm",
|
|
5630
|
-
"humourTolerance",
|
|
5631
|
-
"directnessLevel",
|
|
5632
|
-
"emotionalRegisterPreference"
|
|
5633
|
-
]
|
|
5634
|
-
};
|
|
5635
|
-
var TOTAL_PROFILE_FIELDS = Object.values(PROFILE_FIELD_TEMPLATES).reduce(
|
|
5636
|
-
(n, fs) => n + fs.length,
|
|
5637
|
-
0
|
|
5638
|
-
);
|
|
5639
|
-
function computeAbsentFields(preferences, extraTemplates = {}) {
|
|
5640
|
-
const covered = /* @__PURE__ */ new Set();
|
|
5641
|
-
for (const pref of preferences) {
|
|
5642
|
-
const isCovered = pref.notApplicable === true || pref.confidence >= INJECTION_THRESHOLD;
|
|
5643
|
-
if (isCovered) covered.add(`${pref.category}.${pref.key}`);
|
|
5644
|
-
}
|
|
5645
|
-
const absent = [];
|
|
5646
|
-
for (const [category, fields] of Object.entries(PROFILE_FIELD_TEMPLATES)) {
|
|
5647
|
-
for (const field of fields) {
|
|
5648
|
-
const dotted = `${category}.${field}`;
|
|
5649
|
-
if (!covered.has(dotted)) absent.push(dotted);
|
|
5650
|
-
}
|
|
5651
|
-
}
|
|
5652
|
-
for (const [category, fields] of Object.entries(extraTemplates)) {
|
|
5653
|
-
if (category in PROFILE_FIELD_TEMPLATES) continue;
|
|
5654
|
-
for (const field of fields) {
|
|
5655
|
-
const dotted = `${category}.${field}`;
|
|
5656
|
-
if (!covered.has(dotted)) absent.push(dotted);
|
|
5657
|
-
}
|
|
5658
|
-
}
|
|
5659
|
-
return absent;
|
|
5660
|
-
}
|
|
5661
|
-
function parsePluginProfileKeys(manifest) {
|
|
5662
|
-
const fm = manifest.match(/^---\n([\s\S]*?)\n---/);
|
|
5663
|
-
if (!fm) return [];
|
|
5664
|
-
const out = [];
|
|
5665
|
-
let inBlock = false;
|
|
5666
|
-
for (const line of fm[1].split("\n")) {
|
|
5667
|
-
if (/^profileKeys:/.test(line)) {
|
|
5668
|
-
inBlock = true;
|
|
5669
|
-
continue;
|
|
5670
|
-
}
|
|
5671
|
-
if (inBlock) {
|
|
5672
|
-
const m = line.match(/^\s+-\s+(.+?)\s*$/);
|
|
5673
|
-
if (m) out.push(m[1]);
|
|
5674
|
-
else break;
|
|
5675
|
-
}
|
|
5676
|
-
}
|
|
5677
|
-
return out;
|
|
5678
|
-
}
|
|
5679
|
-
function loadEnabledPluginProfileKeys(accountId) {
|
|
5680
|
-
const accountFile = resolve6(PLATFORM_ROOT3, "..", "data/accounts", accountId, "account.json");
|
|
5681
|
-
if (!existsSync7(accountFile)) return {};
|
|
5682
|
-
let enabled;
|
|
5683
|
-
try {
|
|
5684
|
-
const raw2 = readFileSync9(accountFile, "utf-8");
|
|
5685
|
-
const parsed = JSON.parse(raw2);
|
|
5686
|
-
enabled = Array.isArray(parsed.enabledPlugins) ? parsed.enabledPlugins.filter((p) => typeof p === "string") : [];
|
|
5687
|
-
} catch (err) {
|
|
5688
|
-
console.error(
|
|
5689
|
-
`[profile-keys] account.json read failed for ${accountId.slice(0, 8)}\u2026: ${err instanceof Error ? err.message : String(err)}`
|
|
5690
|
-
);
|
|
5691
|
-
return {};
|
|
5692
|
-
}
|
|
5693
|
-
const merged = {};
|
|
5694
|
-
for (const pluginName of enabled) {
|
|
5695
|
-
const manifestPath = resolve6(PLATFORM_ROOT3, "plugins", pluginName, "PLUGIN.md");
|
|
5696
|
-
if (!existsSync7(manifestPath)) continue;
|
|
5697
|
-
let manifest;
|
|
5698
|
-
try {
|
|
5699
|
-
manifest = readFileSync9(manifestPath, "utf-8");
|
|
5700
|
-
} catch {
|
|
5701
|
-
continue;
|
|
5702
|
-
}
|
|
5703
|
-
for (const dotted of parsePluginProfileKeys(manifest)) {
|
|
5704
|
-
const dot = dotted.indexOf(".");
|
|
5705
|
-
if (dot <= 0 || dot === dotted.length - 1) {
|
|
5706
|
-
console.error(`[profile-keys] malformed dotted key in ${pluginName}: ${dotted}`);
|
|
5707
|
-
continue;
|
|
5708
|
-
}
|
|
5709
|
-
const category = dotted.slice(0, dot);
|
|
5710
|
-
const key = dotted.slice(dot + 1);
|
|
5711
|
-
if (!merged[category]) merged[category] = /* @__PURE__ */ new Set();
|
|
5712
|
-
merged[category].add(key);
|
|
5713
|
-
}
|
|
5714
|
-
}
|
|
5715
|
-
const out = {};
|
|
5716
|
-
for (const [cat, set] of Object.entries(merged)) out[cat] = [...set];
|
|
5717
|
-
return out;
|
|
5718
|
-
}
|
|
5719
|
-
var sparseStateLogged = /* @__PURE__ */ new Set();
|
|
5720
|
-
async function loadUserProfile(accountId, userId, sessionId) {
|
|
5721
|
-
console.error(
|
|
5722
|
-
`[profile] loadUserProfile entry accountId=${accountId.slice(0, 8)}\u2026 userId=${userId} sessionId=${sessionId ? `${sessionId.slice(0, 8)}\u2026` : "<none>"}`
|
|
5723
|
-
);
|
|
5724
|
-
const session = getSession();
|
|
5725
|
-
try {
|
|
5726
|
-
const serverTimezone = Intl.DateTimeFormat().resolvedOptions().timeZone;
|
|
5727
|
-
const mergeResult = await session.run(
|
|
5728
|
-
`MATCH (au:AdminUser {userId: $userId})
|
|
5729
|
-
MERGE (au)-[:HAS_PROFILE]->(up:UserProfile {accountId: $accountId, userId: $userId})
|
|
5730
|
-
ON CREATE SET
|
|
5731
|
-
up.createdAt = $now,
|
|
5732
|
-
up.updatedAt = $now,
|
|
5733
|
-
up.profileVersion = 0,
|
|
5734
|
-
up.scope = 'admin',
|
|
5735
|
-
up.timezone = $serverTimezone,
|
|
5736
|
-
up.timezoneSource = 'server-default'
|
|
5737
|
-
ON MATCH SET
|
|
5738
|
-
up.timezoneSource = CASE WHEN up.timezone IS NULL OR trim(up.timezone) = '' THEN 'server-default' ELSE up.timezoneSource END,
|
|
5739
|
-
up.timezone = CASE WHEN up.timezone IS NULL OR trim(up.timezone) = '' THEN $serverTimezone ELSE up.timezone END
|
|
5740
|
-
RETURN up.createdAt = $now AS isNew`,
|
|
5741
|
-
{ accountId, userId, now: (/* @__PURE__ */ new Date()).toISOString(), serverTimezone }
|
|
5742
|
-
);
|
|
5743
|
-
if (mergeResult.records.length === 0) {
|
|
5744
|
-
console.error(`[profile] loadUserProfile: AdminUser not found for userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 \u2014 profile not created, invariant violated`);
|
|
5745
|
-
return null;
|
|
5746
|
-
}
|
|
5747
|
-
const isNew = mergeResult.records[0].get("isNew");
|
|
5748
|
-
if (isNew) {
|
|
5749
|
-
console.error(`[profile] created new profile: userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 timezone=${serverTimezone}`);
|
|
5750
|
-
}
|
|
5751
|
-
const nowMs = Date.now();
|
|
5752
|
-
const thresholdMs = DECAY_THRESHOLD_DAYS * 24 * 60 * 60 * 1e3;
|
|
5753
|
-
const staleResult = await session.run(
|
|
5754
|
-
`MATCH (up:UserProfile {accountId: $accountId, userId: $userId})-[:HAS_PREFERENCE]->(pref:Preference)
|
|
5755
|
-
WHERE pref.observedAt IS NOT NULL
|
|
5756
|
-
RETURN pref.preferenceId AS preferenceId,
|
|
5757
|
-
pref.observedAt AS observedAt,
|
|
5758
|
-
pref.confidence AS confidence`,
|
|
5759
|
-
{ accountId, userId }
|
|
5760
|
-
);
|
|
5761
|
-
let decayCount = 0;
|
|
5762
|
-
for (const record of staleResult.records) {
|
|
5763
|
-
const observedAt = record.get("observedAt");
|
|
5764
|
-
const confidence = record.get("confidence");
|
|
5765
|
-
const observedMs = new Date(observedAt).getTime();
|
|
5766
|
-
const ageMs = nowMs - observedMs;
|
|
5767
|
-
if (ageMs > thresholdMs && confidence > 0) {
|
|
5768
|
-
const daysPastThreshold = (ageMs - thresholdMs) / (24 * 60 * 60 * 1e3);
|
|
5769
|
-
const decayedConfidence = Math.max(0, confidence - DECAY_RATE_PER_DAY * daysPastThreshold);
|
|
5770
|
-
if (decayedConfidence !== confidence) {
|
|
5771
|
-
await session.run(
|
|
5772
|
-
`MATCH (pref:Preference {preferenceId: $preferenceId, accountId: $accountId})
|
|
5773
|
-
SET pref.confidence = $confidence`,
|
|
5774
|
-
{
|
|
5775
|
-
preferenceId: record.get("preferenceId"),
|
|
5776
|
-
accountId,
|
|
5777
|
-
confidence: decayedConfidence
|
|
5778
|
-
}
|
|
5779
|
-
);
|
|
5780
|
-
decayCount++;
|
|
5781
|
-
}
|
|
5782
|
-
}
|
|
5783
|
-
}
|
|
5784
|
-
const result = await session.run(
|
|
5785
|
-
`MATCH (up:UserProfile {accountId: $accountId, userId: $userId})
|
|
5786
|
-
OPTIONAL MATCH (au:AdminUser {userId: $userId})-[:OWNS]->(p:Person {accountId: $accountId})
|
|
5787
|
-
OPTIONAL MATCH (up)-[:HAS_PREFERENCE]->(pref:Preference)
|
|
5788
|
-
WHERE pref.confidence >= $threshold OR pref.notApplicable = true
|
|
5789
|
-
WITH up, p, pref
|
|
5790
|
-
ORDER BY pref.confidence DESC
|
|
5791
|
-
WITH up, p, collect(pref) AS allPrefs
|
|
5792
|
-
WITH up, p, allPrefs[0..$limit] AS prefs
|
|
5793
|
-
RETURN up, p AS person, prefs`,
|
|
5794
|
-
// List-range slice bounds are an integer position; a raw JS number
|
|
5795
|
-
// serializes as a Cypher float. Bind a Neo4j integer.
|
|
5796
|
-
{ accountId, userId, threshold: INJECTION_THRESHOLD, limit: neo4j.int(MAX_SUMMARY_PREFERENCES) }
|
|
5797
|
-
);
|
|
5798
|
-
if (result.records.length === 0 || !result.records[0].get("up")) {
|
|
5799
|
-
return null;
|
|
5800
|
-
}
|
|
5801
|
-
const upNode = result.records[0].get("up");
|
|
5802
|
-
const profileProps = { ...upNode.properties };
|
|
5803
|
-
const personNode = result.records[0].get("person");
|
|
5804
|
-
if (personNode?.properties) {
|
|
5805
|
-
if (personNode.properties.givenName !== void 0) profileProps.givenName = personNode.properties.givenName;
|
|
5806
|
-
if (personNode.properties.familyName !== void 0) profileProps.familyName = personNode.properties.familyName;
|
|
5807
|
-
if (personNode.properties.email !== void 0) profileProps.email = personNode.properties.email;
|
|
5808
|
-
if (personNode.properties.telephone !== void 0) profileProps.telephone = personNode.properties.telephone;
|
|
5809
|
-
}
|
|
5810
|
-
const prefNodes = result.records[0].get("prefs");
|
|
5811
|
-
const preferences = prefNodes.map((p) => ({
|
|
5812
|
-
preferenceId: p.properties.preferenceId,
|
|
5813
|
-
category: p.properties.category,
|
|
5814
|
-
key: p.properties.key,
|
|
5815
|
-
value: p.properties.value,
|
|
5816
|
-
confidence: p.properties.confidence,
|
|
5817
|
-
source: p.properties.source,
|
|
5818
|
-
observedAt: p.properties.observedAt,
|
|
5819
|
-
notApplicable: p.properties.notApplicable === true
|
|
5820
|
-
}));
|
|
5821
|
-
const extraTemplates = loadEnabledPluginProfileKeys(accountId);
|
|
5822
|
-
const extraFieldCount = Object.values(extraTemplates).reduce((n, fs) => n + fs.length, 0);
|
|
5823
|
-
const summary = formatProfileSummary(profileProps, preferences, extraTemplates);
|
|
5824
|
-
const absentFields = computeAbsentFields(preferences, extraTemplates);
|
|
5825
|
-
const totalFields = TOTAL_PROFILE_FIELDS + extraFieldCount;
|
|
5826
|
-
const fieldCoverageCount = totalFields - absentFields.length;
|
|
5827
|
-
const presentCategories = new Set(
|
|
5828
|
-
preferences.filter((p) => p.notApplicable === true || p.confidence >= INJECTION_THRESHOLD).map((p) => p.category)
|
|
5829
|
-
);
|
|
5830
|
-
const absentCategories = PROFILE_CATEGORIES.filter((c) => !presentCategories.has(c));
|
|
5831
|
-
const categoryCoverage = PROFILE_CATEGORIES.length - absentCategories.length;
|
|
5832
|
-
console.error(
|
|
5833
|
-
`[profile] loaded for userId=${userId} accountId=${accountId.slice(0, 8)}\u2026 preferences=${preferences.length} fieldCoverage=${fieldCoverageCount}/${totalFields} categoryCoverage=${categoryCoverage}/${PROFILE_CATEGORIES.length} (decay: ${decayCount} updated)`
|
|
5834
|
-
);
|
|
5835
|
-
const identityKeys = [
|
|
5836
|
-
{ prop: "email", token: "email" },
|
|
5837
|
-
// Schema-canonical name. `phone` is the synonym (rejected at tool
|
|
5838
|
-
// surface); the log uses `telephone` to keep grep aligned with
|
|
5839
|
-
// schema-base.md doctrine the brief enforces.
|
|
5840
|
-
{ prop: "telephone", token: "telephone" },
|
|
5841
|
-
{ prop: "givenName", token: "givenName" },
|
|
5842
|
-
{ prop: "familyName", token: "familyName" },
|
|
5843
|
-
{ prop: "role", token: "role" }
|
|
5844
|
-
];
|
|
5845
|
-
const absentIdentity = identityKeys.filter((k) => {
|
|
5846
|
-
const v = profileProps[k.prop];
|
|
5847
|
-
return typeof v !== "string" || v.trim().length === 0;
|
|
5848
|
-
});
|
|
5849
|
-
const sparseFired = absentCategories.length > 0 || absentFields.length > 0 || absentIdentity.length > 0;
|
|
5850
|
-
if (sessionId && sparseFired) {
|
|
5851
|
-
const dedupeKey = `${accountId}:${userId}:${sessionId}`;
|
|
5852
|
-
if (!sparseStateLogged.has(dedupeKey)) {
|
|
5853
|
-
sparseStateLogged.add(dedupeKey);
|
|
5854
|
-
const FIELDS_LOG_CAP = 10;
|
|
5855
|
-
const absentFieldsTruncated = absentFields.length <= FIELDS_LOG_CAP ? absentFields.join(",") : `${absentFields.slice(0, FIELDS_LOG_CAP).join(",")}+${absentFields.length - FIELDS_LOG_CAP} more`;
|
|
5856
|
-
console.error(
|
|
5857
|
-
`[profile] sparse-state injected accountId=${accountId.slice(0, 8)}\u2026 userId=${userId} sessionId=${sessionId.slice(0, 8)}\u2026 absentCategories=${absentCategories.join(",")} absentFields=${absentFieldsTruncated || "none"} absentIdentity=${absentIdentity.map((k) => k.token).join(",") || "none"}`
|
|
5858
|
-
);
|
|
5859
|
-
}
|
|
5860
|
-
}
|
|
5861
|
-
return summary;
|
|
5862
|
-
} catch (err) {
|
|
5863
|
-
console.error(`[profile] loadUserProfile failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
5864
|
-
return null;
|
|
5865
|
-
} finally {
|
|
5866
|
-
await session.close();
|
|
5867
|
-
}
|
|
5868
|
-
}
|
|
5869
|
-
function formatProfileSummary(profile, preferences, extraTemplates = {}) {
|
|
5870
|
-
const summary = composeOperatorSummary(profile, preferences);
|
|
5871
|
-
return [
|
|
5872
|
-
`This is what you know about the operator: ${summary}.`,
|
|
5873
|
-
`You must seek to MAXIMISE what you know about the operator to do your job effectively.`
|
|
5874
|
-
].join("\n");
|
|
5875
|
-
}
|
|
5876
|
-
function composeOperatorSummary(profile, preferences) {
|
|
5877
|
-
const facts = [];
|
|
5878
|
-
const givenName = typeof profile.givenName === "string" ? profile.givenName.trim() : "";
|
|
5879
|
-
const familyName = typeof profile.familyName === "string" ? profile.familyName.trim() : "";
|
|
5880
|
-
const fullName = [givenName, familyName].filter((s) => s.length > 0).join(" ");
|
|
5881
|
-
if (fullName) facts.push(`name ${fullName}`);
|
|
5882
|
-
if (typeof profile.role === "string" && profile.role.trim()) {
|
|
5883
|
-
facts.push(`role ${profile.role.trim()}`);
|
|
5884
|
-
}
|
|
5885
|
-
if (typeof profile.timezone === "string" && profile.timezone.trim()) {
|
|
5886
|
-
facts.push(`timezone ${profile.timezone.trim()}`);
|
|
5887
|
-
}
|
|
5888
|
-
if (typeof profile.locale === "string" && profile.locale.trim()) {
|
|
5889
|
-
facts.push(`locale ${profile.locale.trim()}`);
|
|
5890
|
-
}
|
|
5891
|
-
if (typeof profile.email === "string" && profile.email.trim()) {
|
|
5892
|
-
facts.push(`email ${profile.email.trim()}`);
|
|
5893
|
-
}
|
|
5894
|
-
if (typeof profile.telephone === "string" && profile.telephone.trim()) {
|
|
5895
|
-
facts.push(`telephone ${profile.telephone.trim()}`);
|
|
5896
|
-
}
|
|
5897
|
-
if (profile.expertise) {
|
|
5898
|
-
const expVal = typeof profile.expertise === "string" ? profile.expertise : JSON.stringify(profile.expertise);
|
|
5899
|
-
if (expVal.trim()) facts.push(`expertise ${expVal.trim()}`);
|
|
5900
|
-
}
|
|
5901
|
-
const byCategory = {};
|
|
5902
|
-
for (const pref of preferences) {
|
|
5903
|
-
if (pref.notApplicable === true) continue;
|
|
5904
|
-
if (typeof pref.value !== "string" || pref.value.trim().length === 0) continue;
|
|
5905
|
-
if (!byCategory[pref.category]) byCategory[pref.category] = [];
|
|
5906
|
-
byCategory[pref.category].push(pref.value.trim());
|
|
5907
|
-
}
|
|
5908
|
-
for (const [cat, vals] of Object.entries(byCategory)) {
|
|
5909
|
-
facts.push(`${cat} ${vals.join(", ")}`);
|
|
5910
|
-
}
|
|
5911
|
-
return facts.length > 0 ? facts.join("; ") : "NOTHING";
|
|
5912
|
-
}
|
|
5913
|
-
var RECENT_FAILURES_TAIL_BYTES = 10 * 1024;
|
|
5914
|
-
var AGENT_FILE_ROLES = [
|
|
5915
|
-
{ filename: "IDENTITY.md", role: "identity" },
|
|
5916
|
-
{ filename: "SOUL.md", role: "soul" },
|
|
5917
|
-
{ filename: "KNOWLEDGE.md", role: "knowledge" },
|
|
5918
|
-
{ filename: "KNOWLEDGE-SUMMARY.md", role: "knowledge-summary" }
|
|
5919
|
-
];
|
|
5920
|
-
var ROLE_TO_EDGE = {
|
|
5921
|
-
identity: "HAS_IDENTITY",
|
|
5922
|
-
soul: "HAS_SOUL",
|
|
5923
|
-
knowledge: "HAS_KNOWLEDGE",
|
|
5924
|
-
"knowledge-summary": "HAS_KNOWLEDGE"
|
|
5925
|
-
};
|
|
5926
|
-
function assertSafeAgentSlug(slug) {
|
|
5927
|
-
if (!slug || slug.includes("/") || slug.includes("\\") || slug.includes("..")) {
|
|
5928
|
-
throw new Error(`[agent-graph] refusing unsafe slug=${JSON.stringify(slug)}`);
|
|
5929
|
-
}
|
|
5930
|
-
}
|
|
5931
|
-
function agentAttachmentId(accountId, slug, role) {
|
|
5932
|
-
return `agent:${accountId}:${slug}:${role}`;
|
|
5933
|
-
}
|
|
5934
|
-
async function projectAgent(accountId, accountDir, slug) {
|
|
5935
|
-
const start = Date.now();
|
|
5936
|
-
const account8 = accountId.slice(0, 8);
|
|
5937
|
-
try {
|
|
5938
|
-
assertSafeAgentSlug(slug);
|
|
5939
|
-
} catch (err) {
|
|
5940
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5941
|
-
console.error(
|
|
5942
|
-
`[agent-graph] project FAILED slug=${slug} account=${account8} error="${msg}"`
|
|
5943
|
-
);
|
|
5944
|
-
return;
|
|
5945
|
-
}
|
|
5946
|
-
const agentDir = resolve6(accountDir, "agents", slug);
|
|
5947
|
-
const configPath = resolve6(agentDir, "config.json");
|
|
5948
|
-
let config;
|
|
5949
|
-
let presentRoles = [];
|
|
5950
|
-
try {
|
|
5951
|
-
if (!existsSync7(configPath)) {
|
|
5952
|
-
console.error(
|
|
5953
|
-
`[agent-graph] project FAILED slug=${slug} account=${account8} error="config.json missing at ${configPath}"`
|
|
5954
|
-
);
|
|
5955
|
-
return;
|
|
5956
|
-
}
|
|
5957
|
-
config = JSON.parse(readFileSync9(configPath, "utf-8"));
|
|
5958
|
-
for (const { filename, role } of AGENT_FILE_ROLES) {
|
|
5959
|
-
const filePath = resolve6(agentDir, filename);
|
|
5960
|
-
if (!existsSync7(filePath)) continue;
|
|
5961
|
-
const body = readFileSync9(filePath, "utf-8");
|
|
5962
|
-
presentRoles.push({ role, body, edge: ROLE_TO_EDGE[role] });
|
|
5963
|
-
}
|
|
5964
|
-
} catch (err) {
|
|
5965
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
5966
|
-
console.error(
|
|
5967
|
-
`[agent-graph] project FAILED slug=${slug} account=${account8} error="${msg}"`
|
|
5968
|
-
);
|
|
5969
|
-
return;
|
|
5970
|
-
}
|
|
5971
|
-
const session = getSession();
|
|
5972
|
-
try {
|
|
5973
|
-
await session.run(
|
|
5974
|
-
`MERGE (a:Agent {accountId: $accountId, slug: $slug})
|
|
5975
|
-
ON CREATE SET a.createdAt = datetime()
|
|
5976
|
-
SET a.displayName = $displayName,
|
|
5977
|
-
a.status = $status,
|
|
5978
|
-
a.model = $model,
|
|
5979
|
-
a.knowledgeKeywords = $knowledgeKeywords,
|
|
5980
|
-
a.role = 'agent',
|
|
5981
|
-
a.updatedAt = datetime()
|
|
5982
|
-
RETURN a.slug AS slug`,
|
|
5983
|
-
{
|
|
5984
|
-
accountId,
|
|
5985
|
-
slug,
|
|
5986
|
-
displayName: config.displayName ?? slug,
|
|
5987
|
-
status: config.status ?? "unknown",
|
|
5988
|
-
model: config.model ?? "",
|
|
5989
|
-
knowledgeKeywords: config.knowledgeKeywords ?? []
|
|
5990
|
-
}
|
|
5991
|
-
);
|
|
5992
|
-
for (const { role, body, edge } of presentRoles) {
|
|
5993
|
-
const attachmentId = agentAttachmentId(accountId, slug, role);
|
|
5994
|
-
await session.run(
|
|
5995
|
-
`MATCH (a:Agent {accountId: $accountId, slug: $slug})
|
|
5996
|
-
MERGE (k:KnowledgeDocument {attachmentId: $attachmentId})
|
|
5997
|
-
ON CREATE SET k.createdAt = datetime()
|
|
5998
|
-
SET k.accountId = $accountId,
|
|
5999
|
-
k.role = $role,
|
|
6000
|
-
k.name = $name,
|
|
6001
|
-
k.text = $text,
|
|
6002
|
-
k.scope = 'admin',
|
|
6003
|
-
k.updatedAt = datetime()
|
|
6004
|
-
MERGE (a)-[:${edge}]->(k)
|
|
6005
|
-
RETURN k.attachmentId AS attachmentId`,
|
|
6006
|
-
{
|
|
6007
|
-
accountId,
|
|
6008
|
-
slug,
|
|
6009
|
-
attachmentId,
|
|
6010
|
-
role,
|
|
6011
|
-
name: `${slug} ${role}`,
|
|
6012
|
-
text: body
|
|
6013
|
-
}
|
|
6014
|
-
);
|
|
6015
|
-
}
|
|
6016
|
-
const usesResult = await session.run(
|
|
6017
|
-
`MATCH (a:Agent {accountId: $accountId, slug: $slug})
|
|
6018
|
-
MATCH (k:KnowledgeDocument {accountId: $accountId})
|
|
6019
|
-
WHERE $slug IN coalesce(k.agents, [])
|
|
6020
|
-
AND NOT k.attachmentId STARTS WITH $namespacePrefix
|
|
6021
|
-
MERGE (a)-[:USES_KNOWLEDGE]->(k)
|
|
6022
|
-
RETURN count(k) AS uses`,
|
|
6023
|
-
{
|
|
6024
|
-
accountId,
|
|
6025
|
-
slug,
|
|
6026
|
-
namespacePrefix: `agent:${accountId}:${slug}:`
|
|
6027
|
-
}
|
|
6028
|
-
);
|
|
6029
|
-
const uses = usesResult.records[0]?.get("uses");
|
|
6030
|
-
const usesCount = typeof uses === "number" ? uses : uses && typeof uses.toNumber === "function" ? uses.toNumber() : 0;
|
|
6031
|
-
const ms = Date.now() - start;
|
|
6032
|
-
console.error(
|
|
6033
|
-
`[agent-graph] project slug=${slug} account=${account8} docs=${presentRoles.length} uses=${usesCount} ms=${ms}`
|
|
6034
|
-
);
|
|
6035
|
-
} catch (err) {
|
|
6036
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6037
|
-
console.error(
|
|
6038
|
-
`[agent-graph] project FAILED slug=${slug} account=${account8} error="${msg}"`
|
|
6039
|
-
);
|
|
6040
|
-
throw err;
|
|
6041
|
-
} finally {
|
|
6042
|
-
await session.close();
|
|
6043
|
-
}
|
|
6044
|
-
}
|
|
6045
|
-
async function deleteAgentProjection(accountId, slug) {
|
|
6046
|
-
const start = Date.now();
|
|
6047
|
-
const account8 = accountId.slice(0, 8);
|
|
6048
|
-
assertSafeAgentSlug(slug);
|
|
6049
|
-
const session = getSession();
|
|
6050
|
-
try {
|
|
6051
|
-
await session.run(
|
|
6052
|
-
`MATCH (a:Agent {accountId: $accountId, slug: $slug})
|
|
6053
|
-
DETACH DELETE a
|
|
6054
|
-
WITH 1 AS _
|
|
6055
|
-
UNWIND $attachmentIds AS aid
|
|
6056
|
-
OPTIONAL MATCH (k:KnowledgeDocument {accountId: $accountId, attachmentId: aid})
|
|
6057
|
-
WHERE k.attachmentId STARTS WITH $namespacePrefix
|
|
6058
|
-
DETACH DELETE k`,
|
|
6059
|
-
{
|
|
6060
|
-
accountId,
|
|
6061
|
-
slug,
|
|
6062
|
-
namespacePrefix: `agent:${accountId}:${slug}:`,
|
|
6063
|
-
attachmentIds: ["identity", "soul", "knowledge", "knowledge-summary"].map((role) => agentAttachmentId(accountId, slug, role))
|
|
6064
|
-
}
|
|
6065
|
-
);
|
|
6066
|
-
const ms = Date.now() - start;
|
|
6067
|
-
console.error(
|
|
6068
|
-
`[agent-graph] delete slug=${slug} account=${account8} ms=${ms}`
|
|
6069
|
-
);
|
|
6070
|
-
} catch (err) {
|
|
6071
|
-
const msg = err instanceof Error ? err.message : String(err);
|
|
6072
|
-
console.error(
|
|
6073
|
-
`[agent-graph] delete FAILED slug=${slug} account=${account8} error="${msg}"`
|
|
6074
|
-
);
|
|
6075
|
-
throw err;
|
|
6076
|
-
} finally {
|
|
6077
|
-
await session.close();
|
|
6078
|
-
}
|
|
6079
|
-
}
|
|
6080
|
-
|
|
6081
4179
|
// server/routes/_helpers.ts
|
|
6082
4180
|
async function safeJson(c) {
|
|
6083
4181
|
try {
|
|
@@ -6207,34 +4305,6 @@ export {
|
|
|
6207
4305
|
Hono2 as Hono,
|
|
6208
4306
|
getRequestListener,
|
|
6209
4307
|
serve,
|
|
6210
|
-
MAXY_DIR,
|
|
6211
|
-
BRAND_NAME,
|
|
6212
|
-
COMMERCIAL_MODE,
|
|
6213
|
-
VNC_DISPLAY,
|
|
6214
|
-
RFB_PORT,
|
|
6215
|
-
WEBSOCKIFY_PORT,
|
|
6216
|
-
CDP_PORT,
|
|
6217
|
-
CHROMIUM_PROFILE_DIR,
|
|
6218
|
-
USERS_FILE,
|
|
6219
|
-
LID_MAP_FILE,
|
|
6220
|
-
LOG_DIR,
|
|
6221
|
-
USERS_AUDIT_LOG,
|
|
6222
|
-
BIN_DIR,
|
|
6223
|
-
REMOTE_PASSWORD_FILE,
|
|
6224
|
-
INSTALL_OWNER_FILE,
|
|
6225
|
-
VISITOR_TOKEN_SECRET_FILE,
|
|
6226
|
-
CLAUDE_CREDENTIALS_FILE,
|
|
6227
|
-
SYSTEM_PATH_SLUGS,
|
|
6228
|
-
PLATFORM_ROOT2 as PLATFORM_ROOT,
|
|
6229
|
-
ACCOUNTS_DIR,
|
|
6230
|
-
listValidAccounts,
|
|
6231
|
-
isValidAccountId,
|
|
6232
|
-
resolveAccount,
|
|
6233
|
-
validateAgentSlug,
|
|
6234
|
-
resolveDefaultAgentSlug,
|
|
6235
|
-
resolveAgentConfig,
|
|
6236
|
-
isActiveAgentSlug,
|
|
6237
|
-
getDefaultAccountId,
|
|
6238
4308
|
requirePortEnv,
|
|
6239
4309
|
fingerprintSessionKey,
|
|
6240
4310
|
registerSession,
|
|
@@ -6290,35 +4360,6 @@ export {
|
|
|
6290
4360
|
cleanupLeakedPremiumSubs,
|
|
6291
4361
|
emitMissingOnResolve,
|
|
6292
4362
|
stripAttachmentMetaSuffix,
|
|
6293
|
-
getSession,
|
|
6294
|
-
runAdminUserSelfHeal,
|
|
6295
|
-
runUserProfileReconcile,
|
|
6296
|
-
embed,
|
|
6297
|
-
preloadEmbedModel,
|
|
6298
|
-
GREETING_DIRECTIVE,
|
|
6299
|
-
ensureConversation,
|
|
6300
|
-
ensureSessionConversation,
|
|
6301
|
-
findRecentConversation,
|
|
6302
|
-
createNewAdminConversation,
|
|
6303
|
-
fetchBranding,
|
|
6304
|
-
fetchAccountName,
|
|
6305
|
-
persistMessage,
|
|
6306
|
-
getAgentSessionIdForConversation,
|
|
6307
|
-
getRecentMessages,
|
|
6308
|
-
verifyConversationOwnership,
|
|
6309
|
-
verifyAndGetConversationUpdatedAt,
|
|
6310
|
-
listAdminSessions,
|
|
6311
|
-
deleteConversation,
|
|
6312
|
-
linkConversationTranscript,
|
|
6313
|
-
getConversationClaudeSessionIds,
|
|
6314
|
-
renameConversation,
|
|
6315
|
-
getUserTimezone,
|
|
6316
|
-
loadAdminUserName,
|
|
6317
|
-
loadPersonNameByElementId,
|
|
6318
|
-
writeAdminUserAndPerson,
|
|
6319
|
-
loadUserProfile,
|
|
6320
|
-
projectAgent,
|
|
6321
|
-
deleteAgentProjection,
|
|
6322
4363
|
safeJson,
|
|
6323
4364
|
resolveCacheKey,
|
|
6324
4365
|
requireAdminSession,
|