@rubytech/create-maxy-code 0.1.505 → 0.1.507
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/payload/platform/config/brand.json +79 -18
- package/payload/platform/lib/account-schema-regions/dist/index.d.ts +72 -0
- package/payload/platform/lib/account-schema-regions/dist/index.d.ts.map +1 -0
- package/payload/platform/lib/account-schema-regions/dist/index.js +115 -0
- package/payload/platform/lib/account-schema-regions/dist/index.js.map +1 -0
- package/payload/platform/lib/account-schema-regions/package.json +7 -0
- package/payload/platform/lib/account-schema-regions/src/index.ts +135 -0
- package/payload/platform/lib/account-schema-regions/tsconfig.json +9 -0
- package/payload/platform/package.json +3 -3
- package/payload/platform/plugins/admin/PLUGIN.md +36 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +105 -17
- package/payload/platform/plugins/admin/skills/public-agent-manager/SKILL.md +3 -3
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +13 -0
- package/payload/platform/plugins/aeo/PLUGIN.md +3 -0
- package/payload/platform/plugins/browser/PLUGIN.md +18 -0
- package/payload/platform/plugins/connector/PLUGIN.md +4 -0
- package/payload/platform/plugins/contacts/PLUGIN.md +9 -0
- package/payload/platform/plugins/docs/references/admin-ui.md +78 -16
- package/payload/platform/plugins/docs/references/plugins-guide.md +26 -0
- package/payload/platform/plugins/email/PLUGIN.md +17 -0
- package/payload/platform/plugins/filesystem/PLUGIN.md +6 -0
- package/payload/platform/plugins/google/PLUGIN.md +12 -0
- package/payload/platform/plugins/graph/PLUGIN.md +3 -0
- package/payload/platform/plugins/graph-viewer/PLUGIN.md +1 -0
- package/payload/platform/plugins/ledger/PLUGIN.md +7 -0
- package/payload/platform/plugins/memory/PLUGIN.md +41 -0
- package/payload/platform/plugins/outlook/PLUGIN.md +24 -0
- package/payload/platform/plugins/quickbooks/PLUGIN.md +15 -0
- package/payload/platform/plugins/replicate/PLUGIN.md +3 -0
- package/payload/platform/plugins/scheduling/PLUGIN.md +10 -0
- package/payload/platform/plugins/storage-broker/PLUGIN.md +12 -0
- package/payload/platform/plugins/telegram/PLUGIN.md +3 -0
- package/payload/platform/plugins/url-get/PLUGIN.md +1 -0
- package/payload/platform/plugins/voice-mirror/PLUGIN.md +5 -0
- package/payload/platform/plugins/whatsapp/PLUGIN.md +12 -0
- package/payload/platform/plugins/work/PLUGIN.md +15 -0
- package/payload/platform/plugins/workflows/PLUGIN.md +8 -0
- package/payload/platform/scripts/check-no-esm-require.mjs +5 -0
- package/payload/platform/scripts/check-risk-class.mjs +145 -0
- package/payload/platform/scripts/lib/__pycache__/account-schema-owned-dirs.cpython-314.pyc +0 -0
- package/payload/platform/scripts/lib/provision-account-dir.sh +4 -1
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts +1 -1
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js +50 -3
- package/payload/platform/services/claude-session-manager/dist/account-dir-schema-reconcile.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +4 -0
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +8 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts +13 -0
- package/payload/platform/services/claude-session-manager/dist/tool-surface.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/tool-surface.js +33 -8
- package/payload/platform/services/claude-session-manager/dist/tool-surface.js.map +1 -1
- package/payload/server/{chunk-HN4HFTXE.js → chunk-2WAXM5N2.js} +1504 -1503
- package/payload/server/maxy-edge.js +1 -1
- package/payload/server/public/activity.html +5 -5
- package/payload/server/public/agents.html +4 -4
- package/payload/server/public/assets/{AdminLoginScreens-DeWt3H7l.js → AdminLoginScreens-YT7jsh_H.js} +1 -1
- package/payload/server/public/assets/{AdminShell-u32d8puE.js → AdminShell-Ckht9hmU.js} +2 -2
- package/payload/server/public/assets/{activity-DjP45-DD.js → activity-DH3Wgc3O.js} +1 -1
- package/payload/server/public/assets/admin-DetvulAu.js +1 -0
- package/payload/server/public/assets/agents-X_IvDHLZ.js +1 -0
- package/payload/server/public/assets/{browser-ULvl_K5x.js → browser-Bym8QhfA.js} +1 -1
- package/payload/server/public/assets/{calendar-CGMrT6WZ.js → calendar-CFnufJOy.js} +1 -1
- package/payload/server/public/assets/chat-wrckyIpw.js +1 -0
- package/payload/server/public/assets/chevron-left-Dtk3jEK5.js +1 -0
- package/payload/server/public/assets/chevron-right-Dih6IZ8B.js +1 -0
- package/payload/server/public/assets/clock-iJCrrmWM.js +1 -0
- package/payload/server/public/assets/data-D76--TYs.js +1 -0
- package/payload/server/public/assets/{file-text-C5K6DMcK.js → file-text-zch0E1GJ.js} +1 -1
- package/payload/server/public/assets/{graph-CO6e1bgD.js → graph-BavHLzpV.js} +2 -2
- package/payload/server/public/assets/{graph-labels-iliQjlfE.js → graph-labels-CBwh4g-6.js} +1 -1
- package/payload/server/public/assets/{maximize-2-D-64XK6V.js → maximize-2-bTn_Ozpi.js} +1 -1
- package/payload/server/public/assets/{operator-DyXgogJ1.js → operator-a4IOwf62.js} +1 -1
- package/payload/server/public/assets/page-3T2mE63b.js +1 -0
- package/payload/server/public/assets/page-D6M1CmPe.js +32 -0
- package/payload/server/public/assets/{public-B9NVDBxW.js → public-B3y_YU1G.js} +1 -1
- package/payload/server/public/assets/{rotate-ccw-P35h7ogS.js → rotate-ccw-BGvzNB1b.js} +1 -1
- package/payload/server/public/assets/{routines-BeiynLF7.js → routines-C7Dr16am.js} +1 -1
- package/payload/server/public/assets/{skills-C5H7ohLp.js → skills-DFeb3XO3.js} +1 -1
- package/payload/server/public/assets/tasks-CSfnCKJe.js +1 -0
- package/payload/server/public/assets/{time-entry-format-CDhHCMrW.js → time-entry-format-BgZqOOqG.js} +1 -1
- package/payload/server/public/assets/{triangle-alert-OgY71rDV.js → triangle-alert-DBJ7xz_t.js} +1 -1
- package/payload/server/public/assets/{useCopyFeedback-jOGmWwa3.js → useCopyFeedback-BFOujMl-.js} +1 -1
- package/payload/server/public/assets/{useSubAccountSwitcher-6AFuRawP.js → useSubAccountSwitcher-0PfRrN6i.js} +1 -1
- package/payload/server/public/assets/useSubAccountSwitcher-BkQ5tA8A.css +1 -0
- package/payload/server/public/assets/{useVoiceRecorder-CnE3fhfV.js → useVoiceRecorder-BP6CpYQD.js} +1 -1
- package/payload/server/public/assets/{wrench-B1hsCj0R.js → wrench-DdaRhfzS.js} +1 -1
- package/payload/server/public/brand/fonts/inter-400.woff2 +0 -0
- package/payload/server/public/brand/fonts/inter-500.woff2 +0 -0
- package/payload/server/public/brand-defaults.css +47 -4
- 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 +1991 -1372
- package/payload/server/public/assets/admin-Bo-Y6Vjx.js +0 -1
- package/payload/server/public/assets/agents-C80aCXd8.js +0 -1
- package/payload/server/public/assets/chat-DnWXaWD4.js +0 -1
- package/payload/server/public/assets/chevron-left-BB5Iljw-.js +0 -1
- package/payload/server/public/assets/chevron-right-DijU96_j.js +0 -1
- package/payload/server/public/assets/clock-DsmnhhJh.js +0 -1
- package/payload/server/public/assets/data-JPlhOLGC.js +0 -1
- package/payload/server/public/assets/page-DrbiAFS-.js +0 -32
- package/payload/server/public/assets/page-WeM2ry61.js +0 -1
- package/payload/server/public/assets/tasks-CI74_b6H.js +0 -1
- package/payload/server/public/assets/useSubAccountSwitcher-AFSwtthL.css +0 -1
|
@@ -2657,234 +2657,10 @@ var serve = (options, listeningListener) => {
|
|
|
2657
2657
|
return server;
|
|
2658
2658
|
};
|
|
2659
2659
|
|
|
2660
|
-
// server/loop-registry.ts
|
|
2661
|
-
import { readFileSync } from "fs";
|
|
2662
|
-
var TAG = "[loop-registry]";
|
|
2663
|
-
var STALL_INTERVALS = 3;
|
|
2664
|
-
var entries = /* @__PURE__ */ new Map();
|
|
2665
|
-
var defaultCpuIo = {
|
|
2666
|
-
ownCpuUs: () => {
|
|
2667
|
-
const u = process.cpuUsage();
|
|
2668
|
-
return u.user + u.system;
|
|
2669
|
-
},
|
|
2670
|
-
childCpuUs: (pid) => readPidCpuUs(pid)
|
|
2671
|
-
};
|
|
2672
|
-
var cpuIo = defaultCpuIo;
|
|
2673
|
-
function parsePidStatCpuUs(raw2) {
|
|
2674
|
-
const close = raw2.lastIndexOf(")");
|
|
2675
|
-
if (close === -1) return null;
|
|
2676
|
-
const after = raw2.slice(close + 2).split(" ");
|
|
2677
|
-
const utime = Number(after[11]);
|
|
2678
|
-
const stime = Number(after[12]);
|
|
2679
|
-
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
2680
|
-
return (utime + stime) / 100 * 1e6;
|
|
2681
|
-
}
|
|
2682
|
-
function readPidCpuUs(pid) {
|
|
2683
|
-
if (process.platform !== "linux") return null;
|
|
2684
|
-
try {
|
|
2685
|
-
return parsePidStatCpuUs(readFileSync(`/proc/${pid}/stat`, "utf8"));
|
|
2686
|
-
} catch {
|
|
2687
|
-
return null;
|
|
2688
|
-
}
|
|
2689
|
-
}
|
|
2690
|
-
function sampleCpu(nowMs = Date.now()) {
|
|
2691
|
-
for (const e of entries.values()) {
|
|
2692
|
-
if (!e.cpuAttributable) {
|
|
2693
|
-
e.cpuPct = null;
|
|
2694
|
-
continue;
|
|
2695
|
-
}
|
|
2696
|
-
let childUs = 0;
|
|
2697
|
-
for (const pid of [...e.children]) {
|
|
2698
|
-
const us = cpuIo.childCpuUs(pid);
|
|
2699
|
-
if (us === null) {
|
|
2700
|
-
e.children.delete(pid);
|
|
2701
|
-
continue;
|
|
2702
|
-
}
|
|
2703
|
-
childUs += us;
|
|
2704
|
-
}
|
|
2705
|
-
e.lastChildCpuUs = Math.max(e.lastChildCpuUs, childUs);
|
|
2706
|
-
const totalUs = e.cumulativeCpuUs + e.lastChildCpuUs;
|
|
2707
|
-
const p = e.prior;
|
|
2708
|
-
if (p) {
|
|
2709
|
-
const wallUs = (nowMs - p.atMs) * 1e3;
|
|
2710
|
-
e.cpuPct = wallUs > 0 && totalUs >= p.cpuUs ? (totalUs - p.cpuUs) / wallUs * 100 : null;
|
|
2711
|
-
} else {
|
|
2712
|
-
e.cpuPct = null;
|
|
2713
|
-
}
|
|
2714
|
-
e.prior = { cpuUs: totalUs, atMs: nowMs };
|
|
2715
|
-
}
|
|
2716
|
-
}
|
|
2717
|
-
var processPrior = null;
|
|
2718
|
-
function sampleProcessCpu(nowMs = Date.now()) {
|
|
2719
|
-
const cpuUs = cpuIo.ownCpuUs();
|
|
2720
|
-
let processPct = null;
|
|
2721
|
-
if (processPrior) {
|
|
2722
|
-
const wallUs = (nowMs - processPrior.atMs) * 1e3;
|
|
2723
|
-
if (wallUs > 0) processPct = (cpuUs - processPrior.cpuUs) / wallUs * 100;
|
|
2724
|
-
}
|
|
2725
|
-
processPrior = { cpuUs, atMs: nowMs };
|
|
2726
|
-
const rows = [...entries.values()];
|
|
2727
|
-
const measured = rows.filter((e) => e.cpuPct !== null);
|
|
2728
|
-
const attributedPct = measured.length === 0 ? null : measured.reduce((sum, e) => sum + (e.cpuPct ?? 0), 0);
|
|
2729
|
-
const unattributedPct = processPct === null || attributedPct === null ? null : Math.max(0, processPct - attributedPct);
|
|
2730
|
-
return { processPct, attributedPct, unattributedPct };
|
|
2731
|
-
}
|
|
2732
|
-
function messageOf(err) {
|
|
2733
|
-
return err instanceof Error ? err.message : String(err);
|
|
2734
|
-
}
|
|
2735
|
-
function finish(e, startedAt, cpuStart, cpuAttributable, note, err) {
|
|
2736
|
-
const cpu = process.cpuUsage(cpuStart);
|
|
2737
|
-
e.running = false;
|
|
2738
|
-
e.lastDurationMs = Date.now() - startedAt;
|
|
2739
|
-
e.lastCpuMs = cpuAttributable ? (cpu.user + cpu.system) / 1e3 : null;
|
|
2740
|
-
if (err !== null) {
|
|
2741
|
-
e.lastOutcome = "error";
|
|
2742
|
-
e.lastError = messageOf(err);
|
|
2743
|
-
e.lastNote = null;
|
|
2744
|
-
e.consecutiveFailures += 1;
|
|
2745
|
-
console.error(
|
|
2746
|
-
`${TAG} op=run name=${e.spec.name} outcome=error streak=${e.consecutiveFailures} ms=${e.lastDurationMs} err="${e.lastError}"`
|
|
2747
|
-
);
|
|
2748
|
-
return;
|
|
2749
|
-
}
|
|
2750
|
-
e.lastOutcome = "ok";
|
|
2751
|
-
e.lastError = null;
|
|
2752
|
-
e.lastNote = typeof note === "string" && note.length > 0 ? note : null;
|
|
2753
|
-
e.lastSuccessMs = Date.now();
|
|
2754
|
-
e.successCount += 1;
|
|
2755
|
-
e.consecutiveFailures = 0;
|
|
2756
|
-
}
|
|
2757
|
-
function invoke(e) {
|
|
2758
|
-
if (e.running) {
|
|
2759
|
-
console.warn(`${TAG} op=skip name=${e.spec.name} reason=still-running`);
|
|
2760
|
-
return;
|
|
2761
|
-
}
|
|
2762
|
-
e.running = true;
|
|
2763
|
-
e.runCount += 1;
|
|
2764
|
-
e.lastStartMs = Date.now();
|
|
2765
|
-
const startedAt = e.lastStartMs;
|
|
2766
|
-
const cpuStart = process.cpuUsage();
|
|
2767
|
-
const ownStartUs = cpuIo.ownCpuUs();
|
|
2768
|
-
const ctx = {
|
|
2769
|
-
child: (pid) => {
|
|
2770
|
-
if (typeof pid === "number" && Number.isFinite(pid)) e.children.add(pid);
|
|
2771
|
-
}
|
|
2772
|
-
};
|
|
2773
|
-
let result;
|
|
2774
|
-
try {
|
|
2775
|
-
result = e.spec.run(ctx);
|
|
2776
|
-
} catch (err) {
|
|
2777
|
-
e.cumulativeCpuUs += Math.max(0, cpuIo.ownCpuUs() - ownStartUs);
|
|
2778
|
-
finish(e, startedAt, cpuStart, true, null, err);
|
|
2779
|
-
return;
|
|
2780
|
-
}
|
|
2781
|
-
if (result instanceof Promise) {
|
|
2782
|
-
e.cpuAttributable = false;
|
|
2783
|
-
result.then(
|
|
2784
|
-
(note) => finish(e, startedAt, cpuStart, false, note, null),
|
|
2785
|
-
(err) => finish(e, startedAt, cpuStart, false, null, err)
|
|
2786
|
-
);
|
|
2787
|
-
return;
|
|
2788
|
-
}
|
|
2789
|
-
e.cumulativeCpuUs += Math.max(0, cpuIo.ownCpuUs() - ownStartUs);
|
|
2790
|
-
finish(e, startedAt, cpuStart, true, result, null);
|
|
2791
|
-
}
|
|
2792
|
-
function registerLoop(spec) {
|
|
2793
|
-
if (entries.has(spec.name)) {
|
|
2794
|
-
throw new Error(`${TAG} loop "${spec.name}" is already registered`);
|
|
2795
|
-
}
|
|
2796
|
-
const e = {
|
|
2797
|
-
spec,
|
|
2798
|
-
armedAtMs: Date.now(),
|
|
2799
|
-
lastStartMs: null,
|
|
2800
|
-
lastSuccessMs: null,
|
|
2801
|
-
lastDurationMs: null,
|
|
2802
|
-
lastCpuMs: null,
|
|
2803
|
-
lastOutcome: null,
|
|
2804
|
-
lastError: null,
|
|
2805
|
-
lastNote: null,
|
|
2806
|
-
runCount: 0,
|
|
2807
|
-
successCount: 0,
|
|
2808
|
-
consecutiveFailures: 0,
|
|
2809
|
-
running: false,
|
|
2810
|
-
timers: [],
|
|
2811
|
-
cumulativeCpuUs: 0,
|
|
2812
|
-
cpuAttributable: true,
|
|
2813
|
-
children: /* @__PURE__ */ new Set(),
|
|
2814
|
-
lastChildCpuUs: 0,
|
|
2815
|
-
prior: null,
|
|
2816
|
-
cpuPct: null
|
|
2817
|
-
};
|
|
2818
|
-
entries.set(spec.name, e);
|
|
2819
|
-
const interval = setInterval(() => invoke(e), spec.intervalMs);
|
|
2820
|
-
if (typeof interval.unref === "function") interval.unref();
|
|
2821
|
-
e.timers.push(interval);
|
|
2822
|
-
console.log(
|
|
2823
|
-
`${TAG} op=arm name=${spec.name} intervalMs=${spec.intervalMs} firstRunMs=${spec.firstRunDelayMs ?? "interval"} detached=${spec.detached === true} armed=${entries.size}`
|
|
2824
|
-
);
|
|
2825
|
-
if (spec.firstRunDelayMs === 0) {
|
|
2826
|
-
invoke(e);
|
|
2827
|
-
} else if (spec.firstRunDelayMs !== void 0) {
|
|
2828
|
-
const first = setTimeout(() => invoke(e), spec.firstRunDelayMs);
|
|
2829
|
-
if (typeof first.unref === "function") first.unref();
|
|
2830
|
-
e.timers.push(first);
|
|
2831
|
-
}
|
|
2832
|
-
let stopped = false;
|
|
2833
|
-
return () => {
|
|
2834
|
-
if (stopped) return;
|
|
2835
|
-
stopped = true;
|
|
2836
|
-
for (const t of e.timers) clearInterval(t);
|
|
2837
|
-
if (entries.get(spec.name) === e) entries.delete(spec.name);
|
|
2838
|
-
console.log(`${TAG} op=disarm name=${spec.name} armed=${entries.size}`);
|
|
2839
|
-
};
|
|
2840
|
-
}
|
|
2841
|
-
function stateOf(e, nowMs) {
|
|
2842
|
-
if (e.lastStartMs === null) {
|
|
2843
|
-
return nowMs - e.armedAtMs < e.spec.intervalMs ? "waiting" : "never-run";
|
|
2844
|
-
}
|
|
2845
|
-
const overdue = nowMs - e.lastStartMs > e.spec.intervalMs * STALL_INTERVALS;
|
|
2846
|
-
if (overdue) return "stalled";
|
|
2847
|
-
if (e.running) return "running";
|
|
2848
|
-
if (e.successCount === 0) return "never-succeeded";
|
|
2849
|
-
if (e.consecutiveFailures > 0) return "failing";
|
|
2850
|
-
return "ok";
|
|
2851
|
-
}
|
|
2852
|
-
function nextDueOf(e, nowMs) {
|
|
2853
|
-
const elapsed = Math.max(0, nowMs - e.armedAtMs);
|
|
2854
|
-
const ticks = Math.floor(elapsed / e.spec.intervalMs) + 1;
|
|
2855
|
-
return e.armedAtMs + ticks * e.spec.intervalMs;
|
|
2856
|
-
}
|
|
2857
|
-
function listLoops(nowMs = Date.now()) {
|
|
2858
|
-
return [...entries.values()].map((e) => ({
|
|
2859
|
-
name: e.spec.name,
|
|
2860
|
-
intervalMs: e.spec.intervalMs,
|
|
2861
|
-
detached: e.spec.detached === true,
|
|
2862
|
-
armedAtMs: e.armedAtMs,
|
|
2863
|
-
lastStartMs: e.lastStartMs,
|
|
2864
|
-
lastSuccessMs: e.lastSuccessMs,
|
|
2865
|
-
lastDurationMs: e.lastDurationMs,
|
|
2866
|
-
lastCpuMs: e.lastCpuMs,
|
|
2867
|
-
cpuPct: e.cpuPct,
|
|
2868
|
-
cpuAttributable: e.cpuAttributable,
|
|
2869
|
-
lastOutcome: e.lastOutcome,
|
|
2870
|
-
lastError: e.lastError,
|
|
2871
|
-
lastNote: e.lastNote,
|
|
2872
|
-
runCount: e.runCount,
|
|
2873
|
-
successCount: e.successCount,
|
|
2874
|
-
consecutiveFailures: e.consecutiveFailures,
|
|
2875
|
-
running: e.running,
|
|
2876
|
-
state: stateOf(e, nowMs),
|
|
2877
|
-
nextDueMs: nextDueOf(e, nowMs)
|
|
2878
|
-
}));
|
|
2879
|
-
}
|
|
2880
|
-
function armedCount() {
|
|
2881
|
-
return entries.size;
|
|
2882
|
-
}
|
|
2883
|
-
|
|
2884
2660
|
// app/lib/paths.ts
|
|
2885
2661
|
import { homedir } from "os";
|
|
2886
2662
|
import { resolve, join } from "path";
|
|
2887
|
-
import { existsSync, readFileSync
|
|
2663
|
+
import { existsSync, readFileSync } from "fs";
|
|
2888
2664
|
var configDirName = ".maxy";
|
|
2889
2665
|
var commercialMode = false;
|
|
2890
2666
|
var bundleMode = "none";
|
|
@@ -2900,7 +2676,7 @@ if (platformRoot) {
|
|
|
2900
2676
|
if (existsSync(brandPath)) {
|
|
2901
2677
|
let brand;
|
|
2902
2678
|
try {
|
|
2903
|
-
brand = JSON.parse(
|
|
2679
|
+
brand = JSON.parse(readFileSync(brandPath, "utf-8"));
|
|
2904
2680
|
} catch (err) {
|
|
2905
2681
|
const detail = (err instanceof Error ? err.message : String(err)).slice(0, 160);
|
|
2906
2682
|
console.error(`[paths] error reason=brand-config-missing path=${brandPath} detail="parse failed: ${detail.replace(/"/g, "'")}"`);
|
|
@@ -2970,1342 +2746,1597 @@ var TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE = resolve(MAXY_DIR, ".telegram-admin-webh
|
|
|
2970
2746
|
var VISITOR_TOKEN_SECRET_FILE = resolve(MAXY_DIR, "credentials", "visitor-token-secret");
|
|
2971
2747
|
var CLAUDE_CREDENTIALS_FILE = resolve(MAXY_DIR, ".claude", ".credentials.json");
|
|
2972
2748
|
|
|
2973
|
-
// app/lib/
|
|
2974
|
-
|
|
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
|
|
2975
2753
|
import { resolve as resolve2 } from "path";
|
|
2976
|
-
|
|
2977
|
-
|
|
2978
|
-
|
|
2979
|
-
}
|
|
2980
|
-
|
|
2981
|
-
}
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
const
|
|
2985
|
-
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
appendFileSync(VNC_LOG_FILE, line);
|
|
2990
|
-
} catch (err) {
|
|
2991
|
-
console.error(`[vnc-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
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
|
+
);
|
|
2992
2767
|
}
|
|
2768
|
+
return join2(platformRoot2, "config", "brand.json");
|
|
2993
2769
|
}
|
|
2994
|
-
|
|
2995
|
-
|
|
2996
|
-
|
|
2997
|
-
|
|
2998
|
-
|
|
2999
|
-
|
|
3000
|
-
|
|
3001
|
-
function sanitizeClientCorrId(raw2) {
|
|
3002
|
-
if (!raw2) return null;
|
|
3003
|
-
if (raw2.length > 32) return null;
|
|
3004
|
-
if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
|
|
3005
|
-
return raw2;
|
|
3006
|
-
}
|
|
3007
|
-
function formatKv(fields) {
|
|
3008
|
-
return Object.entries(fields).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3009
|
-
}
|
|
3010
|
-
function httpLog(fields) {
|
|
3011
|
-
console.error(`[http] ${formatKv(fields)}`);
|
|
3012
|
-
}
|
|
3013
|
-
function websockifyLog(event, fields = {}) {
|
|
3014
|
-
console.error(`[websockify] ${formatKv({ event, ...fields })}`);
|
|
3015
|
-
}
|
|
3016
|
-
|
|
3017
|
-
// ../lib/admin-access-password/src/index.ts
|
|
3018
|
-
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
3019
|
-
import { existsSync as existsSync2, readFileSync as readFileSync3, writeFileSync, renameSync, unlinkSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3020
|
-
import { dirname } from "path";
|
|
3021
|
-
var SCRYPT_N = 16384;
|
|
3022
|
-
var SCRYPT_R = 8;
|
|
3023
|
-
var SCRYPT_P = 1;
|
|
3024
|
-
var SCRYPT_KEYLEN = 64;
|
|
3025
|
-
function id8(value) {
|
|
3026
|
-
return value.slice(0, 8);
|
|
3027
|
-
}
|
|
3028
|
-
function rowIds(users) {
|
|
3029
|
-
return users.map((u) => id8(u.userId)).join(",");
|
|
3030
|
-
}
|
|
3031
|
-
function appendUsersAuditLine(audit, fields) {
|
|
3032
|
-
const sess = audit.session ? ` session=${id8(audit.session)}` : "";
|
|
3033
|
-
const actor = audit.actor.includes("-") ? id8(audit.actor) : audit.actor;
|
|
3034
|
-
const targetFields = audit.target !== void 0 ? ` target=${audit.target.includes("-") ? id8(audit.target) : audit.target} explicitUserId=${audit.explicitUserId ? "true" : "false"}` : "";
|
|
3035
|
-
const line = `[users-audit] action=${fields.action} actor=${actor}${targetFields}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3036
|
-
`;
|
|
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;
|
|
3037
2777
|
try {
|
|
3038
|
-
|
|
3039
|
-
appendFileSync2(audit.logFile, line, { mode: 384 });
|
|
2778
|
+
parsed = JSON.parse(readFileSync2(path, "utf-8"));
|
|
3040
2779
|
} catch (err) {
|
|
3041
|
-
|
|
3042
|
-
`[
|
|
2780
|
+
throw new Error(
|
|
2781
|
+
`[skill-loader] brand.json unreadable at ${path}: ${err instanceof Error ? err.message : String(err)}`
|
|
3043
2782
|
);
|
|
3044
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;
|
|
3045
2792
|
}
|
|
3046
|
-
function
|
|
3047
|
-
|
|
3048
|
-
|
|
3049
|
-
if (err) reject(err);
|
|
3050
|
-
else resolve7(key);
|
|
3051
|
-
});
|
|
3052
|
-
});
|
|
3053
|
-
}
|
|
3054
|
-
async function hashAccessPassword(password) {
|
|
3055
|
-
const salt = randomBytes(32);
|
|
3056
|
-
const hash = await scryptAsync(password, salt);
|
|
3057
|
-
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
3058
|
-
}
|
|
3059
|
-
async function matchesHash(password, stored) {
|
|
3060
|
-
const colon = stored.indexOf(":");
|
|
3061
|
-
if (colon === -1) return false;
|
|
3062
|
-
const salt = Buffer.from(stored.slice(0, colon), "hex");
|
|
3063
|
-
const storedHash = Buffer.from(stored.slice(colon + 1), "hex");
|
|
3064
|
-
if (storedHash.length !== SCRYPT_KEYLEN) return false;
|
|
3065
|
-
let computed;
|
|
2793
|
+
function substituteBrandPlaceholders(content, sourcePath) {
|
|
2794
|
+
if (!content.includes(PLACEHOLDER)) return content;
|
|
2795
|
+
let productName;
|
|
3066
2796
|
try {
|
|
3067
|
-
|
|
3068
|
-
} catch {
|
|
3069
|
-
|
|
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;
|
|
3070
2803
|
}
|
|
3071
|
-
|
|
3072
|
-
|
|
3073
|
-
|
|
3074
|
-
|
|
3075
|
-
|
|
3076
|
-
|
|
3077
|
-
const parsed = JSON.parse(raw2);
|
|
3078
|
-
if (!Array.isArray(parsed)) throw new Error("users.json is not an array");
|
|
3079
|
-
return parsed;
|
|
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;
|
|
3080
2810
|
}
|
|
3081
|
-
|
|
3082
|
-
|
|
3083
|
-
|
|
3084
|
-
|
|
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
|
+
);
|
|
3085
2820
|
}
|
|
3086
|
-
|
|
3087
|
-
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
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 });
|
|
3092
2850
|
}
|
|
3093
|
-
|
|
3094
|
-
users[idx] = { ...users[idx], accessHash: await hashAccessPassword(password) };
|
|
3095
|
-
writeUsersAtomic(usersFile, users);
|
|
3096
|
-
appendUsersAuditLine(audit, { action: "set-access", field: "accessHash", rowsBefore: rows, rowsAfter: rows });
|
|
2851
|
+
return out;
|
|
3097
2852
|
}
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
|
|
3101
|
-
|
|
3102
|
-
|
|
3103
|
-
|
|
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
|
+
}
|
|
3104
2870
|
}
|
|
3105
|
-
|
|
3106
|
-
|
|
3107
|
-
|
|
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;
|
|
3108
2900
|
}
|
|
3109
2901
|
}
|
|
3110
|
-
|
|
3111
|
-
|
|
3112
|
-
|
|
3113
|
-
|
|
3114
|
-
try {
|
|
3115
|
-
users = readUsers(usersFile);
|
|
3116
|
-
} catch {
|
|
3117
|
-
return false;
|
|
2902
|
+
if (usersJsonUserId && fallback) {
|
|
2903
|
+
console.warn(
|
|
2904
|
+
`[maxy] resolveAccount: no account matches users.json userId ${usersJsonUserId} \u2014 falling back to ${fallback.accountId}`
|
|
2905
|
+
);
|
|
3118
2906
|
}
|
|
3119
|
-
|
|
3120
|
-
|
|
3121
|
-
|
|
3122
|
-
|
|
3123
|
-
|
|
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);
|
|
3124
2915
|
}
|
|
3125
|
-
return
|
|
2916
|
+
return raw2;
|
|
3126
2917
|
}
|
|
3127
|
-
|
|
3128
|
-
|
|
3129
|
-
|
|
3130
|
-
|
|
3131
|
-
|
|
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`);
|
|
3132
2925
|
return false;
|
|
3133
2926
|
}
|
|
3134
|
-
|
|
3135
|
-
}
|
|
3136
|
-
function accessStoreUnreadable(usersFile) {
|
|
3137
|
-
if (!existsSync2(usersFile)) return false;
|
|
3138
|
-
try {
|
|
3139
|
-
readUsers(usersFile);
|
|
2927
|
+
if (SHADOWED_SLUGS.has(slug)) {
|
|
2928
|
+
console.error(`[agent-slug] op=reject slug=${slug} reason=shadowed`);
|
|
3140
2929
|
return false;
|
|
3141
|
-
} catch {
|
|
3142
|
-
return true;
|
|
3143
2930
|
}
|
|
2931
|
+
return true;
|
|
3144
2932
|
}
|
|
3145
|
-
function
|
|
3146
|
-
|
|
3147
|
-
|
|
3148
|
-
|
|
3149
|
-
|
|
3150
|
-
if (idx === -1) return "noop-no-owner";
|
|
3151
|
-
const existing = users[idx].accessHash;
|
|
3152
|
-
if (typeof existing === "string" && existing.length > 0) {
|
|
3153
|
-
unlinkSync(legacyFile);
|
|
3154
|
-
return "noop-owner-has-hash";
|
|
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;
|
|
3155
2938
|
}
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
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;
|
|
3159
2945
|
}
|
|
3160
|
-
|
|
3161
|
-
|
|
3162
|
-
|
|
3163
|
-
|
|
3164
|
-
|
|
3165
|
-
|
|
3166
|
-
}
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
return raw2.includes(":") ? raw2 : null;
|
|
3171
|
-
}
|
|
3172
|
-
|
|
3173
|
-
// app/lib/password-strength.ts
|
|
3174
|
-
function validatePasswordStrength(password) {
|
|
3175
|
-
return [
|
|
3176
|
-
{ key: "length", label: "At least 8 characters", met: password.length >= 8 },
|
|
3177
|
-
{ key: "number", label: "Contains a number", met: /\d/.test(password) },
|
|
3178
|
-
{ key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
|
|
3179
|
-
{ key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
|
|
3180
|
-
];
|
|
3181
|
-
}
|
|
3182
|
-
function isPasswordValid(password) {
|
|
3183
|
-
return validatePasswordStrength(password).every((r) => r.met);
|
|
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;
|
|
3184
2956
|
}
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
import { scrypt as scrypt2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, createHmac } from "crypto";
|
|
3188
|
-
import { readFileSync as readFileSync4, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3189
|
-
import { dirname as dirname2 } from "path";
|
|
3190
|
-
var SCRYPT_N2 = 16384;
|
|
3191
|
-
var SCRYPT_R2 = 8;
|
|
3192
|
-
var SCRYPT_P2 = 1;
|
|
3193
|
-
var SCRYPT_KEYLEN2 = 64;
|
|
3194
|
-
function scryptAsync2(password, salt) {
|
|
3195
|
-
return new Promise((resolve7, reject) => {
|
|
3196
|
-
scrypt2(password, salt, SCRYPT_KEYLEN2, { N: SCRYPT_N2, r: SCRYPT_R2, p: SCRYPT_P2 }, (err, key) => {
|
|
3197
|
-
if (err) reject(err);
|
|
3198
|
-
else resolve7(key);
|
|
3199
|
-
});
|
|
3200
|
-
});
|
|
2957
|
+
function estimateTokens(text) {
|
|
2958
|
+
return Math.ceil(text.length / 4);
|
|
3201
2959
|
}
|
|
3202
|
-
|
|
3203
|
-
|
|
3204
|
-
|
|
3205
|
-
|
|
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 };
|
|
3206
3042
|
}
|
|
3207
|
-
function
|
|
3208
|
-
|
|
3209
|
-
if (accessStoreUnreadable(USERS_FILE)) return true;
|
|
3210
|
-
return readLegacyRemoteHash(REMOTE_PASSWORD_FILE) !== null;
|
|
3043
|
+
function isActiveAgentSlug(accountDir, slug) {
|
|
3044
|
+
return resolveAgentConfig(accountDir, slug).status === "active";
|
|
3211
3045
|
}
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
writeFileSync2(REMOTE_PASSWORD_FILE, hash, "utf-8");
|
|
3046
|
+
function getDefaultAccountId() {
|
|
3047
|
+
return resolveAccount()?.accountId ?? null;
|
|
3215
3048
|
}
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3049
|
+
|
|
3050
|
+
// ../lib/require-port-env/src/index.ts
|
|
3051
|
+
var MissingPortEnvError = class extends Error {
|
|
3052
|
+
envName;
|
|
3053
|
+
tag;
|
|
3054
|
+
failLine;
|
|
3055
|
+
constructor(envName, tag, failLine, message) {
|
|
3056
|
+
super(message);
|
|
3057
|
+
this.name = "MissingPortEnvError";
|
|
3058
|
+
this.envName = envName;
|
|
3059
|
+
this.tag = tag;
|
|
3060
|
+
this.failLine = failLine;
|
|
3222
3061
|
}
|
|
3223
|
-
|
|
3224
|
-
|
|
3062
|
+
};
|
|
3063
|
+
var VALID_PORT_MIN = 1;
|
|
3064
|
+
var VALID_PORT_MAX = 65535;
|
|
3065
|
+
function requirePortEnv(envName, options = {}) {
|
|
3066
|
+
const tag = options.tag ?? "port-env";
|
|
3067
|
+
const failLine = options.failLine ?? "missing-port-env";
|
|
3068
|
+
const raw2 = process.env[envName];
|
|
3069
|
+
if (raw2 === void 0 || raw2 === "") {
|
|
3070
|
+
const msg = `[${tag}] ${failLine} reason=missing-env env=${envName}`;
|
|
3071
|
+
throw new MissingPortEnvError(envName, tag, failLine, msg);
|
|
3072
|
+
}
|
|
3073
|
+
const parsed = Number.parseInt(raw2, 10);
|
|
3074
|
+
if (!Number.isInteger(parsed) || parsed < VALID_PORT_MIN || parsed > VALID_PORT_MAX || String(parsed) !== raw2.trim()) {
|
|
3075
|
+
const msg = `[${tag}] ${failLine} reason=invalid-port-env env=${envName} value=${JSON.stringify(raw2)}`;
|
|
3076
|
+
throw new MissingPortEnvError(envName, tag, failLine, msg);
|
|
3077
|
+
}
|
|
3078
|
+
return parsed;
|
|
3225
3079
|
}
|
|
3080
|
+
|
|
3081
|
+
// app/lib/claude-agent/session-store.ts
|
|
3082
|
+
import { createHash } from "crypto";
|
|
3226
3083
|
var SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
3227
|
-
var
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
|
|
3239
|
-
|
|
3084
|
+
var sessionStore = /* @__PURE__ */ new Map();
|
|
3085
|
+
function fingerprintSessionKey(raw2) {
|
|
3086
|
+
if (typeof raw2 !== "string" || !raw2) return raw2;
|
|
3087
|
+
if (/^sk_[a-f0-9]{16}$/.test(raw2)) return raw2;
|
|
3088
|
+
if (!raw2.startsWith("v1.")) return raw2;
|
|
3089
|
+
return `sk_${createHash("sha256").update(raw2).digest("hex").slice(0, 16)}`;
|
|
3090
|
+
}
|
|
3091
|
+
function registerSession(cacheKey2, agentType, accountId, _unused, userId, userName, role) {
|
|
3092
|
+
const existing = sessionStore.get(cacheKey2);
|
|
3093
|
+
if (existing) {
|
|
3094
|
+
existing.agentType = agentType;
|
|
3095
|
+
existing.accountId = accountId;
|
|
3096
|
+
existing.userId = userId ?? existing.userId;
|
|
3097
|
+
existing.userName = userName ?? existing.userName;
|
|
3098
|
+
existing.role = role ?? existing.role;
|
|
3099
|
+
return;
|
|
3240
3100
|
}
|
|
3241
|
-
|
|
3242
|
-
|
|
3243
|
-
|
|
3244
|
-
|
|
3245
|
-
|
|
3101
|
+
sessionStore.set(cacheKey2, { agentType, createdAt: Date.now(), accountId, userId, userName, role });
|
|
3102
|
+
}
|
|
3103
|
+
function validateSession(cacheKey2, agentType, _signedSessionToken) {
|
|
3104
|
+
const session = sessionStore.get(cacheKey2);
|
|
3105
|
+
if (!session) return { ok: false, reason: "session-not-registered" };
|
|
3106
|
+
if (session.agentType !== agentType) return { ok: false, reason: "agent-type-mismatch" };
|
|
3107
|
+
if (Date.now() - session.createdAt > SESSION_TTL_MS) {
|
|
3108
|
+
sessionStore.delete(cacheKey2);
|
|
3109
|
+
return { ok: false, reason: "session-expired-age" };
|
|
3246
3110
|
}
|
|
3247
|
-
|
|
3248
|
-
cachedSecret = Buffer.from(hex, "hex");
|
|
3249
|
-
return cachedSecret;
|
|
3111
|
+
return { ok: true };
|
|
3250
3112
|
}
|
|
3251
|
-
function
|
|
3252
|
-
return
|
|
3113
|
+
function getAccountIdForSession(cacheKey2) {
|
|
3114
|
+
return sessionStore.get(cacheKey2)?.accountId;
|
|
3253
3115
|
}
|
|
3254
|
-
function
|
|
3255
|
-
|
|
3256
|
-
return Buffer.from(padded, "base64");
|
|
3116
|
+
function getUserIdForSession(cacheKey2) {
|
|
3117
|
+
return sessionStore.get(cacheKey2)?.userId;
|
|
3257
3118
|
}
|
|
3258
|
-
function
|
|
3259
|
-
return
|
|
3119
|
+
function getUserNameForSession(cacheKey2) {
|
|
3120
|
+
return sessionStore.get(cacheKey2)?.userName;
|
|
3260
3121
|
}
|
|
3261
|
-
function
|
|
3262
|
-
|
|
3263
|
-
const payloadJson = JSON.stringify(payload);
|
|
3264
|
-
const payloadB64 = base64urlEncode(Buffer.from(payloadJson, "utf-8"));
|
|
3265
|
-
const sig = signPayload(payloadJson, getSecret());
|
|
3266
|
-
return `${TOKEN_PREFIX}${payloadB64}.${sig}`;
|
|
3122
|
+
function getRoleForSession(cacheKey2) {
|
|
3123
|
+
return sessionStore.get(cacheKey2)?.role;
|
|
3267
3124
|
}
|
|
3268
|
-
function
|
|
3269
|
-
|
|
3270
|
-
const rest = token.slice(TOKEN_PREFIX.length);
|
|
3271
|
-
const dot = rest.indexOf(".");
|
|
3272
|
-
if (dot === -1) return null;
|
|
3273
|
-
const payloadB64 = rest.slice(0, dot);
|
|
3274
|
-
const sigB64 = rest.slice(dot + 1);
|
|
3275
|
-
if (!payloadB64 || !sigB64) return null;
|
|
3276
|
-
let payloadJson;
|
|
3277
|
-
try {
|
|
3278
|
-
payloadJson = base64urlDecode(payloadB64).toString("utf-8");
|
|
3279
|
-
} catch {
|
|
3280
|
-
return null;
|
|
3281
|
-
}
|
|
3282
|
-
const expected = signPayload(payloadJson, getSecret());
|
|
3283
|
-
const expectedBuf = Buffer.from(expected, "utf-8");
|
|
3284
|
-
const provBuf = Buffer.from(sigB64, "utf-8");
|
|
3285
|
-
const signatureValid = expectedBuf.length === provBuf.length && timingSafeEqual2(expectedBuf, provBuf);
|
|
3286
|
-
let payload;
|
|
3287
|
-
try {
|
|
3288
|
-
payload = JSON.parse(payloadJson);
|
|
3289
|
-
} catch {
|
|
3290
|
-
return null;
|
|
3291
|
-
}
|
|
3292
|
-
if (!payload || typeof payload !== "object" || typeof payload.c !== "number" || typeof payload.n !== "string") {
|
|
3293
|
-
return null;
|
|
3294
|
-
}
|
|
3295
|
-
return { payload, signatureValid };
|
|
3125
|
+
function getSessionIdForSession(cacheKey2) {
|
|
3126
|
+
return sessionStore.get(cacheKey2)?.sessionId;
|
|
3296
3127
|
}
|
|
3297
|
-
function
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
return
|
|
3128
|
+
function setSessionIdForSession(cacheKey2, sessionId) {
|
|
3129
|
+
const session = sessionStore.get(cacheKey2);
|
|
3130
|
+
if (!session) return false;
|
|
3131
|
+
session.sessionId = sessionId;
|
|
3132
|
+
return true;
|
|
3302
3133
|
}
|
|
3303
|
-
function
|
|
3304
|
-
|
|
3305
|
-
|
|
3306
|
-
if (!parsed || !parsed.signatureValid) return { present: false, expired: false };
|
|
3307
|
-
return { present: true, expired: now - parsed.payload.c > SESSION_TTL_MS };
|
|
3134
|
+
function setWantsPriorConversation(cacheKey2) {
|
|
3135
|
+
const session = sessionStore.get(cacheKey2);
|
|
3136
|
+
if (session) session.wantsPriorConversation = true;
|
|
3308
3137
|
}
|
|
3309
|
-
function
|
|
3310
|
-
|
|
3311
|
-
const trimmed = ip.trim();
|
|
3312
|
-
if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
|
|
3313
|
-
return trimmed;
|
|
3138
|
+
function unregisterSession(cacheKey2) {
|
|
3139
|
+
return sessionStore.delete(cacheKey2);
|
|
3314
3140
|
}
|
|
3315
|
-
function
|
|
3316
|
-
return
|
|
3141
|
+
function getSessionKeyBySessionId(_sessionId) {
|
|
3142
|
+
return void 0;
|
|
3317
3143
|
}
|
|
3318
|
-
|
|
3319
|
-
|
|
3320
|
-
|
|
3321
|
-
|
|
3322
|
-
|
|
3323
|
-
|
|
3324
|
-
|
|
3325
|
-
|
|
3326
|
-
|
|
3327
|
-
|
|
3328
|
-
|
|
3144
|
+
|
|
3145
|
+
// server/loop-registry.ts
|
|
3146
|
+
import { readFileSync as readFileSync4 } from "fs";
|
|
3147
|
+
var TAG = "[loop-registry]";
|
|
3148
|
+
var STALL_INTERVALS = 3;
|
|
3149
|
+
var entries = /* @__PURE__ */ new Map();
|
|
3150
|
+
var defaultCpuIo = {
|
|
3151
|
+
ownCpuUs: () => {
|
|
3152
|
+
const u = process.cpuUsage();
|
|
3153
|
+
return u.user + u.system;
|
|
3154
|
+
},
|
|
3155
|
+
childCpuUs: (pid) => readPidCpuUs(pid)
|
|
3156
|
+
};
|
|
3157
|
+
var cpuIo = defaultCpuIo;
|
|
3158
|
+
function parsePidStatCpuUs(raw2) {
|
|
3159
|
+
const close = raw2.lastIndexOf(")");
|
|
3160
|
+
if (close === -1) return null;
|
|
3161
|
+
const after = raw2.slice(close + 2).split(" ");
|
|
3162
|
+
const utime = Number(after[11]);
|
|
3163
|
+
const stime = Number(after[12]);
|
|
3164
|
+
if (!Number.isFinite(utime) || !Number.isFinite(stime)) return null;
|
|
3165
|
+
return (utime + stime) / 100 * 1e6;
|
|
3329
3166
|
}
|
|
3330
|
-
function
|
|
3331
|
-
if (
|
|
3332
|
-
|
|
3167
|
+
function readPidCpuUs(pid) {
|
|
3168
|
+
if (process.platform !== "linux") return null;
|
|
3169
|
+
try {
|
|
3170
|
+
return parsePidStatCpuUs(readFileSync4(`/proc/${pid}/stat`, "utf8"));
|
|
3171
|
+
} catch {
|
|
3172
|
+
return null;
|
|
3173
|
+
}
|
|
3333
3174
|
}
|
|
3334
|
-
function
|
|
3335
|
-
const
|
|
3336
|
-
|
|
3337
|
-
|
|
3338
|
-
|
|
3339
|
-
|
|
3340
|
-
|
|
3175
|
+
function sampleCpu(nowMs = Date.now()) {
|
|
3176
|
+
for (const e of entries.values()) {
|
|
3177
|
+
if (!e.cpuAttributable) {
|
|
3178
|
+
e.cpuPct = null;
|
|
3179
|
+
continue;
|
|
3180
|
+
}
|
|
3181
|
+
let childUs = 0;
|
|
3182
|
+
for (const pid of [...e.children]) {
|
|
3183
|
+
const us = cpuIo.childCpuUs(pid);
|
|
3184
|
+
if (us === null) {
|
|
3185
|
+
e.children.delete(pid);
|
|
3186
|
+
continue;
|
|
3187
|
+
}
|
|
3188
|
+
childUs += us;
|
|
3341
3189
|
}
|
|
3190
|
+
e.lastChildCpuUs = Math.max(e.lastChildCpuUs, childUs);
|
|
3191
|
+
const totalUs = e.cumulativeCpuUs + e.lastChildCpuUs;
|
|
3192
|
+
const p = e.prior;
|
|
3193
|
+
if (p) {
|
|
3194
|
+
const wallUs = (nowMs - p.atMs) * 1e3;
|
|
3195
|
+
e.cpuPct = wallUs > 0 && totalUs >= p.cpuUs ? (totalUs - p.cpuUs) / wallUs * 100 : null;
|
|
3196
|
+
} else {
|
|
3197
|
+
e.cpuPct = null;
|
|
3198
|
+
}
|
|
3199
|
+
e.prior = { cpuUs: totalUs, atMs: nowMs };
|
|
3342
3200
|
}
|
|
3343
|
-
return { ip: remote, kind: classifyNormalizedIp(remote) };
|
|
3344
|
-
}
|
|
3345
|
-
var MAX_ATTEMPTS = 5;
|
|
3346
|
-
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
3347
|
-
var LOCAL_BUCKET = "__local__";
|
|
3348
|
-
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
3349
|
-
function bucketKey(client) {
|
|
3350
|
-
return client.kind === "external" ? client.ip : LOCAL_BUCKET;
|
|
3351
3201
|
}
|
|
3352
|
-
|
|
3353
|
-
|
|
3354
|
-
const
|
|
3355
|
-
|
|
3356
|
-
if (
|
|
3357
|
-
const
|
|
3358
|
-
|
|
3359
|
-
}
|
|
3360
|
-
if (rec.lockedUntil > 0) {
|
|
3361
|
-
rateLimitMap.delete(key);
|
|
3202
|
+
var processPrior = null;
|
|
3203
|
+
function sampleProcessCpu(nowMs = Date.now()) {
|
|
3204
|
+
const cpuUs = cpuIo.ownCpuUs();
|
|
3205
|
+
let processPct = null;
|
|
3206
|
+
if (processPrior) {
|
|
3207
|
+
const wallUs = (nowMs - processPrior.atMs) * 1e3;
|
|
3208
|
+
if (wallUs > 0) processPct = (cpuUs - processPrior.cpuUs) / wallUs * 100;
|
|
3362
3209
|
}
|
|
3363
|
-
|
|
3210
|
+
processPrior = { cpuUs, atMs: nowMs };
|
|
3211
|
+
const rows = [...entries.values()];
|
|
3212
|
+
const measured = rows.filter((e) => e.cpuPct !== null);
|
|
3213
|
+
const attributedPct = measured.length === 0 ? null : measured.reduce((sum, e) => sum + (e.cpuPct ?? 0), 0);
|
|
3214
|
+
const unattributedPct = processPct === null || attributedPct === null ? null : Math.max(0, processPct - attributedPct);
|
|
3215
|
+
return { processPct, attributedPct, unattributedPct };
|
|
3364
3216
|
}
|
|
3365
|
-
function
|
|
3366
|
-
|
|
3367
|
-
const rec = rateLimitMap.get(key) ?? { count: 0, lockedUntil: 0 };
|
|
3368
|
-
rec.count++;
|
|
3369
|
-
if (rec.count >= MAX_ATTEMPTS) {
|
|
3370
|
-
rec.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
3371
|
-
rec.count = 0;
|
|
3372
|
-
}
|
|
3373
|
-
rateLimitMap.set(key, rec);
|
|
3217
|
+
function messageOf(err) {
|
|
3218
|
+
return err instanceof Error ? err.message : String(err);
|
|
3374
3219
|
}
|
|
3375
|
-
function
|
|
3376
|
-
|
|
3220
|
+
function finish(e, startedAt, cpuStart, cpuAttributable, note, err) {
|
|
3221
|
+
const cpu = process.cpuUsage(cpuStart);
|
|
3222
|
+
e.running = false;
|
|
3223
|
+
e.lastDurationMs = Date.now() - startedAt;
|
|
3224
|
+
e.lastCpuMs = cpuAttributable ? (cpu.user + cpu.system) / 1e3 : null;
|
|
3225
|
+
if (err !== null) {
|
|
3226
|
+
e.lastOutcome = "error";
|
|
3227
|
+
e.lastError = messageOf(err);
|
|
3228
|
+
e.lastNote = null;
|
|
3229
|
+
e.consecutiveFailures += 1;
|
|
3230
|
+
console.error(
|
|
3231
|
+
`${TAG} op=run name=${e.spec.name} outcome=error streak=${e.consecutiveFailures} ms=${e.lastDurationMs} err="${e.lastError}"`
|
|
3232
|
+
);
|
|
3233
|
+
return;
|
|
3234
|
+
}
|
|
3235
|
+
e.lastOutcome = "ok";
|
|
3236
|
+
e.lastError = null;
|
|
3237
|
+
e.lastNote = typeof note === "string" && note.length > 0 ? note : null;
|
|
3238
|
+
e.lastSuccessMs = Date.now();
|
|
3239
|
+
e.successCount += 1;
|
|
3240
|
+
e.consecutiveFailures = 0;
|
|
3377
3241
|
}
|
|
3378
|
-
|
|
3379
|
-
|
|
3380
|
-
|
|
3381
|
-
|
|
3382
|
-
|
|
3383
|
-
|
|
3384
|
-
|
|
3385
|
-
|
|
3386
|
-
|
|
3387
|
-
|
|
3388
|
-
|
|
3242
|
+
function invoke(e) {
|
|
3243
|
+
if (e.running) {
|
|
3244
|
+
console.warn(`${TAG} op=skip name=${e.spec.name} reason=still-running`);
|
|
3245
|
+
return;
|
|
3246
|
+
}
|
|
3247
|
+
e.running = true;
|
|
3248
|
+
e.runCount += 1;
|
|
3249
|
+
e.lastStartMs = Date.now();
|
|
3250
|
+
const startedAt = e.lastStartMs;
|
|
3251
|
+
const cpuStart = process.cpuUsage();
|
|
3252
|
+
const ownStartUs = cpuIo.ownCpuUs();
|
|
3253
|
+
const ctx = {
|
|
3254
|
+
child: (pid) => {
|
|
3255
|
+
if (typeof pid === "number" && Number.isFinite(pid)) e.children.add(pid);
|
|
3389
3256
|
}
|
|
3390
|
-
|
|
3257
|
+
};
|
|
3258
|
+
let result;
|
|
3259
|
+
try {
|
|
3260
|
+
result = e.spec.run(ctx);
|
|
3261
|
+
} catch (err) {
|
|
3262
|
+
e.cumulativeCpuUs += Math.max(0, cpuIo.ownCpuUs() - ownStartUs);
|
|
3263
|
+
finish(e, startedAt, cpuStart, true, null, err);
|
|
3264
|
+
return;
|
|
3391
3265
|
}
|
|
3392
|
-
|
|
3393
|
-
|
|
3394
|
-
|
|
3395
|
-
|
|
3396
|
-
|
|
3397
|
-
|
|
3398
|
-
|
|
3399
|
-
|
|
3400
|
-
|
|
3401
|
-
|
|
3402
|
-
|
|
3403
|
-
|
|
3404
|
-
|
|
3405
|
-
|
|
3406
|
-
|
|
3407
|
-
const
|
|
3408
|
-
|
|
3409
|
-
|
|
3410
|
-
|
|
3411
|
-
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3415
|
-
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
3440
|
-
|
|
3441
|
-
|
|
3442
|
-
|
|
3443
|
-
|
|
3444
|
-
|
|
3445
|
-
|
|
3446
|
-
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
|
|
3452
|
-
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
const
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
|
|
3462
|
-
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
|
|
3466
|
-
|
|
3467
|
-
|
|
3468
|
-
|
|
3469
|
-
|
|
3470
|
-
|
|
3471
|
-
|
|
3472
|
-
|
|
3473
|
-
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
.
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
.logo-img {
|
|
3495
|
-
width: 110px;
|
|
3496
|
-
height: 110px;
|
|
3497
|
-
margin: -11px;
|
|
3498
|
-
object-fit: contain;
|
|
3499
|
-
}
|
|
3500
|
-
.title {
|
|
3501
|
-
font-family: ${displayFont};
|
|
3502
|
-
font-weight: 300;
|
|
3503
|
-
font-size: 28px;
|
|
3504
|
-
color: #2A2D38;
|
|
3505
|
-
text-align: center;
|
|
3506
|
-
letter-spacing: -0.01em;
|
|
3507
|
-
line-height: 1.3;
|
|
3508
|
-
}
|
|
3509
|
-
.subtitle {
|
|
3510
|
-
font-size: 15px;
|
|
3511
|
-
color: #4A4D58;
|
|
3512
|
-
text-align: center;
|
|
3513
|
-
margin-top: -8px;
|
|
3514
|
-
}
|
|
3515
|
-
.form-wrap {
|
|
3516
|
-
width: 100%;
|
|
3517
|
-
max-width: 300px;
|
|
3518
|
-
}
|
|
3519
|
-
.field { margin-bottom: 12px; }
|
|
3520
|
-
.field-label {
|
|
3521
|
-
display: block;
|
|
3522
|
-
font-size: 13px;
|
|
3523
|
-
font-weight: 500;
|
|
3524
|
-
color: #4A4D58;
|
|
3525
|
-
margin-bottom: 6px;
|
|
3526
|
-
}
|
|
3527
|
-
.field-input {
|
|
3528
|
-
width: 100%;
|
|
3529
|
-
padding: 10px 12px;
|
|
3530
|
-
border: 1px solid #E4E3DE;
|
|
3531
|
-
border-radius: 8px;
|
|
3532
|
-
font-size: 15px;
|
|
3533
|
-
font-family: inherit;
|
|
3534
|
-
outline: none;
|
|
3535
|
-
background: #fff;
|
|
3536
|
-
transition: border-color 0.15s, box-shadow 0.15s;
|
|
3537
|
-
}
|
|
3538
|
-
.field-input:focus {
|
|
3539
|
-
border-color: ${primaryColor};
|
|
3540
|
-
box-shadow: 0 0 0 3px ${primarySubtle};
|
|
3541
|
-
}
|
|
3542
|
-
.options {
|
|
3543
|
-
display: flex;
|
|
3544
|
-
align-items: center;
|
|
3545
|
-
justify-content: space-between;
|
|
3546
|
-
gap: 12px;
|
|
3547
|
-
margin-top: 8px;
|
|
3548
|
-
}
|
|
3549
|
-
|
|
3550
|
-
/* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
|
|
3551
|
-
.check {
|
|
3552
|
-
position: relative;
|
|
3553
|
-
display: flex;
|
|
3554
|
-
align-items: center;
|
|
3555
|
-
gap: 8px;
|
|
3556
|
-
cursor: pointer;
|
|
3557
|
-
user-select: none;
|
|
3558
|
-
}
|
|
3559
|
-
.check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
|
|
3560
|
-
.check-box {
|
|
3561
|
-
width: 14px;
|
|
3562
|
-
height: 14px;
|
|
3563
|
-
border: 1px solid #E4E3DE;
|
|
3564
|
-
border-radius: 2px;
|
|
3565
|
-
display: flex;
|
|
3566
|
-
align-items: center;
|
|
3567
|
-
justify-content: center;
|
|
3568
|
-
font-size: 10px;
|
|
3569
|
-
color: transparent;
|
|
3570
|
-
transition: border-color 0.15s, color 0.15s;
|
|
3571
|
-
flex-shrink: 0;
|
|
3572
|
-
}
|
|
3573
|
-
.check input:checked + .check-box {
|
|
3574
|
-
border-color: ${primaryColor};
|
|
3575
|
-
color: ${primaryColor};
|
|
3576
|
-
}
|
|
3577
|
-
.check-label {
|
|
3578
|
-
font-size: 13px;
|
|
3579
|
-
color: #4A4D58;
|
|
3580
|
-
}
|
|
3581
|
-
|
|
3582
|
-
/* Ghost button \u2014 matches Maxy ghost variant */
|
|
3583
|
-
.ghost {
|
|
3584
|
-
background: none;
|
|
3585
|
-
border: none;
|
|
3586
|
-
font-family: inherit;
|
|
3587
|
-
font-size: 13px;
|
|
3588
|
-
color: #4A4D58;
|
|
3589
|
-
cursor: pointer;
|
|
3590
|
-
padding: 4px 0;
|
|
3591
|
-
transition: color 0.15s;
|
|
3592
|
-
}
|
|
3593
|
-
.ghost:hover { color: #2A2D38; }
|
|
3594
|
-
|
|
3595
|
-
/* Primary button */
|
|
3596
|
-
.btn {
|
|
3597
|
-
width: 100%;
|
|
3598
|
-
padding: 12px;
|
|
3599
|
-
margin-top: 16px;
|
|
3600
|
-
background: ${primaryColor};
|
|
3601
|
-
color: #fff;
|
|
3602
|
-
border: none;
|
|
3603
|
-
border-radius: 10px;
|
|
3604
|
-
font-size: 15px;
|
|
3605
|
-
font-weight: 500;
|
|
3606
|
-
font-family: inherit;
|
|
3607
|
-
cursor: pointer;
|
|
3608
|
-
transition: background 0.15s;
|
|
3609
|
-
}
|
|
3610
|
-
.btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
|
|
3611
|
-
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
3612
|
-
|
|
3613
|
-
/* Messages */
|
|
3614
|
-
.msg {
|
|
3615
|
-
font-size: 13px;
|
|
3616
|
-
text-align: center;
|
|
3617
|
-
margin-top: 8px;
|
|
3618
|
-
}
|
|
3619
|
-
.msg--error { color: #c44; }
|
|
3620
|
-
.msg--success { color: ${primaryColor}; }
|
|
3621
|
-
|
|
3622
|
-
/* Strength checklist (setup mode) */
|
|
3623
|
-
.strength-checklist { margin: 8px 0; }
|
|
3624
|
-
.strength-item {
|
|
3625
|
-
font-size: 13px;
|
|
3626
|
-
color: #4A4D58;
|
|
3627
|
-
padding: 2px 0;
|
|
3628
|
-
transition: color 0.15s;
|
|
3629
|
-
}
|
|
3630
|
-
.strength-icon {
|
|
3631
|
-
display: inline-block;
|
|
3632
|
-
width: 16px;
|
|
3633
|
-
text-align: center;
|
|
3634
|
-
}
|
|
3635
|
-
|
|
3636
|
-
@media (max-width: 440px) {
|
|
3637
|
-
.page { padding: 24px 16px; }
|
|
3638
|
-
}
|
|
3639
|
-
</style>
|
|
3640
|
-
</head>
|
|
3641
|
-
<body>
|
|
3642
|
-
<div class="page">
|
|
3643
|
-
<img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
|
|
3644
|
-
<h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
|
|
3645
|
-
<p class="subtitle">${escapeHtml(subtitleText)}</p>
|
|
3646
|
-
|
|
3647
|
-
${successHtml}
|
|
3648
|
-
|
|
3649
|
-
<!-- Login -->
|
|
3650
|
-
<div id="login-section" class="form-wrap" style="display:${loginDisplay}">
|
|
3651
|
-
<form method="POST" action="/__remote-auth/login">
|
|
3652
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3653
|
-
<div class="field">
|
|
3654
|
-
<label class="field-label" for="password">Password</label>
|
|
3655
|
-
<input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
|
|
3656
|
-
</div>
|
|
3657
|
-
<div class="options">
|
|
3658
|
-
<label class="check">
|
|
3659
|
-
<input type="checkbox" id="show-login-pw">
|
|
3660
|
-
<span class="check-box">\u2731</span>
|
|
3661
|
-
<span class="check-label">Show password</span>
|
|
3662
|
-
</label>
|
|
3663
|
-
<button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
|
|
3664
|
-
</div>
|
|
3665
|
-
<button type="submit" class="btn" ${formDisabled}>Sign in</button>
|
|
3666
|
-
</form>
|
|
3667
|
-
${errorHtml}
|
|
3668
|
-
${lockoutHtml}
|
|
3669
|
-
</div>
|
|
3670
|
-
|
|
3671
|
-
<!-- Change password -->
|
|
3672
|
-
<div id="change-section" class="form-wrap" style="display:${changeDisplay}">
|
|
3673
|
-
<form method="POST" action="/__remote-auth/change-password">
|
|
3674
|
-
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
3675
|
-
<div class="field">
|
|
3676
|
-
<label class="field-label" for="current_password">Current password</label>
|
|
3677
|
-
<input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
|
|
3678
|
-
</div>
|
|
3679
|
-
<div class="field">
|
|
3680
|
-
<label class="field-label" for="new_password">New password</label>
|
|
3681
|
-
<input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
|
|
3682
|
-
</div>
|
|
3683
|
-
<div class="field">
|
|
3684
|
-
<label class="field-label" for="confirm_password">Confirm new password</label>
|
|
3685
|
-
<input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
|
|
3686
|
-
</div>
|
|
3687
|
-
<div class="options">
|
|
3688
|
-
<label class="check">
|
|
3689
|
-
<input type="checkbox" id="show-change-pw">
|
|
3690
|
-
<span class="check-box">\u2731</span>
|
|
3691
|
-
<span class="check-label">Show passwords</span>
|
|
3692
|
-
</label>
|
|
3693
|
-
<button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
|
|
3694
|
-
</div>
|
|
3695
|
-
<button type="submit" class="btn" ${formDisabled}>Change password</button>
|
|
3696
|
-
</form>
|
|
3697
|
-
${changeErrorHtml}
|
|
3698
|
-
</div>
|
|
3699
|
-
|
|
3700
|
-
<!-- Setup (initial password) -->
|
|
3701
|
-
<div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
|
|
3702
|
-
<form method="POST" action="/__remote-auth/set-initial-password">
|
|
3703
|
-
<div class="field">
|
|
3704
|
-
<label class="field-label" for="setup_password">Password</label>
|
|
3705
|
-
<input class="field-input" type="password" id="setup_password" name="password" required autofocus>
|
|
3706
|
-
</div>
|
|
3707
|
-
<div class="field">
|
|
3708
|
-
<label class="field-label" for="setup_confirm">Confirm password</label>
|
|
3709
|
-
<input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
|
|
3710
|
-
</div>
|
|
3711
|
-
<div id="strength-checklist" class="strength-checklist">
|
|
3712
|
-
<div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
|
|
3713
|
-
<div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
|
|
3714
|
-
<div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
|
|
3715
|
-
<div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
|
|
3716
|
-
</div>
|
|
3717
|
-
<div class="options">
|
|
3718
|
-
<label class="check">
|
|
3719
|
-
<input type="checkbox" id="show-setup-pw">
|
|
3720
|
-
<span class="check-box">\u2731</span>
|
|
3721
|
-
<span class="check-label">Show passwords</span>
|
|
3722
|
-
</label>
|
|
3723
|
-
</div>
|
|
3724
|
-
<button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
|
|
3725
|
-
</form>
|
|
3726
|
-
${setupErrorHtml}
|
|
3727
|
-
</div>
|
|
3728
|
-
|
|
3729
|
-
<!-- Success (after initial password set) -->
|
|
3730
|
-
<div id="success-section" class="form-wrap" style="display:${successDisplay}">
|
|
3731
|
-
<p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
|
|
3732
|
-
<p style="font-size:14px;color:#4A4D58;text-align:center;">You can close this tab and return to the onboarding tab.</p>
|
|
3733
|
-
<a href="/" style="display:block;text-align:center;margin-top:16px;font-size:13px;color:#4A4D58;text-decoration:none;" onmouseover="this.style.color='#2A2D38'" onmouseout="this.style.color='#4A4D58'">Sign in \u2192</a>
|
|
3734
|
-
<script>
|
|
3735
|
-
(function() {
|
|
3736
|
-
try {
|
|
3737
|
-
var ch = new BroadcastChannel('platform-onboarding');
|
|
3738
|
-
setTimeout(function() {
|
|
3739
|
-
ch.postMessage({ type: 'remote-password-set' });
|
|
3740
|
-
ch.close();
|
|
3741
|
-
}, 2500);
|
|
3742
|
-
} catch(e) {}
|
|
3743
|
-
})();
|
|
3744
|
-
</script>
|
|
3745
|
-
</div>
|
|
3746
|
-
</div>
|
|
3747
|
-
|
|
3748
|
-
<script>
|
|
3749
|
-
/* Toggle between login and change-password modes */
|
|
3750
|
-
function toggleMode(mode) {
|
|
3751
|
-
document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
|
|
3752
|
-
document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
|
|
3753
|
-
/* Focus first input in the active section */
|
|
3754
|
-
var id = mode === 'login' ? 'password' : 'current_password';
|
|
3755
|
-
var el = document.getElementById(id);
|
|
3756
|
-
if (el) el.focus();
|
|
3757
|
-
}
|
|
3758
|
-
|
|
3759
|
-
/* Show/hide password toggles */
|
|
3760
|
-
function bindShowHide(checkboxId, inputIds) {
|
|
3761
|
-
var cb = document.getElementById(checkboxId);
|
|
3762
|
-
if (!cb) return;
|
|
3763
|
-
cb.addEventListener('change', function() {
|
|
3764
|
-
var type = cb.checked ? 'text' : 'password';
|
|
3765
|
-
inputIds.forEach(function(id) {
|
|
3766
|
-
var el = document.getElementById(id);
|
|
3767
|
-
if (el) el.type = type;
|
|
3768
|
-
});
|
|
3769
|
-
});
|
|
3770
|
-
}
|
|
3771
|
-
bindShowHide('show-login-pw', ['password']);
|
|
3772
|
-
bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
|
|
3773
|
-
bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
|
|
3774
|
-
|
|
3775
|
-
/* Explicit Enter-to-submit for the login password field */
|
|
3776
|
-
var loginPw = document.getElementById('password');
|
|
3777
|
-
if (loginPw) {
|
|
3778
|
-
loginPw.addEventListener('keydown', function(e) {
|
|
3779
|
-
if (e.key === 'Enter') {
|
|
3780
|
-
e.preventDefault();
|
|
3781
|
-
this.closest('form').requestSubmit();
|
|
3782
|
-
}
|
|
3783
|
-
});
|
|
3784
|
-
}
|
|
3785
|
-
|
|
3786
|
-
/* Live password strength validation (setup mode) */
|
|
3787
|
-
(function() {
|
|
3788
|
-
var pw = document.getElementById('setup_password');
|
|
3789
|
-
var confirm = document.getElementById('setup_confirm');
|
|
3790
|
-
var btn = document.getElementById('setup-submit');
|
|
3791
|
-
if (!pw || !confirm || !btn) return;
|
|
3792
|
-
|
|
3793
|
-
var rules = [
|
|
3794
|
-
{ id: 'req-length', test: function(p) { return p.length >= 8; } },
|
|
3795
|
-
{ id: 'req-number', test: function(p) { return /\\d/.test(p); } },
|
|
3796
|
-
{ id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
|
|
3797
|
-
{ id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
|
|
3798
|
-
];
|
|
3266
|
+
if (result instanceof Promise) {
|
|
3267
|
+
e.cpuAttributable = false;
|
|
3268
|
+
result.then(
|
|
3269
|
+
(note) => finish(e, startedAt, cpuStart, false, note, null),
|
|
3270
|
+
(err) => finish(e, startedAt, cpuStart, false, null, err)
|
|
3271
|
+
);
|
|
3272
|
+
return;
|
|
3273
|
+
}
|
|
3274
|
+
e.cumulativeCpuUs += Math.max(0, cpuIo.ownCpuUs() - ownStartUs);
|
|
3275
|
+
finish(e, startedAt, cpuStart, true, result, null);
|
|
3276
|
+
}
|
|
3277
|
+
function registerLoop(spec) {
|
|
3278
|
+
if (entries.has(spec.name)) {
|
|
3279
|
+
throw new Error(`${TAG} loop "${spec.name}" is already registered`);
|
|
3280
|
+
}
|
|
3281
|
+
const e = {
|
|
3282
|
+
spec,
|
|
3283
|
+
armedAtMs: Date.now(),
|
|
3284
|
+
lastStartMs: null,
|
|
3285
|
+
lastSuccessMs: null,
|
|
3286
|
+
lastDurationMs: null,
|
|
3287
|
+
lastCpuMs: null,
|
|
3288
|
+
lastOutcome: null,
|
|
3289
|
+
lastError: null,
|
|
3290
|
+
lastNote: null,
|
|
3291
|
+
runCount: 0,
|
|
3292
|
+
successCount: 0,
|
|
3293
|
+
consecutiveFailures: 0,
|
|
3294
|
+
running: false,
|
|
3295
|
+
timers: [],
|
|
3296
|
+
cumulativeCpuUs: 0,
|
|
3297
|
+
cpuAttributable: true,
|
|
3298
|
+
children: /* @__PURE__ */ new Set(),
|
|
3299
|
+
lastChildCpuUs: 0,
|
|
3300
|
+
prior: null,
|
|
3301
|
+
cpuPct: null
|
|
3302
|
+
};
|
|
3303
|
+
entries.set(spec.name, e);
|
|
3304
|
+
const interval = setInterval(() => invoke(e), spec.intervalMs);
|
|
3305
|
+
if (typeof interval.unref === "function") interval.unref();
|
|
3306
|
+
e.timers.push(interval);
|
|
3307
|
+
console.log(
|
|
3308
|
+
`${TAG} op=arm name=${spec.name} intervalMs=${spec.intervalMs} firstRunMs=${spec.firstRunDelayMs ?? "interval"} detached=${spec.detached === true} armed=${entries.size}`
|
|
3309
|
+
);
|
|
3310
|
+
if (spec.firstRunDelayMs === 0) {
|
|
3311
|
+
invoke(e);
|
|
3312
|
+
} else if (spec.firstRunDelayMs !== void 0) {
|
|
3313
|
+
const first = setTimeout(() => invoke(e), spec.firstRunDelayMs);
|
|
3314
|
+
if (typeof first.unref === "function") first.unref();
|
|
3315
|
+
e.timers.push(first);
|
|
3316
|
+
}
|
|
3317
|
+
let stopped = false;
|
|
3318
|
+
return () => {
|
|
3319
|
+
if (stopped) return;
|
|
3320
|
+
stopped = true;
|
|
3321
|
+
for (const t of e.timers) clearInterval(t);
|
|
3322
|
+
if (entries.get(spec.name) === e) entries.delete(spec.name);
|
|
3323
|
+
console.log(`${TAG} op=disarm name=${spec.name} armed=${entries.size}`);
|
|
3324
|
+
};
|
|
3325
|
+
}
|
|
3326
|
+
function stateOf(e, nowMs) {
|
|
3327
|
+
if (e.lastStartMs === null) {
|
|
3328
|
+
return nowMs - e.armedAtMs < e.spec.intervalMs ? "waiting" : "never-run";
|
|
3329
|
+
}
|
|
3330
|
+
const overdue = nowMs - e.lastStartMs > e.spec.intervalMs * STALL_INTERVALS;
|
|
3331
|
+
if (overdue) return "stalled";
|
|
3332
|
+
if (e.running) return "running";
|
|
3333
|
+
if (e.successCount === 0) return "never-succeeded";
|
|
3334
|
+
if (e.consecutiveFailures > 0) return "failing";
|
|
3335
|
+
return "ok";
|
|
3336
|
+
}
|
|
3337
|
+
function nextDueOf(e, nowMs) {
|
|
3338
|
+
const elapsed = Math.max(0, nowMs - e.armedAtMs);
|
|
3339
|
+
const ticks = Math.floor(elapsed / e.spec.intervalMs) + 1;
|
|
3340
|
+
return e.armedAtMs + ticks * e.spec.intervalMs;
|
|
3341
|
+
}
|
|
3342
|
+
function listLoops(nowMs = Date.now()) {
|
|
3343
|
+
return [...entries.values()].map((e) => ({
|
|
3344
|
+
name: e.spec.name,
|
|
3345
|
+
intervalMs: e.spec.intervalMs,
|
|
3346
|
+
detached: e.spec.detached === true,
|
|
3347
|
+
armedAtMs: e.armedAtMs,
|
|
3348
|
+
lastStartMs: e.lastStartMs,
|
|
3349
|
+
lastSuccessMs: e.lastSuccessMs,
|
|
3350
|
+
lastDurationMs: e.lastDurationMs,
|
|
3351
|
+
lastCpuMs: e.lastCpuMs,
|
|
3352
|
+
cpuPct: e.cpuPct,
|
|
3353
|
+
cpuAttributable: e.cpuAttributable,
|
|
3354
|
+
lastOutcome: e.lastOutcome,
|
|
3355
|
+
lastError: e.lastError,
|
|
3356
|
+
lastNote: e.lastNote,
|
|
3357
|
+
runCount: e.runCount,
|
|
3358
|
+
successCount: e.successCount,
|
|
3359
|
+
consecutiveFailures: e.consecutiveFailures,
|
|
3360
|
+
running: e.running,
|
|
3361
|
+
state: stateOf(e, nowMs),
|
|
3362
|
+
nextDueMs: nextDueOf(e, nowMs)
|
|
3363
|
+
}));
|
|
3364
|
+
}
|
|
3365
|
+
function armedCount() {
|
|
3366
|
+
return entries.size;
|
|
3367
|
+
}
|
|
3799
3368
|
|
|
3800
|
-
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3369
|
+
// app/lib/vnc-logger.ts
|
|
3370
|
+
import { appendFileSync, mkdirSync } from "fs";
|
|
3371
|
+
import { resolve as resolve3 } from "path";
|
|
3372
|
+
var VNC_LOG_FILE = resolve3(LOG_DIR, "vnc-boot.log");
|
|
3373
|
+
try {
|
|
3374
|
+
mkdirSync(LOG_DIR, { recursive: true });
|
|
3375
|
+
} catch (err) {
|
|
3376
|
+
console.error(`[vnc-log-fail] mkdir ${LOG_DIR} failed: ${err.message}`);
|
|
3377
|
+
}
|
|
3378
|
+
function vncLog(phase, fields = {}) {
|
|
3379
|
+
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
3380
|
+
const kv = Object.entries(fields).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3381
|
+
const line = kv.length > 0 ? `[${ts}] [${phase}] ${kv}
|
|
3382
|
+
` : `[${ts}] [${phase}]
|
|
3383
|
+
`;
|
|
3384
|
+
try {
|
|
3385
|
+
appendFileSync(VNC_LOG_FILE, line);
|
|
3386
|
+
} catch (err) {
|
|
3387
|
+
console.error(`[vnc-log-fail] ${err.message} \u2014 dropped: ${line.slice(0, 300).trim()}`);
|
|
3388
|
+
}
|
|
3389
|
+
}
|
|
3390
|
+
var corrCounter = 0;
|
|
3391
|
+
function newCorrId() {
|
|
3392
|
+
corrCounter = corrCounter + 1 & 4294967295;
|
|
3393
|
+
const counterHex = corrCounter.toString(16).padStart(8, "0");
|
|
3394
|
+
const randomHex = Math.floor(Math.random() * 16777215).toString(16).padStart(6, "0");
|
|
3395
|
+
return `${counterHex}-${randomHex}`;
|
|
3396
|
+
}
|
|
3397
|
+
function sanitizeClientCorrId(raw2) {
|
|
3398
|
+
if (!raw2) return null;
|
|
3399
|
+
if (raw2.length > 32) return null;
|
|
3400
|
+
if (!/^[A-Za-z0-9_-]+$/.test(raw2)) return null;
|
|
3401
|
+
return raw2;
|
|
3402
|
+
}
|
|
3403
|
+
function formatKv(fields) {
|
|
3404
|
+
return Object.entries(fields).filter(([, v]) => v !== void 0).map(([k, v]) => `${k}=${JSON.stringify(v)}`).join(" ");
|
|
3405
|
+
}
|
|
3406
|
+
function httpLog(fields) {
|
|
3407
|
+
console.error(`[http] ${formatKv(fields)}`);
|
|
3408
|
+
}
|
|
3409
|
+
function websockifyLog(event, fields = {}) {
|
|
3410
|
+
console.error(`[websockify] ${formatKv({ event, ...fields })}`);
|
|
3411
|
+
}
|
|
3813
3412
|
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3413
|
+
// ../lib/admin-access-password/src/index.ts
|
|
3414
|
+
import { scrypt, randomBytes, timingSafeEqual } from "crypto";
|
|
3415
|
+
import { existsSync as existsSync4, readFileSync as readFileSync5, writeFileSync, renameSync, unlinkSync, appendFileSync as appendFileSync2, mkdirSync as mkdirSync2 } from "fs";
|
|
3416
|
+
import { dirname } from "path";
|
|
3417
|
+
var SCRYPT_N = 16384;
|
|
3418
|
+
var SCRYPT_R = 8;
|
|
3419
|
+
var SCRYPT_P = 1;
|
|
3420
|
+
var SCRYPT_KEYLEN = 64;
|
|
3421
|
+
function id8(value) {
|
|
3422
|
+
return value.slice(0, 8);
|
|
3423
|
+
}
|
|
3424
|
+
function rowIds(users) {
|
|
3425
|
+
return users.map((u) => id8(u.userId)).join(",");
|
|
3426
|
+
}
|
|
3427
|
+
function appendUsersAuditLine(audit, fields) {
|
|
3428
|
+
const sess = audit.session ? ` session=${id8(audit.session)}` : "";
|
|
3429
|
+
const actor = audit.actor.includes("-") ? id8(audit.actor) : audit.actor;
|
|
3430
|
+
const targetFields = audit.target !== void 0 ? ` target=${audit.target.includes("-") ? id8(audit.target) : audit.target} explicitUserId=${audit.explicitUserId ? "true" : "false"}` : "";
|
|
3431
|
+
const line = `[users-audit] action=${fields.action} actor=${actor}${targetFields}${sess} field=${fields.field} rowsBefore=${fields.rowsBefore} rowsAfter=${fields.rowsAfter} ts=${(/* @__PURE__ */ new Date()).toISOString()}
|
|
3432
|
+
`;
|
|
3433
|
+
try {
|
|
3434
|
+
mkdirSync2(dirname(audit.logFile), { recursive: true, mode: 448 });
|
|
3435
|
+
appendFileSync2(audit.logFile, line, { mode: 384 });
|
|
3436
|
+
} catch (err) {
|
|
3437
|
+
console.error(
|
|
3438
|
+
`[users-audit] op=write-failed logFile=${audit.logFile} error=${err instanceof Error ? err.message : String(err)}`
|
|
3439
|
+
);
|
|
3440
|
+
}
|
|
3441
|
+
}
|
|
3442
|
+
function scryptAsync(password, salt) {
|
|
3443
|
+
return new Promise((resolve7, reject) => {
|
|
3444
|
+
scrypt(password, salt, SCRYPT_KEYLEN, { N: SCRYPT_N, r: SCRYPT_R, p: SCRYPT_P }, (err, key) => {
|
|
3445
|
+
if (err) reject(err);
|
|
3446
|
+
else resolve7(key);
|
|
3447
|
+
});
|
|
3448
|
+
});
|
|
3449
|
+
}
|
|
3450
|
+
async function hashAccessPassword(password) {
|
|
3451
|
+
const salt = randomBytes(32);
|
|
3452
|
+
const hash = await scryptAsync(password, salt);
|
|
3453
|
+
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
3454
|
+
}
|
|
3455
|
+
async function matchesHash(password, stored) {
|
|
3456
|
+
const colon = stored.indexOf(":");
|
|
3457
|
+
if (colon === -1) return false;
|
|
3458
|
+
const salt = Buffer.from(stored.slice(0, colon), "hex");
|
|
3459
|
+
const storedHash = Buffer.from(stored.slice(colon + 1), "hex");
|
|
3460
|
+
if (storedHash.length !== SCRYPT_KEYLEN) return false;
|
|
3461
|
+
let computed;
|
|
3462
|
+
try {
|
|
3463
|
+
computed = await scryptAsync(password, salt);
|
|
3464
|
+
} catch {
|
|
3465
|
+
return false;
|
|
3466
|
+
}
|
|
3467
|
+
return timingSafeEqual(computed, storedHash);
|
|
3820
3468
|
}
|
|
3821
|
-
function
|
|
3822
|
-
|
|
3469
|
+
function readUsers(usersFile) {
|
|
3470
|
+
if (!existsSync4(usersFile)) return [];
|
|
3471
|
+
const raw2 = readFileSync5(usersFile, "utf-8").trim();
|
|
3472
|
+
if (!raw2) return [];
|
|
3473
|
+
const parsed = JSON.parse(raw2);
|
|
3474
|
+
if (!Array.isArray(parsed)) throw new Error("users.json is not an array");
|
|
3475
|
+
return parsed;
|
|
3823
3476
|
}
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3477
|
+
function writeUsersAtomic(usersFile, users) {
|
|
3478
|
+
const tmp = `${usersFile}.tmp-${process.pid}`;
|
|
3479
|
+
writeFileSync(tmp, JSON.stringify(users, null, 2) + "\n", { mode: 384 });
|
|
3480
|
+
renameSync(tmp, usersFile);
|
|
3481
|
+
}
|
|
3482
|
+
async function setAccessPassword(userId, password, usersFile, audit) {
|
|
3483
|
+
const users = readUsers(usersFile);
|
|
3484
|
+
const idx = users.findIndex((u) => u.userId === userId);
|
|
3485
|
+
if (idx === -1) throw new Error(`no users.json record for userId=${userId}`);
|
|
3486
|
+
if (await accessPasswordCollides(password, usersFile, userId)) {
|
|
3487
|
+
throw new Error("accessHash collision: password already in use by another admin");
|
|
3830
3488
|
}
|
|
3831
|
-
|
|
3832
|
-
|
|
3489
|
+
const rows = rowIds(users);
|
|
3490
|
+
users[idx] = { ...users[idx], accessHash: await hashAccessPassword(password) };
|
|
3491
|
+
writeUsersAtomic(usersFile, users);
|
|
3492
|
+
appendUsersAuditLine(audit, { action: "set-access", field: "accessHash", rowsBefore: rows, rowsAfter: rows });
|
|
3493
|
+
}
|
|
3494
|
+
async function verifyAccessPassword(password, usersFile) {
|
|
3495
|
+
let users;
|
|
3496
|
+
try {
|
|
3497
|
+
users = readUsers(usersFile);
|
|
3498
|
+
} catch {
|
|
3499
|
+
return null;
|
|
3833
3500
|
}
|
|
3834
|
-
|
|
3835
|
-
|
|
3501
|
+
for (const u of users) {
|
|
3502
|
+
if (typeof u.accessHash === "string" && await matchesHash(password, u.accessHash)) {
|
|
3503
|
+
return u.userId;
|
|
3504
|
+
}
|
|
3836
3505
|
}
|
|
3837
|
-
|
|
3838
|
-
|
|
3839
|
-
|
|
3506
|
+
return null;
|
|
3507
|
+
}
|
|
3508
|
+
async function accessPasswordCollides(password, usersFile, excludeUserId) {
|
|
3509
|
+
let users;
|
|
3510
|
+
try {
|
|
3511
|
+
users = readUsers(usersFile);
|
|
3512
|
+
} catch {
|
|
3513
|
+
return false;
|
|
3840
3514
|
}
|
|
3841
|
-
|
|
3515
|
+
for (const u of users) {
|
|
3516
|
+
if (u.userId === excludeUserId) continue;
|
|
3517
|
+
if (typeof u.accessHash === "string" && await matchesHash(password, u.accessHash)) {
|
|
3518
|
+
return true;
|
|
3519
|
+
}
|
|
3520
|
+
}
|
|
3521
|
+
return false;
|
|
3842
3522
|
}
|
|
3843
|
-
function
|
|
3844
|
-
|
|
3845
|
-
const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
3846
|
-
if (!match2) return null;
|
|
3523
|
+
function anyAccessPasswordConfigured(usersFile) {
|
|
3524
|
+
let users;
|
|
3847
3525
|
try {
|
|
3848
|
-
|
|
3526
|
+
users = readUsers(usersFile);
|
|
3849
3527
|
} catch {
|
|
3850
|
-
return
|
|
3528
|
+
return false;
|
|
3851
3529
|
}
|
|
3530
|
+
return users.some((u) => typeof u.accessHash === "string" && u.accessHash.length > 0);
|
|
3852
3531
|
}
|
|
3853
|
-
function
|
|
3854
|
-
|
|
3532
|
+
function accessStoreUnreadable(usersFile) {
|
|
3533
|
+
if (!existsSync4(usersFile)) return false;
|
|
3534
|
+
try {
|
|
3535
|
+
readUsers(usersFile);
|
|
3536
|
+
return false;
|
|
3537
|
+
} catch {
|
|
3538
|
+
return true;
|
|
3539
|
+
}
|
|
3540
|
+
}
|
|
3541
|
+
function migrateLegacyRemotePassword(usersFile, legacyFile, ownerUserId, audit) {
|
|
3542
|
+
if (!existsSync4(legacyFile)) return "noop-no-legacy";
|
|
3543
|
+
const legacy = readFileSync5(legacyFile, "utf-8").trim();
|
|
3544
|
+
const users = readUsers(usersFile);
|
|
3545
|
+
const idx = users.findIndex((u) => u.userId === ownerUserId);
|
|
3546
|
+
if (idx === -1) return "noop-no-owner";
|
|
3547
|
+
const existing = users[idx].accessHash;
|
|
3548
|
+
if (typeof existing === "string" && existing.length > 0) {
|
|
3549
|
+
unlinkSync(legacyFile);
|
|
3550
|
+
return "noop-owner-has-hash";
|
|
3551
|
+
}
|
|
3552
|
+
if (!legacy.includes(":")) {
|
|
3553
|
+
unlinkSync(legacyFile);
|
|
3554
|
+
return "noop-no-legacy";
|
|
3555
|
+
}
|
|
3556
|
+
const rows = rowIds(users);
|
|
3557
|
+
users[idx] = { ...users[idx], accessHash: legacy };
|
|
3558
|
+
writeUsersAtomic(usersFile, users);
|
|
3559
|
+
unlinkSync(legacyFile);
|
|
3560
|
+
appendUsersAuditLine(audit, { action: "migrate", field: "accessHash", rowsBefore: rows, rowsAfter: rows });
|
|
3561
|
+
return "migrated";
|
|
3562
|
+
}
|
|
3563
|
+
function readLegacyRemoteHash(legacyFile) {
|
|
3564
|
+
if (!existsSync4(legacyFile)) return null;
|
|
3565
|
+
const raw2 = readFileSync5(legacyFile, "utf-8").trim();
|
|
3566
|
+
return raw2.includes(":") ? raw2 : null;
|
|
3855
3567
|
}
|
|
3856
3568
|
|
|
3857
|
-
// app/lib/
|
|
3858
|
-
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
|
|
3569
|
+
// app/lib/password-strength.ts
|
|
3570
|
+
function validatePasswordStrength(password) {
|
|
3571
|
+
return [
|
|
3572
|
+
{ key: "length", label: "At least 8 characters", met: password.length >= 8 },
|
|
3573
|
+
{ key: "number", label: "Contains a number", met: /\d/.test(password) },
|
|
3574
|
+
{ key: "special", label: "Contains a special character", met: /[^A-Za-z0-9]/.test(password) },
|
|
3575
|
+
{ key: "whitespace", label: "No spaces", met: password.length > 0 && !/\s/.test(password) }
|
|
3576
|
+
];
|
|
3577
|
+
}
|
|
3578
|
+
function isPasswordValid(password) {
|
|
3579
|
+
return validatePasswordStrength(password).every((r) => r.met);
|
|
3580
|
+
}
|
|
3862
3581
|
|
|
3863
|
-
//
|
|
3864
|
-
|
|
3865
|
-
|
|
3866
|
-
|
|
3867
|
-
|
|
3868
|
-
|
|
3869
|
-
|
|
3870
|
-
|
|
3871
|
-
|
|
3872
|
-
|
|
3873
|
-
|
|
3874
|
-
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
// Task 1881 — the data portal's device-held-file fetch. Reached by the edge
|
|
3879
|
-
// Pages Function's signed HEAD/GET probe on the public host; authenticated by
|
|
3880
|
-
// the per-account HMAC signature the route re-verifies (portal-fetch.ts), not
|
|
3881
|
-
// the remote-auth gate. Pointing PORTAL_INSTALL_ORIGIN at the admin host
|
|
3882
|
-
// instead made every download 503 (remote-auth-required on the probe).
|
|
3883
|
-
"/api/portal/fetch",
|
|
3884
|
-
// QuickBooks OAuth consent callback. Intuit redirects the operator's browser
|
|
3885
|
-
// here with no admin cookie; the route authenticates by validating the OAuth
|
|
3886
|
-
// `state` nonce, not the remote-auth gate (same model as /api/telegram/).
|
|
3887
|
-
"/api/quickbooks/",
|
|
3888
|
-
"/assets/",
|
|
3889
|
-
"/brand/",
|
|
3890
|
-
"/agent-assets/",
|
|
3891
|
-
"/generated/",
|
|
3892
|
-
"/g/",
|
|
3893
|
-
"/sites/"
|
|
3894
|
-
];
|
|
3895
|
-
var PUBLIC_ALLOWED_EXACT = [
|
|
3896
|
-
"/favicon.ico",
|
|
3897
|
-
"/brand-defaults.css"
|
|
3898
|
-
];
|
|
3899
|
-
function isPublicPathAllowed(path) {
|
|
3900
|
-
if (path === "/") return true;
|
|
3901
|
-
if (PUBLIC_ALLOWED_EXACT.includes(path)) return true;
|
|
3902
|
-
if (PUBLIC_ALLOWED_PREFIXES.some((prefix) => path.startsWith(prefix))) return true;
|
|
3903
|
-
return AGENT_SLUG_PATTERN.test(path);
|
|
3582
|
+
// app/lib/remote-auth.ts
|
|
3583
|
+
import { scrypt as scrypt2, randomBytes as randomBytes2, timingSafeEqual as timingSafeEqual2, createHmac } from "crypto";
|
|
3584
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync2, mkdirSync as mkdirSync3 } from "fs";
|
|
3585
|
+
import { dirname as dirname2 } from "path";
|
|
3586
|
+
var SCRYPT_N2 = 16384;
|
|
3587
|
+
var SCRYPT_R2 = 8;
|
|
3588
|
+
var SCRYPT_P2 = 1;
|
|
3589
|
+
var SCRYPT_KEYLEN2 = 64;
|
|
3590
|
+
function scryptAsync2(password, salt) {
|
|
3591
|
+
return new Promise((resolve7, reject) => {
|
|
3592
|
+
scrypt2(password, salt, SCRYPT_KEYLEN2, { N: SCRYPT_N2, r: SCRYPT_R2, p: SCRYPT_P2 }, (err, key) => {
|
|
3593
|
+
if (err) reject(err);
|
|
3594
|
+
else resolve7(key);
|
|
3595
|
+
});
|
|
3596
|
+
});
|
|
3904
3597
|
}
|
|
3905
|
-
function
|
|
3906
|
-
|
|
3598
|
+
async function hashPassword(password) {
|
|
3599
|
+
const salt = randomBytes2(32);
|
|
3600
|
+
const hash = await scryptAsync2(password, salt);
|
|
3601
|
+
return `${salt.toString("hex")}:${hash.toString("hex")}`;
|
|
3907
3602
|
}
|
|
3908
|
-
function
|
|
3909
|
-
if (
|
|
3910
|
-
if (
|
|
3911
|
-
return
|
|
3603
|
+
function isRemoteAuthConfigured() {
|
|
3604
|
+
if (anyAccessPasswordConfigured(USERS_FILE)) return true;
|
|
3605
|
+
if (accessStoreUnreadable(USERS_FILE)) return true;
|
|
3606
|
+
return readLegacyRemoteHash(REMOTE_PASSWORD_FILE) !== null;
|
|
3912
3607
|
}
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
3921
|
-
|
|
3922
|
-
|
|
3608
|
+
async function setRemotePassword(password) {
|
|
3609
|
+
const hash = await hashPassword(password);
|
|
3610
|
+
writeFileSync2(REMOTE_PASSWORD_FILE, hash, "utf-8");
|
|
3611
|
+
}
|
|
3612
|
+
async function verifyRemotePassword(password) {
|
|
3613
|
+
const matchedUserId = await verifyAccessPassword(password, USERS_FILE);
|
|
3614
|
+
if (matchedUserId) return { ok: true, hashBytes: SCRYPT_KEYLEN2, userId: matchedUserId };
|
|
3615
|
+
const legacy = readLegacyRemoteHash(REMOTE_PASSWORD_FILE);
|
|
3616
|
+
if (legacy && await matchesHash(password, legacy)) {
|
|
3617
|
+
return { ok: true, hashBytes: SCRYPT_KEYLEN2, userId: null };
|
|
3618
|
+
}
|
|
3619
|
+
const configured = anyAccessPasswordConfigured(USERS_FILE) || legacy !== null;
|
|
3620
|
+
return { ok: false, why: configured ? "password-mismatch" : "no-password-file", hashBytes: 0 };
|
|
3621
|
+
}
|
|
3622
|
+
var SESSION_TTL_MS2 = 24 * 60 * 60 * 1e3;
|
|
3623
|
+
var TOKEN_PREFIX = "v1.";
|
|
3624
|
+
var SECRET_BYTES = 32;
|
|
3625
|
+
var NONCE_BYTES = 16;
|
|
3626
|
+
var cachedSecret = null;
|
|
3627
|
+
function getSecret() {
|
|
3628
|
+
if (cachedSecret) return cachedSecret;
|
|
3629
|
+
try {
|
|
3630
|
+
const hex2 = readFileSync6(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
|
|
3631
|
+
if (hex2.length === SECRET_BYTES * 2) {
|
|
3632
|
+
cachedSecret = Buffer.from(hex2, "hex");
|
|
3633
|
+
return cachedSecret;
|
|
3634
|
+
}
|
|
3635
|
+
} catch {
|
|
3636
|
+
}
|
|
3637
|
+
const fresh = randomBytes2(SECRET_BYTES).toString("hex");
|
|
3638
|
+
try {
|
|
3639
|
+
mkdirSync3(dirname2(REMOTE_SESSION_SECRET_FILE), { recursive: true, mode: 448 });
|
|
3640
|
+
writeFileSync2(REMOTE_SESSION_SECRET_FILE, fresh, { mode: 384, flag: "wx" });
|
|
3641
|
+
} catch {
|
|
3642
|
+
}
|
|
3643
|
+
const hex = readFileSync6(REMOTE_SESSION_SECRET_FILE, "utf-8").trim();
|
|
3644
|
+
cachedSecret = Buffer.from(hex, "hex");
|
|
3645
|
+
return cachedSecret;
|
|
3646
|
+
}
|
|
3647
|
+
function base64urlEncode(buf) {
|
|
3648
|
+
return buf.toString("base64").replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
|
|
3649
|
+
}
|
|
3650
|
+
function base64urlDecode(s) {
|
|
3651
|
+
const padded = s.replace(/-/g, "+").replace(/_/g, "/") + "=".repeat((4 - s.length % 4) % 4);
|
|
3652
|
+
return Buffer.from(padded, "base64");
|
|
3653
|
+
}
|
|
3654
|
+
function signPayload(payloadJson, secret) {
|
|
3655
|
+
return base64urlEncode(createHmac("sha256", secret).update(payloadJson).digest());
|
|
3656
|
+
}
|
|
3657
|
+
function createRemoteSession(opts = {}) {
|
|
3658
|
+
const payload = { c: opts.now ?? Date.now(), n: randomBytes2(NONCE_BYTES).toString("hex") };
|
|
3659
|
+
const payloadJson = JSON.stringify(payload);
|
|
3660
|
+
const payloadB64 = base64urlEncode(Buffer.from(payloadJson, "utf-8"));
|
|
3661
|
+
const sig = signPayload(payloadJson, getSecret());
|
|
3662
|
+
return `${TOKEN_PREFIX}${payloadB64}.${sig}`;
|
|
3663
|
+
}
|
|
3664
|
+
function parseToken(token) {
|
|
3665
|
+
if (!token.startsWith(TOKEN_PREFIX)) return null;
|
|
3666
|
+
const rest = token.slice(TOKEN_PREFIX.length);
|
|
3667
|
+
const dot = rest.indexOf(".");
|
|
3668
|
+
if (dot === -1) return null;
|
|
3669
|
+
const payloadB64 = rest.slice(0, dot);
|
|
3670
|
+
const sigB64 = rest.slice(dot + 1);
|
|
3671
|
+
if (!payloadB64 || !sigB64) return null;
|
|
3672
|
+
let payloadJson;
|
|
3923
3673
|
try {
|
|
3924
|
-
|
|
3925
|
-
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
3926
|
-
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
3674
|
+
payloadJson = base64urlDecode(payloadB64).toString("utf-8");
|
|
3927
3675
|
} catch {
|
|
3928
|
-
return
|
|
3676
|
+
return null;
|
|
3929
3677
|
}
|
|
3678
|
+
const expected = signPayload(payloadJson, getSecret());
|
|
3679
|
+
const expectedBuf = Buffer.from(expected, "utf-8");
|
|
3680
|
+
const provBuf = Buffer.from(sigB64, "utf-8");
|
|
3681
|
+
const signatureValid = expectedBuf.length === provBuf.length && timingSafeEqual2(expectedBuf, provBuf);
|
|
3682
|
+
let payload;
|
|
3683
|
+
try {
|
|
3684
|
+
payload = JSON.parse(payloadJson);
|
|
3685
|
+
} catch {
|
|
3686
|
+
return null;
|
|
3687
|
+
}
|
|
3688
|
+
if (!payload || typeof payload !== "object" || typeof payload.c !== "number" || typeof payload.n !== "string") {
|
|
3689
|
+
return null;
|
|
3690
|
+
}
|
|
3691
|
+
return { payload, signatureValid };
|
|
3930
3692
|
}
|
|
3931
|
-
|
|
3932
|
-
|
|
3933
|
-
|
|
3934
|
-
if (
|
|
3935
|
-
|
|
3936
|
-
`[operator-domains] loaded ${operatorDomains.size} operator domain(s): ${[...operatorDomains].join(", ")}`
|
|
3937
|
-
);
|
|
3693
|
+
function validateRemoteSession(token, now = Date.now()) {
|
|
3694
|
+
if (!token) return false;
|
|
3695
|
+
const parsed = parseToken(token);
|
|
3696
|
+
if (!parsed || !parsed.signatureValid) return false;
|
|
3697
|
+
return now - parsed.payload.c <= SESSION_TTL_MS2;
|
|
3938
3698
|
}
|
|
3939
|
-
|
|
3940
|
-
|
|
3941
|
-
|
|
3942
|
-
|
|
3943
|
-
|
|
3944
|
-
});
|
|
3945
|
-
function getOperatorDomains() {
|
|
3946
|
-
return operatorDomains;
|
|
3699
|
+
function describeRemoteSession(token, now = Date.now()) {
|
|
3700
|
+
if (!token) return { present: false, expired: false };
|
|
3701
|
+
const parsed = parseToken(token);
|
|
3702
|
+
if (!parsed || !parsed.signatureValid) return { present: false, expired: false };
|
|
3703
|
+
return { present: true, expired: now - parsed.payload.c > SESSION_TTL_MS2 };
|
|
3947
3704
|
}
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
|
|
3951
|
-
|
|
3952
|
-
|
|
3953
|
-
// ../lib/brand-templating/src/index.ts
|
|
3954
|
-
import { join as join2 } from "path";
|
|
3955
|
-
import { existsSync as existsSync4, readFileSync as readFileSync6 } from "fs";
|
|
3956
|
-
var PLACEHOLDER = "{{productName}}";
|
|
3957
|
-
var cachedProductName = null;
|
|
3958
|
-
function brandJsonPath() {
|
|
3959
|
-
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT;
|
|
3960
|
-
if (!platformRoot2) {
|
|
3961
|
-
throw new Error(
|
|
3962
|
-
"[skill-loader] MAXY_PLATFORM_ROOT not set \u2014 cannot resolve brand.json"
|
|
3963
|
-
);
|
|
3964
|
-
}
|
|
3965
|
-
return join2(platformRoot2, "config", "brand.json");
|
|
3705
|
+
function normalizeIp(ip) {
|
|
3706
|
+
if (!ip) return void 0;
|
|
3707
|
+
const trimmed = ip.trim();
|
|
3708
|
+
if (trimmed.startsWith("::ffff:")) return trimmed.slice(7);
|
|
3709
|
+
return trimmed;
|
|
3966
3710
|
}
|
|
3967
|
-
function
|
|
3968
|
-
|
|
3969
|
-
|
|
3970
|
-
|
|
3971
|
-
|
|
3711
|
+
function isLoopback(ip) {
|
|
3712
|
+
return ip === "127.0.0.1" || ip.startsWith("127.") || ip === "::1";
|
|
3713
|
+
}
|
|
3714
|
+
function isPrivateIp(ip) {
|
|
3715
|
+
const parts = ip.split(".");
|
|
3716
|
+
if (parts.length !== 4) return false;
|
|
3717
|
+
const a = parseInt(parts[0], 10);
|
|
3718
|
+
const b = parseInt(parts[1], 10);
|
|
3719
|
+
if (Number.isNaN(a) || Number.isNaN(b)) return false;
|
|
3720
|
+
if (a === 10) return true;
|
|
3721
|
+
if (a === 172 && b >= 16 && b <= 31) return true;
|
|
3722
|
+
if (a === 192 && b === 168) return true;
|
|
3723
|
+
if (a === 169 && b === 254) return true;
|
|
3724
|
+
return false;
|
|
3725
|
+
}
|
|
3726
|
+
function classifyNormalizedIp(ip) {
|
|
3727
|
+
if (isLoopback(ip) || isPrivateIp(ip)) return "lan";
|
|
3728
|
+
return "external";
|
|
3729
|
+
}
|
|
3730
|
+
function resolveClientIp(remoteAddress, xForwardedFor) {
|
|
3731
|
+
const remote = normalizeIp(remoteAddress);
|
|
3732
|
+
if (!remote) return { ip: "", kind: "unknown" };
|
|
3733
|
+
if (isLoopback(remote) && xForwardedFor) {
|
|
3734
|
+
const firstIp = normalizeIp(xForwardedFor.split(",")[0]?.trim());
|
|
3735
|
+
if (firstIp) {
|
|
3736
|
+
return { ip: firstIp, kind: classifyNormalizedIp(firstIp) };
|
|
3737
|
+
}
|
|
3972
3738
|
}
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
3976
|
-
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
3739
|
+
return { ip: remote, kind: classifyNormalizedIp(remote) };
|
|
3740
|
+
}
|
|
3741
|
+
var MAX_ATTEMPTS = 5;
|
|
3742
|
+
var LOCKOUT_MS = 15 * 60 * 1e3;
|
|
3743
|
+
var LOCAL_BUCKET = "__local__";
|
|
3744
|
+
var rateLimitMap = /* @__PURE__ */ new Map();
|
|
3745
|
+
function bucketKey(client) {
|
|
3746
|
+
return client.kind === "external" ? client.ip : LOCAL_BUCKET;
|
|
3747
|
+
}
|
|
3748
|
+
function checkRateLimit(client) {
|
|
3749
|
+
const key = bucketKey(client);
|
|
3750
|
+
const rec = rateLimitMap.get(key);
|
|
3751
|
+
if (!rec) return null;
|
|
3752
|
+
if (rec.lockedUntil > Date.now()) {
|
|
3753
|
+
const remaining = Math.ceil((rec.lockedUntil - Date.now()) / 1e3);
|
|
3754
|
+
return `Too many attempts. Try again in ${remaining}s`;
|
|
3980
3755
|
}
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
throw new Error(
|
|
3984
|
-
`[skill-loader] brand.json at ${path} has missing or empty productName`
|
|
3985
|
-
);
|
|
3756
|
+
if (rec.lockedUntil > 0) {
|
|
3757
|
+
rateLimitMap.delete(key);
|
|
3986
3758
|
}
|
|
3987
|
-
|
|
3988
|
-
return value;
|
|
3759
|
+
return null;
|
|
3989
3760
|
}
|
|
3990
|
-
function
|
|
3991
|
-
|
|
3992
|
-
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
`[skill-loader] ERROR: brand.json missing \u2014 cannot resolve productName for skill ${sourcePath}`
|
|
3998
|
-
);
|
|
3999
|
-
throw err;
|
|
3761
|
+
function recordFailedAttempt(client) {
|
|
3762
|
+
const key = bucketKey(client);
|
|
3763
|
+
const rec = rateLimitMap.get(key) ?? { count: 0, lockedUntil: 0 };
|
|
3764
|
+
rec.count++;
|
|
3765
|
+
if (rec.count >= MAX_ATTEMPTS) {
|
|
3766
|
+
rec.lockedUntil = Date.now() + LOCKOUT_MS;
|
|
3767
|
+
rec.count = 0;
|
|
4000
3768
|
}
|
|
4001
|
-
|
|
4002
|
-
const substituted = content.split(PLACEHOLDER).join(productName);
|
|
4003
|
-
console.log(
|
|
4004
|
-
`[skill-loader] brand-substituted productName=${productName} skill=${sourcePath} occurrences=${occurrences}`
|
|
4005
|
-
);
|
|
4006
|
-
return substituted;
|
|
3769
|
+
rateLimitMap.set(key, rec);
|
|
4007
3770
|
}
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
var PLATFORM_ROOT2 = process.env.MAXY_PLATFORM_ROOT ?? resolve4(process.cwd(), "..");
|
|
4011
|
-
var ACCOUNTS_DIR = resolve4(PLATFORM_ROOT2, "..", "data/accounts");
|
|
4012
|
-
if (!existsSync5(PLATFORM_ROOT2)) {
|
|
4013
|
-
throw new Error(
|
|
4014
|
-
`PLATFORM_ROOT does not exist: ${PLATFORM_ROOT2}
|
|
4015
|
-
Set the MAXY_PLATFORM_ROOT environment variable to the absolute path of the platform directory.`
|
|
4016
|
-
);
|
|
3771
|
+
function clearRateLimit(client) {
|
|
3772
|
+
rateLimitMap.delete(bucketKey(client));
|
|
4017
3773
|
}
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
if (!existsSync5(configPath)) {
|
|
4029
|
-
if (!_stubDirsLoggedThisBoot.has(entry.name)) {
|
|
4030
|
-
console.error(
|
|
4031
|
-
`[platform] accounts-state STUB-DIR id=${entry.name} \u2014 onboarding leak; remove or repair`
|
|
4032
|
-
);
|
|
4033
|
-
_stubDirsLoggedThisBoot.add(entry.name);
|
|
3774
|
+
registerLoop({
|
|
3775
|
+
name: "rate-limit-cleanup",
|
|
3776
|
+
intervalMs: 15 * 60 * 1e3,
|
|
3777
|
+
run: () => {
|
|
3778
|
+
const now = Date.now();
|
|
3779
|
+
let purged = 0;
|
|
3780
|
+
for (const [key, rec] of rateLimitMap) {
|
|
3781
|
+
if (rec.lockedUntil > 0 && rec.lockedUntil <= now) {
|
|
3782
|
+
rateLimitMap.delete(key);
|
|
3783
|
+
purged += 1;
|
|
4034
3784
|
}
|
|
4035
|
-
continue;
|
|
4036
3785
|
}
|
|
4037
|
-
|
|
4038
|
-
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4044
|
-
|
|
3786
|
+
return `purged ${purged}, ${rateLimitMap.size} tracked`;
|
|
3787
|
+
}
|
|
3788
|
+
});
|
|
3789
|
+
function renderLoginPage(opts) {
|
|
3790
|
+
const error = opts?.error ?? "";
|
|
3791
|
+
const lockout = opts?.lockoutSeconds ?? 0;
|
|
3792
|
+
const redirect = opts?.redirect ?? "/";
|
|
3793
|
+
const mode = opts?.mode ?? "login";
|
|
3794
|
+
const changeError = opts?.changeError ?? "";
|
|
3795
|
+
const success = opts?.success ?? "";
|
|
3796
|
+
const setupError = opts?.setupError ?? "";
|
|
3797
|
+
const primaryColor = opts?.primaryColor ?? "#7C8C72";
|
|
3798
|
+
const primaryHoverColor = opts?.primaryHoverColor ?? "#6A7A62";
|
|
3799
|
+
const primarySubtle = opts?.primarySubtle ?? "rgba(124,140,114,0.08)";
|
|
3800
|
+
const productName = opts?.productName ?? "Maxy";
|
|
3801
|
+
const logoPath = opts?.logoPath ?? "/brand/maxy-monochrome.png";
|
|
3802
|
+
const faviconPath = opts?.faviconPath ?? "/favicon.ico";
|
|
3803
|
+
const displayFont = opts?.displayFont ?? "'Newsreader', Georgia, serif";
|
|
3804
|
+
const bodyFont = opts?.bodyFont ?? "'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif";
|
|
3805
|
+
const logoContainsName = opts?.logoContainsName ?? false;
|
|
3806
|
+
const tagline = opts?.tagline ?? "";
|
|
3807
|
+
const origin = opts?.origin ?? "";
|
|
3808
|
+
const ogCardPath = opts?.ogCardPath ?? "";
|
|
3809
|
+
const manifestPath = opts?.manifestPath ?? "";
|
|
3810
|
+
const appleTouchIconPath = opts?.appleTouchIconPath ?? "";
|
|
3811
|
+
const pwaThemeColor = opts?.themeColor ?? "";
|
|
3812
|
+
const ogBlock = origin && ogCardPath ? [
|
|
3813
|
+
`<meta property="og:title" content="Sign in \u2014 ${escapeHtml(productName)}">`,
|
|
3814
|
+
`<meta property="og:description" content="${escapeHtml(tagline)}">`,
|
|
3815
|
+
`<meta property="og:type" content="website">`,
|
|
3816
|
+
`<meta property="og:url" content="${escapeHtml(origin)}">`,
|
|
3817
|
+
`<meta property="og:image" content="${escapeHtml(origin + ogCardPath)}">`,
|
|
3818
|
+
`<meta property="og:image:width" content="1200">`,
|
|
3819
|
+
`<meta property="og:image:height" content="630">`,
|
|
3820
|
+
`<meta name="twitter:card" content="summary_large_image">`,
|
|
3821
|
+
`<meta name="twitter:title" content="Sign in \u2014 ${escapeHtml(productName)}">`,
|
|
3822
|
+
`<meta name="twitter:description" content="${escapeHtml(tagline)}">`,
|
|
3823
|
+
`<meta name="twitter:image" content="${escapeHtml(origin + ogCardPath)}">`
|
|
3824
|
+
].join("\n ") : "";
|
|
3825
|
+
const pwaInstallBlock = manifestPath && appleTouchIconPath ? [
|
|
3826
|
+
`<link rel="manifest" href="${escapeHtml(manifestPath)}">`,
|
|
3827
|
+
`<link rel="apple-touch-icon" href="${escapeHtml(appleTouchIconPath)}">`,
|
|
3828
|
+
`<meta name="mobile-web-app-capable" content="yes">`,
|
|
3829
|
+
`<meta name="apple-mobile-web-app-capable" content="yes">`,
|
|
3830
|
+
pwaThemeColor ? `<meta name="theme-color" content="${escapeHtml(pwaThemeColor)}">` : ""
|
|
3831
|
+
].filter(Boolean).join("\n ") : "";
|
|
3832
|
+
const errorHtml = error ? `<p class="msg msg--error">${escapeHtml(error)}</p>` : "";
|
|
3833
|
+
const changeErrorHtml = changeError ? `<p class="msg msg--error">${escapeHtml(changeError)}</p>` : "";
|
|
3834
|
+
const successHtml = success ? `<p class="msg msg--success">${escapeHtml(success)}</p>` : "";
|
|
3835
|
+
const lockoutHtml = lockout > 0 ? `<p class="msg msg--error">Too many attempts. Try again in <span id="countdown">${lockout}</span>s</p>
|
|
3836
|
+
<script>
|
|
3837
|
+
(function() {
|
|
3838
|
+
var s = ${lockout};
|
|
3839
|
+
var el = document.getElementById('countdown');
|
|
3840
|
+
var iv = setInterval(function() { s--; el.textContent = s; if (s <= 0) { clearInterval(iv); location.reload(); } }, 1000);
|
|
3841
|
+
})();
|
|
3842
|
+
</script>` : "";
|
|
3843
|
+
const setupErrorHtml = setupError ? `<p class="msg msg--error">${escapeHtml(setupError)}</p>` : "";
|
|
3844
|
+
const formDisabled = lockout > 0 ? "disabled" : "";
|
|
3845
|
+
const loginDisplay = mode === "login" ? "block" : "none";
|
|
3846
|
+
const changeDisplay = mode === "change" ? "block" : "none";
|
|
3847
|
+
const setupDisplay = mode === "setup" ? "block" : "none";
|
|
3848
|
+
const successDisplay = mode === "success" ? "block" : "none";
|
|
3849
|
+
const subtitleText = mode === "setup" ? "Set your remote password" : "Remote access";
|
|
3850
|
+
const displayFontName = displayFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3851
|
+
const bodyFontName = bodyFont.match(/^'([^']+)'/)?.[1] ?? "";
|
|
3852
|
+
const googleFontsLink = displayFontName || bodyFontName ? [
|
|
3853
|
+
'<link rel="preconnect" href="https://fonts.googleapis.com">',
|
|
3854
|
+
'<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>',
|
|
3855
|
+
'<link href="https://fonts.googleapis.com/css2?' + [
|
|
3856
|
+
displayFontName ? `family=${displayFontName.replace(/ /g, "+")}:wght@300;400` : "",
|
|
3857
|
+
bodyFontName ? `family=${bodyFontName.replace(/ /g, "+")}:wght@400;500` : ""
|
|
3858
|
+
].filter(Boolean).join("&") + '&display=swap" rel="stylesheet">'
|
|
3859
|
+
].join("\n ") : "";
|
|
3860
|
+
return `<!DOCTYPE html>
|
|
3861
|
+
<html lang="en">
|
|
3862
|
+
<head>
|
|
3863
|
+
<meta charset="utf-8">
|
|
3864
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
3865
|
+
<title>Sign in \u2014 ${escapeHtml(productName)}</title>
|
|
3866
|
+
<link rel="icon" href="${escapeHtml(faviconPath)}">
|
|
3867
|
+
${pwaInstallBlock}
|
|
3868
|
+
${ogBlock}
|
|
3869
|
+
${googleFontsLink}
|
|
3870
|
+
<style>
|
|
3871
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
3872
|
+
body {
|
|
3873
|
+
font-family: ${bodyFont};
|
|
3874
|
+
background: #F6F5F2;
|
|
3875
|
+
color: #2A2D38;
|
|
3876
|
+
min-height: 100vh;
|
|
3877
|
+
display: flex;
|
|
3878
|
+
align-items: center;
|
|
3879
|
+
justify-content: center;
|
|
3880
|
+
}
|
|
3881
|
+
.page {
|
|
3882
|
+
display: flex;
|
|
3883
|
+
flex-direction: column;
|
|
3884
|
+
align-items: center;
|
|
3885
|
+
gap: 24px;
|
|
3886
|
+
max-width: 420px;
|
|
3887
|
+
width: 100%;
|
|
3888
|
+
padding: 40px 20px;
|
|
4045
3889
|
}
|
|
4046
|
-
|
|
4047
|
-
|
|
4048
|
-
|
|
4049
|
-
|
|
4050
|
-
|
|
4051
|
-
return listValidAccounts().some((a) => a.accountId === id);
|
|
4052
|
-
}
|
|
4053
|
-
function resolveAccount() {
|
|
4054
|
-
if (!existsSync5(ACCOUNTS_DIR)) return null;
|
|
4055
|
-
let usersJsonUserId = null;
|
|
4056
|
-
if (existsSync5(USERS_FILE)) {
|
|
4057
|
-
try {
|
|
4058
|
-
const raw2 = readFileSync7(USERS_FILE, "utf-8").trim();
|
|
4059
|
-
if (raw2) {
|
|
4060
|
-
const users = JSON.parse(raw2);
|
|
4061
|
-
if (users.length > 0) {
|
|
4062
|
-
usersJsonUserId = users[0].userId;
|
|
4063
|
-
}
|
|
4064
|
-
}
|
|
4065
|
-
} catch {
|
|
3890
|
+
.logo-img {
|
|
3891
|
+
width: 110px;
|
|
3892
|
+
height: 110px;
|
|
3893
|
+
margin: -11px;
|
|
3894
|
+
object-fit: contain;
|
|
4066
3895
|
}
|
|
4067
|
-
|
|
4068
|
-
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
|
|
4075
|
-
let config;
|
|
4076
|
-
try {
|
|
4077
|
-
config = JSON.parse(raw2);
|
|
4078
|
-
} catch {
|
|
4079
|
-
console.error(`[maxy] account.json is corrupt at ${configPath} \u2014 skipping`);
|
|
4080
|
-
continue;
|
|
3896
|
+
.title {
|
|
3897
|
+
font-family: ${displayFont};
|
|
3898
|
+
font-weight: 300;
|
|
3899
|
+
font-size: 28px;
|
|
3900
|
+
color: #2A2D38;
|
|
3901
|
+
text-align: center;
|
|
3902
|
+
letter-spacing: -0.01em;
|
|
3903
|
+
line-height: 1.3;
|
|
4081
3904
|
}
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
3905
|
+
.subtitle {
|
|
3906
|
+
font-size: 15px;
|
|
3907
|
+
color: #4A4D58;
|
|
3908
|
+
text-align: center;
|
|
3909
|
+
margin-top: -8px;
|
|
4086
3910
|
}
|
|
4087
|
-
|
|
4088
|
-
|
|
4089
|
-
|
|
4090
|
-
config
|
|
4091
|
-
};
|
|
4092
|
-
if (usersJsonUserId && config.admins?.some((a) => a.userId === usersJsonUserId)) {
|
|
4093
|
-
return result;
|
|
3911
|
+
.form-wrap {
|
|
3912
|
+
width: 100%;
|
|
3913
|
+
max-width: 300px;
|
|
4094
3914
|
}
|
|
4095
|
-
|
|
4096
|
-
|
|
3915
|
+
.field { margin-bottom: 12px; }
|
|
3916
|
+
.field-label {
|
|
3917
|
+
display: block;
|
|
3918
|
+
font-size: 13px;
|
|
3919
|
+
font-weight: 500;
|
|
3920
|
+
color: #4A4D58;
|
|
3921
|
+
margin-bottom: 6px;
|
|
4097
3922
|
}
|
|
4098
|
-
|
|
4099
|
-
|
|
4100
|
-
|
|
4101
|
-
|
|
4102
|
-
|
|
4103
|
-
|
|
4104
|
-
|
|
4105
|
-
|
|
4106
|
-
|
|
4107
|
-
|
|
4108
|
-
|
|
4109
|
-
|
|
4110
|
-
|
|
4111
|
-
|
|
4112
|
-
|
|
4113
|
-
|
|
4114
|
-
|
|
4115
|
-
|
|
4116
|
-
|
|
4117
|
-
|
|
4118
|
-
|
|
4119
|
-
|
|
4120
|
-
|
|
4121
|
-
|
|
4122
|
-
|
|
4123
|
-
|
|
4124
|
-
|
|
4125
|
-
|
|
4126
|
-
|
|
4127
|
-
|
|
4128
|
-
|
|
4129
|
-
}
|
|
4130
|
-
|
|
4131
|
-
|
|
4132
|
-
|
|
4133
|
-
|
|
4134
|
-
|
|
4135
|
-
|
|
4136
|
-
|
|
4137
|
-
|
|
4138
|
-
|
|
4139
|
-
|
|
4140
|
-
|
|
4141
|
-
|
|
4142
|
-
|
|
4143
|
-
|
|
4144
|
-
|
|
4145
|
-
|
|
4146
|
-
|
|
4147
|
-
|
|
4148
|
-
|
|
4149
|
-
|
|
4150
|
-
|
|
4151
|
-
|
|
4152
|
-
|
|
4153
|
-
|
|
4154
|
-
|
|
4155
|
-
|
|
4156
|
-
|
|
4157
|
-
|
|
4158
|
-
|
|
4159
|
-
|
|
4160
|
-
|
|
4161
|
-
|
|
4162
|
-
|
|
4163
|
-
|
|
4164
|
-
|
|
4165
|
-
|
|
4166
|
-
|
|
4167
|
-
|
|
4168
|
-
|
|
4169
|
-
|
|
4170
|
-
|
|
4171
|
-
|
|
4172
|
-
|
|
4173
|
-
|
|
4174
|
-
|
|
3923
|
+
.field-input {
|
|
3924
|
+
width: 100%;
|
|
3925
|
+
padding: 10px 12px;
|
|
3926
|
+
border: 1px solid #E4E3DE;
|
|
3927
|
+
border-radius: 8px;
|
|
3928
|
+
font-size: 15px;
|
|
3929
|
+
font-family: inherit;
|
|
3930
|
+
outline: none;
|
|
3931
|
+
background: #fff;
|
|
3932
|
+
transition: border-color 0.15s, box-shadow 0.15s;
|
|
3933
|
+
}
|
|
3934
|
+
.field-input:focus {
|
|
3935
|
+
border-color: ${primaryColor};
|
|
3936
|
+
box-shadow: 0 0 0 3px ${primarySubtle};
|
|
3937
|
+
}
|
|
3938
|
+
.options {
|
|
3939
|
+
display: flex;
|
|
3940
|
+
align-items: center;
|
|
3941
|
+
justify-content: space-between;
|
|
3942
|
+
gap: 12px;
|
|
3943
|
+
margin-top: 8px;
|
|
3944
|
+
}
|
|
3945
|
+
|
|
3946
|
+
/* Checkbox \u2014 matches Maxy shared checkbox (asterisk-in-square) */
|
|
3947
|
+
.check {
|
|
3948
|
+
position: relative;
|
|
3949
|
+
display: flex;
|
|
3950
|
+
align-items: center;
|
|
3951
|
+
gap: 8px;
|
|
3952
|
+
cursor: pointer;
|
|
3953
|
+
user-select: none;
|
|
3954
|
+
}
|
|
3955
|
+
.check input { position: absolute; opacity: 0; width: 0; height: 0; pointer-events: none; }
|
|
3956
|
+
.check-box {
|
|
3957
|
+
width: 14px;
|
|
3958
|
+
height: 14px;
|
|
3959
|
+
border: 1px solid #E4E3DE;
|
|
3960
|
+
border-radius: 2px;
|
|
3961
|
+
display: flex;
|
|
3962
|
+
align-items: center;
|
|
3963
|
+
justify-content: center;
|
|
3964
|
+
font-size: 10px;
|
|
3965
|
+
color: transparent;
|
|
3966
|
+
transition: border-color 0.15s, color 0.15s;
|
|
3967
|
+
flex-shrink: 0;
|
|
3968
|
+
}
|
|
3969
|
+
.check input:checked + .check-box {
|
|
3970
|
+
border-color: ${primaryColor};
|
|
3971
|
+
color: ${primaryColor};
|
|
3972
|
+
}
|
|
3973
|
+
.check-label {
|
|
3974
|
+
font-size: 13px;
|
|
3975
|
+
color: #4A4D58;
|
|
3976
|
+
}
|
|
3977
|
+
|
|
3978
|
+
/* Ghost button \u2014 matches Maxy ghost variant */
|
|
3979
|
+
.ghost {
|
|
3980
|
+
background: none;
|
|
3981
|
+
border: none;
|
|
3982
|
+
font-family: inherit;
|
|
3983
|
+
font-size: 13px;
|
|
3984
|
+
color: #4A4D58;
|
|
3985
|
+
cursor: pointer;
|
|
3986
|
+
padding: 4px 0;
|
|
3987
|
+
transition: color 0.15s;
|
|
3988
|
+
}
|
|
3989
|
+
.ghost:hover { color: #2A2D38; }
|
|
3990
|
+
|
|
3991
|
+
/* Primary button */
|
|
3992
|
+
.btn {
|
|
3993
|
+
width: 100%;
|
|
3994
|
+
padding: 12px;
|
|
3995
|
+
margin-top: 16px;
|
|
3996
|
+
background: ${primaryColor};
|
|
3997
|
+
color: #fff;
|
|
3998
|
+
border: none;
|
|
3999
|
+
border-radius: 10px;
|
|
4000
|
+
font-size: 15px;
|
|
4001
|
+
font-weight: 500;
|
|
4002
|
+
font-family: inherit;
|
|
4003
|
+
cursor: pointer;
|
|
4004
|
+
transition: background 0.15s;
|
|
4175
4005
|
}
|
|
4176
|
-
|
|
4177
|
-
|
|
4178
|
-
|
|
4179
|
-
|
|
4180
|
-
|
|
4181
|
-
|
|
4006
|
+
.btn:hover:not(:disabled) { background: ${primaryHoverColor}; }
|
|
4007
|
+
.btn:disabled { opacity: 0.5; cursor: not-allowed; }
|
|
4008
|
+
|
|
4009
|
+
/* Messages */
|
|
4010
|
+
.msg {
|
|
4011
|
+
font-size: 13px;
|
|
4012
|
+
text-align: center;
|
|
4013
|
+
margin-top: 8px;
|
|
4182
4014
|
}
|
|
4183
|
-
|
|
4184
|
-
|
|
4185
|
-
|
|
4186
|
-
|
|
4015
|
+
.msg--error { color: #c44; }
|
|
4016
|
+
.msg--success { color: ${primaryColor}; }
|
|
4017
|
+
|
|
4018
|
+
/* Strength checklist (setup mode) */
|
|
4019
|
+
.strength-checklist { margin: 8px 0; }
|
|
4020
|
+
.strength-item {
|
|
4021
|
+
font-size: 13px;
|
|
4022
|
+
color: #4A4D58;
|
|
4023
|
+
padding: 2px 0;
|
|
4024
|
+
transition: color 0.15s;
|
|
4187
4025
|
}
|
|
4188
|
-
|
|
4189
|
-
|
|
4026
|
+
.strength-icon {
|
|
4027
|
+
display: inline-block;
|
|
4028
|
+
width: 16px;
|
|
4029
|
+
text-align: center;
|
|
4190
4030
|
}
|
|
4191
|
-
|
|
4192
|
-
|
|
4031
|
+
|
|
4032
|
+
@media (max-width: 440px) {
|
|
4033
|
+
.page { padding: 24px 16px; }
|
|
4193
4034
|
}
|
|
4194
|
-
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4198
|
-
|
|
4199
|
-
|
|
4035
|
+
</style>
|
|
4036
|
+
</head>
|
|
4037
|
+
<body>
|
|
4038
|
+
<div class="page">
|
|
4039
|
+
<img src="${escapeHtml(logoPath)}" alt="${escapeHtml(productName)}" class="logo-img">
|
|
4040
|
+
<h1 class="title"${logoContainsName ? ' style="display:none"' : ""}>${escapeHtml(productName)}</h1>
|
|
4041
|
+
<p class="subtitle">${escapeHtml(subtitleText)}</p>
|
|
4042
|
+
|
|
4043
|
+
${successHtml}
|
|
4044
|
+
|
|
4045
|
+
<!-- Login -->
|
|
4046
|
+
<div id="login-section" class="form-wrap" style="display:${loginDisplay}">
|
|
4047
|
+
<form method="POST" action="/__remote-auth/login">
|
|
4048
|
+
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
4049
|
+
<div class="field">
|
|
4050
|
+
<label class="field-label" for="password">Password</label>
|
|
4051
|
+
<input class="field-input" type="password" id="password" name="password" required autofocus ${formDisabled}>
|
|
4052
|
+
</div>
|
|
4053
|
+
<div class="options">
|
|
4054
|
+
<label class="check">
|
|
4055
|
+
<input type="checkbox" id="show-login-pw">
|
|
4056
|
+
<span class="check-box">\u2731</span>
|
|
4057
|
+
<span class="check-label">Show password</span>
|
|
4058
|
+
</label>
|
|
4059
|
+
<button type="button" class="ghost" onclick="toggleMode('change')">Change password</button>
|
|
4060
|
+
</div>
|
|
4061
|
+
<button type="submit" class="btn" ${formDisabled}>Sign in</button>
|
|
4062
|
+
</form>
|
|
4063
|
+
${errorHtml}
|
|
4064
|
+
${lockoutHtml}
|
|
4065
|
+
</div>
|
|
4066
|
+
|
|
4067
|
+
<!-- Change password -->
|
|
4068
|
+
<div id="change-section" class="form-wrap" style="display:${changeDisplay}">
|
|
4069
|
+
<form method="POST" action="/__remote-auth/change-password">
|
|
4070
|
+
<input type="hidden" name="redirect" value="${escapeHtml(redirect)}">
|
|
4071
|
+
<div class="field">
|
|
4072
|
+
<label class="field-label" for="current_password">Current password</label>
|
|
4073
|
+
<input class="field-input" type="password" id="current_password" name="current_password" required ${formDisabled}>
|
|
4074
|
+
</div>
|
|
4075
|
+
<div class="field">
|
|
4076
|
+
<label class="field-label" for="new_password">New password</label>
|
|
4077
|
+
<input class="field-input" type="password" id="new_password" name="new_password" required ${formDisabled}>
|
|
4078
|
+
</div>
|
|
4079
|
+
<div class="field">
|
|
4080
|
+
<label class="field-label" for="confirm_password">Confirm new password</label>
|
|
4081
|
+
<input class="field-input" type="password" id="confirm_password" name="confirm_password" required ${formDisabled}>
|
|
4082
|
+
</div>
|
|
4083
|
+
<div class="options">
|
|
4084
|
+
<label class="check">
|
|
4085
|
+
<input type="checkbox" id="show-change-pw">
|
|
4086
|
+
<span class="check-box">\u2731</span>
|
|
4087
|
+
<span class="check-label">Show passwords</span>
|
|
4088
|
+
</label>
|
|
4089
|
+
<button type="button" class="ghost" onclick="toggleMode('login')">Back to sign in</button>
|
|
4090
|
+
</div>
|
|
4091
|
+
<button type="submit" class="btn" ${formDisabled}>Change password</button>
|
|
4092
|
+
</form>
|
|
4093
|
+
${changeErrorHtml}
|
|
4094
|
+
</div>
|
|
4095
|
+
|
|
4096
|
+
<!-- Setup (initial password) -->
|
|
4097
|
+
<div id="setup-section" class="form-wrap" style="display:${setupDisplay}">
|
|
4098
|
+
<form method="POST" action="/__remote-auth/set-initial-password">
|
|
4099
|
+
<div class="field">
|
|
4100
|
+
<label class="field-label" for="setup_password">Password</label>
|
|
4101
|
+
<input class="field-input" type="password" id="setup_password" name="password" required autofocus>
|
|
4102
|
+
</div>
|
|
4103
|
+
<div class="field">
|
|
4104
|
+
<label class="field-label" for="setup_confirm">Confirm password</label>
|
|
4105
|
+
<input class="field-input" type="password" id="setup_confirm" name="confirm_password" required>
|
|
4106
|
+
</div>
|
|
4107
|
+
<div id="strength-checklist" class="strength-checklist">
|
|
4108
|
+
<div class="strength-item" id="req-length"><span class="strength-icon">\u25CB</span> At least 8 characters</div>
|
|
4109
|
+
<div class="strength-item" id="req-number"><span class="strength-icon">\u25CB</span> Contains a number</div>
|
|
4110
|
+
<div class="strength-item" id="req-special"><span class="strength-icon">\u25CB</span> Contains a special character</div>
|
|
4111
|
+
<div class="strength-item" id="req-spaces"><span class="strength-icon">\u25CB</span> No spaces</div>
|
|
4112
|
+
</div>
|
|
4113
|
+
<div class="options">
|
|
4114
|
+
<label class="check">
|
|
4115
|
+
<input type="checkbox" id="show-setup-pw">
|
|
4116
|
+
<span class="check-box">\u2731</span>
|
|
4117
|
+
<span class="check-label">Show passwords</span>
|
|
4118
|
+
</label>
|
|
4119
|
+
</div>
|
|
4120
|
+
<button type="submit" class="btn" id="setup-submit" disabled>Set password</button>
|
|
4121
|
+
</form>
|
|
4122
|
+
${setupErrorHtml}
|
|
4123
|
+
</div>
|
|
4124
|
+
|
|
4125
|
+
<!-- Success (after initial password set) -->
|
|
4126
|
+
<div id="success-section" class="form-wrap" style="display:${successDisplay}">
|
|
4127
|
+
<p class="msg msg--success" style="font-size:15px;margin-bottom:4px;">\u2713 Password set</p>
|
|
4128
|
+
<p style="font-size:14px;color:#4A4D58;text-align:center;">You can close this tab and return to the onboarding tab.</p>
|
|
4129
|
+
<a href="/" style="display:block;text-align:center;margin-top:16px;font-size:13px;color:#4A4D58;text-decoration:none;" onmouseover="this.style.color='#2A2D38'" onmouseout="this.style.color='#4A4D58'">Sign in \u2192</a>
|
|
4130
|
+
<script>
|
|
4131
|
+
(function() {
|
|
4132
|
+
try {
|
|
4133
|
+
var ch = new BroadcastChannel('platform-onboarding');
|
|
4134
|
+
setTimeout(function() {
|
|
4135
|
+
ch.postMessage({ type: 'remote-password-set' });
|
|
4136
|
+
ch.close();
|
|
4137
|
+
}, 2500);
|
|
4138
|
+
} catch(e) {}
|
|
4139
|
+
})();
|
|
4140
|
+
</script>
|
|
4141
|
+
</div>
|
|
4142
|
+
</div>
|
|
4143
|
+
|
|
4144
|
+
<script>
|
|
4145
|
+
/* Toggle between login and change-password modes */
|
|
4146
|
+
function toggleMode(mode) {
|
|
4147
|
+
document.getElementById('login-section').style.display = mode === 'login' ? 'block' : 'none';
|
|
4148
|
+
document.getElementById('change-section').style.display = mode === 'change' ? 'block' : 'none';
|
|
4149
|
+
/* Focus first input in the active section */
|
|
4150
|
+
var id = mode === 'login' ? 'password' : 'current_password';
|
|
4151
|
+
var el = document.getElementById(id);
|
|
4152
|
+
if (el) el.focus();
|
|
4200
4153
|
}
|
|
4201
|
-
|
|
4202
|
-
|
|
4203
|
-
|
|
4204
|
-
|
|
4205
|
-
|
|
4206
|
-
|
|
4207
|
-
|
|
4208
|
-
|
|
4209
|
-
|
|
4210
|
-
|
|
4211
|
-
|
|
4212
|
-
|
|
4213
|
-
knowledge = readFileSync7(summaryPath, "utf-8");
|
|
4214
|
-
} else {
|
|
4215
|
-
console.warn(`[agent-config] ${agentName}: KNOWLEDGE-SUMMARY.md is stale (KNOWLEDGE.md is newer) \u2014 using full knowledge`);
|
|
4216
|
-
knowledge = readFileSync7(knowledgePath, "utf-8");
|
|
4154
|
+
|
|
4155
|
+
/* Show/hide password toggles */
|
|
4156
|
+
function bindShowHide(checkboxId, inputIds) {
|
|
4157
|
+
var cb = document.getElementById(checkboxId);
|
|
4158
|
+
if (!cb) return;
|
|
4159
|
+
cb.addEventListener('change', function() {
|
|
4160
|
+
var type = cb.checked ? 'text' : 'password';
|
|
4161
|
+
inputIds.forEach(function(id) {
|
|
4162
|
+
var el = document.getElementById(id);
|
|
4163
|
+
if (el) el.type = type;
|
|
4164
|
+
});
|
|
4165
|
+
});
|
|
4217
4166
|
}
|
|
4218
|
-
|
|
4219
|
-
|
|
4220
|
-
|
|
4221
|
-
|
|
4222
|
-
|
|
4223
|
-
|
|
4224
|
-
|
|
4225
|
-
|
|
4226
|
-
|
|
4227
|
-
|
|
4228
|
-
|
|
4229
|
-
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4167
|
+
bindShowHide('show-login-pw', ['password']);
|
|
4168
|
+
bindShowHide('show-change-pw', ['current_password', 'new_password', 'confirm_password']);
|
|
4169
|
+
bindShowHide('show-setup-pw', ['setup_password', 'setup_confirm']);
|
|
4170
|
+
|
|
4171
|
+
/* Explicit Enter-to-submit for the login password field */
|
|
4172
|
+
var loginPw = document.getElementById('password');
|
|
4173
|
+
if (loginPw) {
|
|
4174
|
+
loginPw.addEventListener('keydown', function(e) {
|
|
4175
|
+
if (e.key === 'Enter') {
|
|
4176
|
+
e.preventDefault();
|
|
4177
|
+
this.closest('form').requestSubmit();
|
|
4178
|
+
}
|
|
4179
|
+
});
|
|
4180
|
+
}
|
|
4181
|
+
|
|
4182
|
+
/* Live password strength validation (setup mode) */
|
|
4183
|
+
(function() {
|
|
4184
|
+
var pw = document.getElementById('setup_password');
|
|
4185
|
+
var confirm = document.getElementById('setup_confirm');
|
|
4186
|
+
var btn = document.getElementById('setup-submit');
|
|
4187
|
+
if (!pw || !confirm || !btn) return;
|
|
4188
|
+
|
|
4189
|
+
var rules = [
|
|
4190
|
+
{ id: 'req-length', test: function(p) { return p.length >= 8; } },
|
|
4191
|
+
{ id: 'req-number', test: function(p) { return /\\d/.test(p); } },
|
|
4192
|
+
{ id: 'req-special', test: function(p) { return /[^A-Za-z0-9]/.test(p); } },
|
|
4193
|
+
{ id: 'req-spaces', test: function(p) { return p.length > 0 && !/\\s/.test(p); } }
|
|
4194
|
+
];
|
|
4195
|
+
|
|
4196
|
+
function check() {
|
|
4197
|
+
var p = pw.value;
|
|
4198
|
+
var allMet = true;
|
|
4199
|
+
rules.forEach(function(r) {
|
|
4200
|
+
var el = document.getElementById(r.id);
|
|
4201
|
+
var met = r.test(p);
|
|
4202
|
+
if (!met) allMet = false;
|
|
4203
|
+
el.querySelector('.strength-icon').textContent = met ? '\\u2713' : '\\u25CB';
|
|
4204
|
+
el.style.color = met ? '${primaryColor}' : '#4A4D58';
|
|
4205
|
+
});
|
|
4206
|
+
var match = p.length > 0 && p === confirm.value;
|
|
4207
|
+
btn.disabled = !(allMet && match);
|
|
4208
|
+
}
|
|
4209
|
+
|
|
4210
|
+
pw.addEventListener('input', check);
|
|
4211
|
+
confirm.addEventListener('input', check);
|
|
4212
|
+
})();
|
|
4213
|
+
</script>
|
|
4214
|
+
</body>
|
|
4215
|
+
</html>`;
|
|
4242
4216
|
}
|
|
4243
|
-
function
|
|
4244
|
-
return
|
|
4217
|
+
function escapeHtml(str) {
|
|
4218
|
+
return str.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4245
4219
|
}
|
|
4246
4220
|
|
|
4247
|
-
// app/lib/
|
|
4248
|
-
|
|
4249
|
-
|
|
4250
|
-
|
|
4251
|
-
|
|
4252
|
-
if (typeof raw2 !== "string" || !raw2) return raw2;
|
|
4253
|
-
if (/^sk_[a-f0-9]{16}$/.test(raw2)) return raw2;
|
|
4254
|
-
if (!raw2.startsWith("v1.")) return raw2;
|
|
4255
|
-
return `sk_${createHash("sha256").update(raw2).digest("hex").slice(0, 16)}`;
|
|
4256
|
-
}
|
|
4257
|
-
function registerSession(cacheKey2, agentType, accountId, _unused, userId, userName, role) {
|
|
4258
|
-
const existing = sessionStore.get(cacheKey2);
|
|
4259
|
-
if (existing) {
|
|
4260
|
-
existing.agentType = agentType;
|
|
4261
|
-
existing.accountId = accountId;
|
|
4262
|
-
existing.userId = userId ?? existing.userId;
|
|
4263
|
-
existing.userName = userName ?? existing.userName;
|
|
4264
|
-
existing.role = role ?? existing.role;
|
|
4265
|
-
return;
|
|
4221
|
+
// app/lib/auth-gate.ts
|
|
4222
|
+
function canAccessAdmin(input) {
|
|
4223
|
+
const client = resolveClientIp(input.remoteAddress, input.xForwardedFor);
|
|
4224
|
+
if (input.isPublicHost(input.host)) {
|
|
4225
|
+
return { allow: false, reason: "public-host", client };
|
|
4266
4226
|
}
|
|
4267
|
-
|
|
4268
|
-
}
|
|
4269
|
-
function validateSession(cacheKey2, agentType, _signedSessionToken) {
|
|
4270
|
-
const session = sessionStore.get(cacheKey2);
|
|
4271
|
-
if (!session) return { ok: false, reason: "session-not-registered" };
|
|
4272
|
-
if (session.agentType !== agentType) return { ok: false, reason: "agent-type-mismatch" };
|
|
4273
|
-
if (Date.now() - session.createdAt > SESSION_TTL_MS2) {
|
|
4274
|
-
sessionStore.delete(cacheKey2);
|
|
4275
|
-
return { ok: false, reason: "session-expired-age" };
|
|
4227
|
+
if (client.kind !== "external") {
|
|
4228
|
+
return { allow: true, client };
|
|
4276
4229
|
}
|
|
4277
|
-
|
|
4278
|
-
}
|
|
4279
|
-
|
|
4280
|
-
|
|
4230
|
+
if (!isRemoteAuthConfigured()) {
|
|
4231
|
+
return { allow: false, reason: "remote-unconfigured", client };
|
|
4232
|
+
}
|
|
4233
|
+
const token = parseCookieValue(input.cookieHeader, "__remote_session");
|
|
4234
|
+
if (token && validateRemoteSession(token)) {
|
|
4235
|
+
return { allow: true, client };
|
|
4236
|
+
}
|
|
4237
|
+
return { allow: false, reason: "unauthorized", client };
|
|
4281
4238
|
}
|
|
4282
|
-
function
|
|
4283
|
-
|
|
4239
|
+
function parseCookieValue(cookieHeader, name) {
|
|
4240
|
+
if (!cookieHeader) return null;
|
|
4241
|
+
const match2 = cookieHeader.match(new RegExp(`(?:^|;\\s*)${name}=([^;]*)`));
|
|
4242
|
+
if (!match2) return null;
|
|
4243
|
+
try {
|
|
4244
|
+
return decodeURIComponent(match2[1]);
|
|
4245
|
+
} catch {
|
|
4246
|
+
return null;
|
|
4247
|
+
}
|
|
4284
4248
|
}
|
|
4285
|
-
function
|
|
4286
|
-
return
|
|
4249
|
+
function isApiRequestPath(path) {
|
|
4250
|
+
return path.startsWith("/api/");
|
|
4287
4251
|
}
|
|
4288
|
-
|
|
4289
|
-
|
|
4252
|
+
|
|
4253
|
+
// app/lib/agent-slug-pattern.ts
|
|
4254
|
+
var AGENT_SLUG_PATTERN = /^\/([a-z][a-z0-9-]{2,49})$/;
|
|
4255
|
+
|
|
4256
|
+
// server/public-host-gate.ts
|
|
4257
|
+
var PUBLIC_ALLOWED_PREFIXES = [
|
|
4258
|
+
"/api/session",
|
|
4259
|
+
"/api/chat",
|
|
4260
|
+
// Task 873 — the detooled public surface's visitor-authed reader (pointer +
|
|
4261
|
+
// server-filtered SSE stream). Reached by the visitor browser on the public
|
|
4262
|
+
// host; authenticated by the __access_session grant inside the route.
|
|
4263
|
+
"/api/public-reader/",
|
|
4264
|
+
"/api/access/",
|
|
4265
|
+
"/api/health",
|
|
4266
|
+
// Public free/busy for the booking page. Open slots only, no :Meeting detail
|
|
4267
|
+
// (server/routes/calendar-public.ts). Admitted on public hosts; on the brand
|
|
4268
|
+
// host the remote-auth gate bypasses the same path (server/index.ts).
|
|
4269
|
+
"/api/calendar/free-busy",
|
|
4270
|
+
"/api/telegram/",
|
|
4271
|
+
// Task 1881 — the data portal's device-held-file fetch. Reached by the edge
|
|
4272
|
+
// Pages Function's signed HEAD/GET probe on the public host; authenticated by
|
|
4273
|
+
// the per-account HMAC signature the route re-verifies (portal-fetch.ts), not
|
|
4274
|
+
// the remote-auth gate. Pointing PORTAL_INSTALL_ORIGIN at the admin host
|
|
4275
|
+
// instead made every download 503 (remote-auth-required on the probe).
|
|
4276
|
+
"/api/portal/fetch",
|
|
4277
|
+
// QuickBooks OAuth consent callback. Intuit redirects the operator's browser
|
|
4278
|
+
// here with no admin cookie; the route authenticates by validating the OAuth
|
|
4279
|
+
// `state` nonce, not the remote-auth gate (same model as /api/telegram/).
|
|
4280
|
+
"/api/quickbooks/",
|
|
4281
|
+
"/assets/",
|
|
4282
|
+
"/brand/",
|
|
4283
|
+
"/agent-assets/",
|
|
4284
|
+
"/generated/",
|
|
4285
|
+
"/g/",
|
|
4286
|
+
"/sites/"
|
|
4287
|
+
];
|
|
4288
|
+
var PUBLIC_ALLOWED_EXACT = [
|
|
4289
|
+
"/favicon.ico",
|
|
4290
|
+
"/brand-defaults.css"
|
|
4291
|
+
];
|
|
4292
|
+
function isPublicPathAllowed(path) {
|
|
4293
|
+
if (path === "/") return true;
|
|
4294
|
+
if (PUBLIC_ALLOWED_EXACT.includes(path)) return true;
|
|
4295
|
+
if (PUBLIC_ALLOWED_PREFIXES.some((prefix) => path.startsWith(prefix))) return true;
|
|
4296
|
+
return AGENT_SLUG_PATTERN.test(path);
|
|
4290
4297
|
}
|
|
4291
|
-
function
|
|
4292
|
-
return
|
|
4298
|
+
function isOperatorHost(host, operatorDomains2) {
|
|
4299
|
+
return operatorDomains2.has(host);
|
|
4293
4300
|
}
|
|
4294
|
-
function
|
|
4295
|
-
|
|
4296
|
-
if (
|
|
4297
|
-
|
|
4298
|
-
return true;
|
|
4301
|
+
function classifyHost(host, operatorDomains2, isPublicHost) {
|
|
4302
|
+
if (operatorDomains2.has(host)) return "operator";
|
|
4303
|
+
if (isPublicHost(host)) return "public";
|
|
4304
|
+
return "admin";
|
|
4299
4305
|
}
|
|
4300
|
-
|
|
4301
|
-
|
|
4302
|
-
|
|
4306
|
+
|
|
4307
|
+
// server/operator-domains-reader.ts
|
|
4308
|
+
import { watchFile } from "fs";
|
|
4309
|
+
|
|
4310
|
+
// app/lib/operator-domains.ts
|
|
4311
|
+
import { existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync7, writeFileSync as writeFileSync3 } from "fs";
|
|
4312
|
+
import { dirname as dirname3, resolve as resolve4 } from "path";
|
|
4313
|
+
var OPERATOR_DOMAINS_PATH = resolve4(MAXY_DIR, "operator-domains.json");
|
|
4314
|
+
function loadOperatorDomains() {
|
|
4315
|
+
if (!existsSync5(OPERATOR_DOMAINS_PATH)) return /* @__PURE__ */ new Set();
|
|
4316
|
+
try {
|
|
4317
|
+
const parsed = JSON.parse(readFileSync7(OPERATOR_DOMAINS_PATH, "utf-8"));
|
|
4318
|
+
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
4319
|
+
return new Set(parsed.filter((h) => typeof h === "string"));
|
|
4320
|
+
} catch {
|
|
4321
|
+
return /* @__PURE__ */ new Set();
|
|
4322
|
+
}
|
|
4303
4323
|
}
|
|
4304
|
-
|
|
4305
|
-
|
|
4324
|
+
|
|
4325
|
+
// server/operator-domains-reader.ts
|
|
4326
|
+
var operatorDomains = loadOperatorDomains();
|
|
4327
|
+
if (operatorDomains.size > 0) {
|
|
4328
|
+
console.log(
|
|
4329
|
+
`[operator-domains] loaded ${operatorDomains.size} operator domain(s): ${[...operatorDomains].join(", ")}`
|
|
4330
|
+
);
|
|
4306
4331
|
}
|
|
4307
|
-
|
|
4308
|
-
|
|
4332
|
+
watchFile(OPERATOR_DOMAINS_PATH, { interval: 2e3 }, () => {
|
|
4333
|
+
operatorDomains = loadOperatorDomains();
|
|
4334
|
+
console.log(
|
|
4335
|
+
`[operator-domains] reloaded ${operatorDomains.size} operator domain(s): ${[...operatorDomains].join(", ")}`
|
|
4336
|
+
);
|
|
4337
|
+
});
|
|
4338
|
+
function getOperatorDomains() {
|
|
4339
|
+
return operatorDomains;
|
|
4309
4340
|
}
|
|
4310
4341
|
|
|
4311
4342
|
// app/lib/claude-agent/plugin-manifest.ts
|
|
@@ -4522,37 +4553,6 @@ var emitMissingOnResolve = (..._args) => null;
|
|
|
4522
4553
|
// app/lib/claude-agent/summary-helpers.ts
|
|
4523
4554
|
var stripAttachmentMetaSuffix = (..._args) => null;
|
|
4524
4555
|
|
|
4525
|
-
// ../lib/require-port-env/src/index.ts
|
|
4526
|
-
var MissingPortEnvError = class extends Error {
|
|
4527
|
-
envName;
|
|
4528
|
-
tag;
|
|
4529
|
-
failLine;
|
|
4530
|
-
constructor(envName, tag, failLine, message) {
|
|
4531
|
-
super(message);
|
|
4532
|
-
this.name = "MissingPortEnvError";
|
|
4533
|
-
this.envName = envName;
|
|
4534
|
-
this.tag = tag;
|
|
4535
|
-
this.failLine = failLine;
|
|
4536
|
-
}
|
|
4537
|
-
};
|
|
4538
|
-
var VALID_PORT_MIN = 1;
|
|
4539
|
-
var VALID_PORT_MAX = 65535;
|
|
4540
|
-
function requirePortEnv(envName, options = {}) {
|
|
4541
|
-
const tag = options.tag ?? "port-env";
|
|
4542
|
-
const failLine = options.failLine ?? "missing-port-env";
|
|
4543
|
-
const raw2 = process.env[envName];
|
|
4544
|
-
if (raw2 === void 0 || raw2 === "") {
|
|
4545
|
-
const msg = `[${tag}] ${failLine} reason=missing-env env=${envName}`;
|
|
4546
|
-
throw new MissingPortEnvError(envName, tag, failLine, msg);
|
|
4547
|
-
}
|
|
4548
|
-
const parsed = Number.parseInt(raw2, 10);
|
|
4549
|
-
if (!Number.isInteger(parsed) || parsed < VALID_PORT_MIN || parsed > VALID_PORT_MAX || String(parsed) !== raw2.trim()) {
|
|
4550
|
-
const msg = `[${tag}] ${failLine} reason=invalid-port-env env=${envName} value=${JSON.stringify(raw2)}`;
|
|
4551
|
-
throw new MissingPortEnvError(envName, tag, failLine, msg);
|
|
4552
|
-
}
|
|
4553
|
-
return parsed;
|
|
4554
|
-
}
|
|
4555
|
-
|
|
4556
4556
|
// app/lib/neo4j-store.ts
|
|
4557
4557
|
import neo4j from "neo4j-driver";
|
|
4558
4558
|
import { randomUUID } from "crypto";
|
|
@@ -6208,11 +6208,6 @@ export {
|
|
|
6208
6208
|
Hono2 as Hono,
|
|
6209
6209
|
getRequestListener,
|
|
6210
6210
|
serve,
|
|
6211
|
-
sampleCpu,
|
|
6212
|
-
sampleProcessCpu,
|
|
6213
|
-
registerLoop,
|
|
6214
|
-
listLoops,
|
|
6215
|
-
armedCount,
|
|
6216
6211
|
MAXY_DIR,
|
|
6217
6212
|
BRAND_NAME,
|
|
6218
6213
|
COMMERCIAL_MODE,
|
|
@@ -6230,6 +6225,35 @@ export {
|
|
|
6230
6225
|
INSTALL_OWNER_FILE,
|
|
6231
6226
|
VISITOR_TOKEN_SECRET_FILE,
|
|
6232
6227
|
CLAUDE_CREDENTIALS_FILE,
|
|
6228
|
+
SYSTEM_PATH_SLUGS,
|
|
6229
|
+
PLATFORM_ROOT2 as PLATFORM_ROOT,
|
|
6230
|
+
ACCOUNTS_DIR,
|
|
6231
|
+
listValidAccounts,
|
|
6232
|
+
isValidAccountId,
|
|
6233
|
+
resolveAccount,
|
|
6234
|
+
validateAgentSlug,
|
|
6235
|
+
resolveDefaultAgentSlug,
|
|
6236
|
+
resolveAgentConfig,
|
|
6237
|
+
isActiveAgentSlug,
|
|
6238
|
+
getDefaultAccountId,
|
|
6239
|
+
requirePortEnv,
|
|
6240
|
+
fingerprintSessionKey,
|
|
6241
|
+
registerSession,
|
|
6242
|
+
validateSession,
|
|
6243
|
+
getAccountIdForSession,
|
|
6244
|
+
getUserIdForSession,
|
|
6245
|
+
getUserNameForSession,
|
|
6246
|
+
getRoleForSession,
|
|
6247
|
+
getSessionIdForSession,
|
|
6248
|
+
setSessionIdForSession,
|
|
6249
|
+
setWantsPriorConversation,
|
|
6250
|
+
unregisterSession,
|
|
6251
|
+
getSessionKeyBySessionId,
|
|
6252
|
+
sampleCpu,
|
|
6253
|
+
sampleProcessCpu,
|
|
6254
|
+
registerLoop,
|
|
6255
|
+
listLoops,
|
|
6256
|
+
armedCount,
|
|
6233
6257
|
vncLog,
|
|
6234
6258
|
newCorrId,
|
|
6235
6259
|
sanitizeClientCorrId,
|
|
@@ -6254,35 +6278,12 @@ export {
|
|
|
6254
6278
|
parseCookieValue,
|
|
6255
6279
|
isApiRequestPath,
|
|
6256
6280
|
AGENT_SLUG_PATTERN,
|
|
6257
|
-
SYSTEM_PATH_SLUGS,
|
|
6258
6281
|
PUBLIC_ALLOWED_PREFIXES,
|
|
6259
6282
|
PUBLIC_ALLOWED_EXACT,
|
|
6260
6283
|
isPublicPathAllowed,
|
|
6261
6284
|
isOperatorHost,
|
|
6262
6285
|
classifyHost,
|
|
6263
6286
|
getOperatorDomains,
|
|
6264
|
-
PLATFORM_ROOT2 as PLATFORM_ROOT,
|
|
6265
|
-
ACCOUNTS_DIR,
|
|
6266
|
-
listValidAccounts,
|
|
6267
|
-
isValidAccountId,
|
|
6268
|
-
resolveAccount,
|
|
6269
|
-
validateAgentSlug,
|
|
6270
|
-
resolveDefaultAgentSlug,
|
|
6271
|
-
resolveAgentConfig,
|
|
6272
|
-
isActiveAgentSlug,
|
|
6273
|
-
getDefaultAccountId,
|
|
6274
|
-
fingerprintSessionKey,
|
|
6275
|
-
registerSession,
|
|
6276
|
-
validateSession,
|
|
6277
|
-
getAccountIdForSession,
|
|
6278
|
-
getUserIdForSession,
|
|
6279
|
-
getUserNameForSession,
|
|
6280
|
-
getRoleForSession,
|
|
6281
|
-
getSessionIdForSession,
|
|
6282
|
-
setSessionIdForSession,
|
|
6283
|
-
setWantsPriorConversation,
|
|
6284
|
-
unregisterSession,
|
|
6285
|
-
getSessionKeyBySessionId,
|
|
6286
6287
|
findMissingPlugins,
|
|
6287
6288
|
walkPremiumBundles,
|
|
6288
6289
|
autoDeliverPremiumPlugins,
|
|
@@ -6290,7 +6291,6 @@ export {
|
|
|
6290
6291
|
cleanupLeakedPremiumSubs,
|
|
6291
6292
|
emitMissingOnResolve,
|
|
6292
6293
|
stripAttachmentMetaSuffix,
|
|
6293
|
-
requirePortEnv,
|
|
6294
6294
|
getSession,
|
|
6295
6295
|
runAdminUserSelfHeal,
|
|
6296
6296
|
runUserProfileReconcile,
|
|
@@ -6321,6 +6321,7 @@ export {
|
|
|
6321
6321
|
projectAgent,
|
|
6322
6322
|
deleteAgentProjection,
|
|
6323
6323
|
safeJson,
|
|
6324
|
+
resolveCacheKey,
|
|
6324
6325
|
requireAdminSession,
|
|
6325
6326
|
tryCookieBridgeForConversation,
|
|
6326
6327
|
requireAdminCookie,
|