@rubytech/create-maxy 1.0.806 → 1.0.808
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/payload/platform/neo4j/migrations/004-project-admin-agent.ts +247 -0
- package/payload/platform/neo4j/migrations/004-prune-alien-accounts.ts +134 -0
- package/payload/platform/plugins/admin/skills/onboarding/SKILL.md +2 -0
- package/payload/platform/plugins/docs/references/cloudflare.md +1 -0
- package/payload/platform/plugins/docs/references/graph.md +42 -0
- package/payload/platform/plugins/docs/references/internals.md +3 -1
- package/payload/platform/plugins/docs/references/memory-guide.md +4 -0
- package/payload/platform/plugins/docs/references/troubleshooting.md +19 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.d.ts.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js +19 -0
- package/payload/platform/plugins/memory/mcp/dist/tools/profile-read.js.map +1 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +4 -1
- package/payload/platform/templates/agents/admin/SOUL.md +2 -0
- package/payload/server/chunk-CRWLE6BZ.js +3511 -0
- package/payload/server/chunk-LSUMH6OF.js +9993 -0
- package/payload/server/chunk-V3VLAL7N.js +10009 -0
- package/payload/server/chunk-YULDSPAC.js +3484 -0
- package/payload/server/client-pool-LXE7RIRT.js +31 -0
- package/payload/server/client-pool-N2Y57223.js +31 -0
- package/payload/server/maxy-edge.js +5 -4
- package/payload/server/neo4j-migrations-HEECOAGK.js +128 -0
- package/payload/server/public/assets/admin-MxaCgGHZ.js +352 -0
- package/payload/server/public/assets/{graph-CBu0rtrP.js → graph-CDwy6Qw1.js} +1 -1
- package/payload/server/public/assets/page-DEyK-lSN.js +50 -0
- package/payload/server/public/graph.html +2 -2
- package/payload/server/public/index.html +2 -2
- package/payload/server/server.js +658 -278
- package/payload/server/public/assets/admin-BYsaXlDv.js +0 -352
- package/payload/server/public/assets/page-BNM63zsb.js +0 -50
package/payload/server/server.js
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE,
|
|
7
7
|
TELEGRAM_WEBHOOK_SECRET_FILE,
|
|
8
8
|
USERS_FILE,
|
|
9
|
+
actionLogPath,
|
|
9
10
|
autoDeliverPremiumPlugins,
|
|
10
11
|
buildX11Env,
|
|
11
12
|
callOauthLlm,
|
|
@@ -29,6 +30,7 @@ import {
|
|
|
29
30
|
launchAction,
|
|
30
31
|
load,
|
|
31
32
|
logPath,
|
|
33
|
+
reconcileCloudflareSetupFromLog,
|
|
32
34
|
recordFailedAttempt,
|
|
33
35
|
render,
|
|
34
36
|
renderLoginPage,
|
|
@@ -50,7 +52,7 @@ import {
|
|
|
50
52
|
vncLog,
|
|
51
53
|
waitForExit,
|
|
52
54
|
writeChromiumWrapper
|
|
53
|
-
} from "./chunk-
|
|
55
|
+
} from "./chunk-V3VLAL7N.js";
|
|
54
56
|
import {
|
|
55
57
|
ACCOUNTS_DIR,
|
|
56
58
|
GREETING_DIRECTIVE,
|
|
@@ -65,6 +67,7 @@ import {
|
|
|
65
67
|
deleteAgentProjection,
|
|
66
68
|
deleteConversation,
|
|
67
69
|
embed,
|
|
70
|
+
ensureConversation,
|
|
68
71
|
fetchBranding,
|
|
69
72
|
findGroupBySlug,
|
|
70
73
|
findRecentConversation,
|
|
@@ -113,7 +116,7 @@ import {
|
|
|
113
116
|
verifyAndGetConversationUpdatedAt,
|
|
114
117
|
verifyConversationOwnership,
|
|
115
118
|
writeAdminUserAndPerson
|
|
116
|
-
} from "./chunk-
|
|
119
|
+
} from "./chunk-CRWLE6BZ.js";
|
|
117
120
|
import {
|
|
118
121
|
__commonJS,
|
|
119
122
|
__toESM
|
|
@@ -616,8 +619,8 @@ var serveStatic = (options = { root: "" }) => {
|
|
|
616
619
|
};
|
|
617
620
|
|
|
618
621
|
// server/index.ts
|
|
619
|
-
import { readFileSync as
|
|
620
|
-
import { resolve as resolve24, join as
|
|
622
|
+
import { readFileSync as readFileSync18, existsSync as existsSync25, watchFile } from "fs";
|
|
623
|
+
import { resolve as resolve24, join as join11, basename as basename7 } from "path";
|
|
621
624
|
import { homedir as homedir2 } from "os";
|
|
622
625
|
|
|
623
626
|
// app/lib/agent-slug-pattern.ts
|
|
@@ -803,6 +806,28 @@ function defaultRules() {
|
|
|
803
806
|
scope: "session",
|
|
804
807
|
suggestedAction: "The WebFetch SPA preflight has fired more than once in this conversation. Either the agent is ignoring the loud-failure directive (retrying WebFetch after seeing WEBFETCH_CANNOT_READ_JS_SPA), or multiple SPA URLs are being asked about. Read the conversation's stream log for the [tool-use] / [tool-result] sequence around each occurrence \u2014 if the agent dispatched WebFetch on the same URL or substituted Playwright silently, revisit the IDENTITY.md `Tool Failure Discipline` paragraph that names structured-error handling."
|
|
805
808
|
},
|
|
809
|
+
{
|
|
810
|
+
// Task 867 — fires when setup-tunnel.sh emits step=done but no
|
|
811
|
+
// `[persist] role=user … Cloudflare setup completed (actionId: <id>)`
|
|
812
|
+
// line appears within 60s. Covers the three failure modes of the
|
|
813
|
+
// action-relay-queue plumbing: queue-write failed, boot-drain consumer
|
|
814
|
+
// skipped the record, or the agent's hoisted persist threw. The
|
|
815
|
+
// followup pattern is intentionally narrow to the cloudflare-setup
|
|
816
|
+
// relay shape (not a general "any user persist") so a benign user
|
|
817
|
+
// typing in chat does not satisfy the followup. logSource=any so
|
|
818
|
+
// the rule sees both the script tee (in stream logs / server.log)
|
|
819
|
+
// and the [persist] line (server.log).
|
|
820
|
+
id: "cloudflare-setup-relay-not-acknowledged",
|
|
821
|
+
name: "Cloudflare-setup completed but the chat relay never acknowledged",
|
|
822
|
+
type: "absent-followup",
|
|
823
|
+
logSource: "any",
|
|
824
|
+
pattern: "\\[script:setup-tunnel\\] step=done",
|
|
825
|
+
followupPattern: "\\[persist\\] .*role=user.* Cloudflare setup completed \\(actionId:",
|
|
826
|
+
followupWindowMs: 6e4,
|
|
827
|
+
thresholdCount: 0,
|
|
828
|
+
thresholdWindowMinutes: 0,
|
|
829
|
+
suggestedAction: "[Task 867] cloudflare-setup completed but the post-action relay never reached the chat history. Check `[action-relay-queue] phase=enqueued` (queue write), `[action-completion-relay] phase=consumed` (boot-drain ran), and `[persist] role=user \u2026 Cloudflare setup completed` (graph write). One of those is missing; the matching grep recipe is in the Task 867 brief Observability section."
|
|
830
|
+
},
|
|
806
831
|
{
|
|
807
832
|
// Task 538: fires when a [spawn] line appears in a conversation's stream
|
|
808
833
|
// log but no subprocess-lifecycle marker follows within 10s. The three
|
|
@@ -3739,6 +3764,37 @@ function sanitizeReason(err) {
|
|
|
3739
3764
|
return `${err.name}:${msg}`;
|
|
3740
3765
|
}
|
|
3741
3766
|
|
|
3767
|
+
// app/lib/whatsapp/ensure-conversation.ts
|
|
3768
|
+
var TAG8 = "[whatsapp-persist]";
|
|
3769
|
+
async function ensureWhatsAppConversation(input) {
|
|
3770
|
+
const t0 = Date.now();
|
|
3771
|
+
try {
|
|
3772
|
+
const result = await ensureConversation(
|
|
3773
|
+
input.accountId,
|
|
3774
|
+
input.agentType,
|
|
3775
|
+
input.sessionKey
|
|
3776
|
+
);
|
|
3777
|
+
const ms = Date.now() - t0;
|
|
3778
|
+
if (!result.conversationId) {
|
|
3779
|
+
console.error(
|
|
3780
|
+
`${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=null-conversationId ms=${ms}`
|
|
3781
|
+
);
|
|
3782
|
+
return null;
|
|
3783
|
+
}
|
|
3784
|
+
console.error(
|
|
3785
|
+
`${TAG8} conversation-merged sessionKey=${input.sessionKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
3786
|
+
);
|
|
3787
|
+
return { conversationId: result.conversationId, created: result.created };
|
|
3788
|
+
} catch (err) {
|
|
3789
|
+
const ms = Date.now() - t0;
|
|
3790
|
+
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
3791
|
+
console.error(
|
|
3792
|
+
`${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=${reason} ms=${ms}`
|
|
3793
|
+
);
|
|
3794
|
+
return null;
|
|
3795
|
+
}
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3742
3798
|
// app/lib/whatsapp/inbound/media.ts
|
|
3743
3799
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3744
3800
|
import { writeFile, mkdir } from "fs/promises";
|
|
@@ -3748,7 +3804,7 @@ import {
|
|
|
3748
3804
|
downloadContentFromMessage,
|
|
3749
3805
|
normalizeMessageContent as normalizeMessageContent2
|
|
3750
3806
|
} from "@whiskeysockets/baileys";
|
|
3751
|
-
var
|
|
3807
|
+
var TAG9 = "[whatsapp:media]";
|
|
3752
3808
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
3753
3809
|
function mimeToExt(mimetype) {
|
|
3754
3810
|
const map = {
|
|
@@ -3804,25 +3860,25 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3804
3860
|
}
|
|
3805
3861
|
);
|
|
3806
3862
|
if (!buffer || buffer.length === 0) {
|
|
3807
|
-
console.error(`${
|
|
3863
|
+
console.error(`${TAG9} primary download returned empty, trying direct fallback`);
|
|
3808
3864
|
const downloadable = getDownloadableContent(content);
|
|
3809
3865
|
if (downloadable) {
|
|
3810
3866
|
try {
|
|
3811
3867
|
const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
3812
3868
|
buffer = await streamToBuffer(stream);
|
|
3813
3869
|
} catch (fallbackErr) {
|
|
3814
|
-
console.error(`${
|
|
3870
|
+
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
3815
3871
|
}
|
|
3816
3872
|
}
|
|
3817
3873
|
}
|
|
3818
3874
|
if (!buffer || buffer.length === 0) {
|
|
3819
|
-
console.error(`${
|
|
3875
|
+
console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
3820
3876
|
return void 0;
|
|
3821
3877
|
}
|
|
3822
3878
|
if (buffer.length > maxBytes) {
|
|
3823
3879
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
3824
3880
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
3825
|
-
console.error(`${
|
|
3881
|
+
console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
3826
3882
|
return void 0;
|
|
3827
3883
|
}
|
|
3828
3884
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
@@ -3831,20 +3887,20 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3831
3887
|
const filePath = join4(MEDIA_DIR, filename);
|
|
3832
3888
|
await writeFile(filePath, buffer);
|
|
3833
3889
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
3834
|
-
console.error(`${
|
|
3890
|
+
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
3835
3891
|
return {
|
|
3836
3892
|
path: filePath,
|
|
3837
3893
|
mimetype: mimetype ?? "application/octet-stream",
|
|
3838
3894
|
size: buffer.length
|
|
3839
3895
|
};
|
|
3840
3896
|
} catch (err) {
|
|
3841
|
-
console.error(`${
|
|
3897
|
+
console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
3842
3898
|
return void 0;
|
|
3843
3899
|
}
|
|
3844
3900
|
}
|
|
3845
3901
|
|
|
3846
3902
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
3847
|
-
var
|
|
3903
|
+
var TAG10 = "[whatsapp:debounce]";
|
|
3848
3904
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
3849
3905
|
function createInboundDebouncer(opts) {
|
|
3850
3906
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -3875,7 +3931,7 @@ function createInboundDebouncer(opts) {
|
|
|
3875
3931
|
pending.delete(key);
|
|
3876
3932
|
const batchSize = batch.entries.length;
|
|
3877
3933
|
try {
|
|
3878
|
-
console.error(`${
|
|
3934
|
+
console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
|
|
3879
3935
|
const result = onFlush(batch.entries);
|
|
3880
3936
|
if (result && typeof result.catch === "function") {
|
|
3881
3937
|
result.catch(onError);
|
|
@@ -3947,7 +4003,7 @@ function createInboundDebouncer(opts) {
|
|
|
3947
4003
|
}
|
|
3948
4004
|
|
|
3949
4005
|
// app/lib/whatsapp/opening-hours.ts
|
|
3950
|
-
var
|
|
4006
|
+
var TAG11 = "[whatsapp:hours]";
|
|
3951
4007
|
async function isBusinessOpen(accountId) {
|
|
3952
4008
|
try {
|
|
3953
4009
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -3965,7 +4021,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3965
4021
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
3966
4022
|
);
|
|
3967
4023
|
if (result.records.length === 0) {
|
|
3968
|
-
console.error(`${
|
|
4024
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3969
4025
|
return { open: true, reason: "no opening hours configured" };
|
|
3970
4026
|
}
|
|
3971
4027
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -3974,7 +4030,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3974
4030
|
closes: String(r.get("closes")).trim()
|
|
3975
4031
|
}));
|
|
3976
4032
|
if (specs.length === 0) {
|
|
3977
|
-
console.error(`${
|
|
4033
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3978
4034
|
return { open: true, reason: "no opening hours configured" };
|
|
3979
4035
|
}
|
|
3980
4036
|
for (const spec of specs) {
|
|
@@ -3984,7 +4040,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3984
4040
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
3985
4041
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
3986
4042
|
console.error(
|
|
3987
|
-
`${
|
|
4043
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3988
4044
|
);
|
|
3989
4045
|
return {
|
|
3990
4046
|
open: true,
|
|
@@ -3997,7 +4053,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3997
4053
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
3998
4054
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
3999
4055
|
console.error(
|
|
4000
|
-
`${
|
|
4056
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
4001
4057
|
);
|
|
4002
4058
|
return {
|
|
4003
4059
|
open: true,
|
|
@@ -4011,7 +4067,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4011
4067
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
4012
4068
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
4013
4069
|
console.error(
|
|
4014
|
-
`${
|
|
4070
|
+
`${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
4015
4071
|
);
|
|
4016
4072
|
return {
|
|
4017
4073
|
open: false,
|
|
@@ -4025,7 +4081,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4025
4081
|
}
|
|
4026
4082
|
} catch (err) {
|
|
4027
4083
|
console.error(
|
|
4028
|
-
`${
|
|
4084
|
+
`${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
4029
4085
|
);
|
|
4030
4086
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
4031
4087
|
}
|
|
@@ -4071,7 +4127,7 @@ import { execFile } from "child_process";
|
|
|
4071
4127
|
import { unlink, stat } from "fs/promises";
|
|
4072
4128
|
import { promisify } from "util";
|
|
4073
4129
|
var execFileAsync = promisify(execFile);
|
|
4074
|
-
var
|
|
4130
|
+
var TAG12 = "[stt]";
|
|
4075
4131
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
4076
4132
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
4077
4133
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -4082,11 +4138,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4082
4138
|
const s = await stat(audioPath);
|
|
4083
4139
|
audioBytes = s.size;
|
|
4084
4140
|
} catch {
|
|
4085
|
-
console.error(`${
|
|
4141
|
+
console.error(`${TAG12} failed: file not readable path=${audioPath}`);
|
|
4086
4142
|
return void 0;
|
|
4087
4143
|
}
|
|
4088
4144
|
console.error(
|
|
4089
|
-
`${
|
|
4145
|
+
`${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
4090
4146
|
);
|
|
4091
4147
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
4092
4148
|
try {
|
|
@@ -4105,7 +4161,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4105
4161
|
], { timeout: 3e4 });
|
|
4106
4162
|
} catch (err) {
|
|
4107
4163
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4108
|
-
console.error(`${
|
|
4164
|
+
console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
|
|
4109
4165
|
return void 0;
|
|
4110
4166
|
}
|
|
4111
4167
|
try {
|
|
@@ -4123,20 +4179,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4123
4179
|
const text = stdout.trim();
|
|
4124
4180
|
const durationMs = Date.now() - startMs;
|
|
4125
4181
|
if (!text) {
|
|
4126
|
-
console.error(`${
|
|
4182
|
+
console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
4127
4183
|
return void 0;
|
|
4128
4184
|
}
|
|
4129
4185
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
4130
4186
|
const language = langMatch?.[1] ?? "unknown";
|
|
4131
4187
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
4132
4188
|
console.error(
|
|
4133
|
-
`${
|
|
4189
|
+
`${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
4134
4190
|
);
|
|
4135
4191
|
return { text, language, durationMs };
|
|
4136
4192
|
} catch (err) {
|
|
4137
4193
|
const durationMs = Date.now() - startMs;
|
|
4138
4194
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4139
|
-
console.error(`${
|
|
4195
|
+
console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
4140
4196
|
return void 0;
|
|
4141
4197
|
} finally {
|
|
4142
4198
|
unlink(wavPath).catch(() => {
|
|
@@ -4145,7 +4201,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4145
4201
|
}
|
|
4146
4202
|
|
|
4147
4203
|
// app/lib/whatsapp/manager.ts
|
|
4148
|
-
var
|
|
4204
|
+
var TAG13 = "[whatsapp:manager]";
|
|
4149
4205
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
4150
4206
|
var MESSAGE_STORE_MAX = 500;
|
|
4151
4207
|
var GROUP_META_TTL = 5 * 60 * 1e3;
|
|
@@ -4165,7 +4221,7 @@ function storeMessage(storeKey, entry) {
|
|
|
4165
4221
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
4166
4222
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
4167
4223
|
entries.splice(0, trimmed);
|
|
4168
|
-
console.error(`${
|
|
4224
|
+
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
4169
4225
|
}
|
|
4170
4226
|
}
|
|
4171
4227
|
function deriveSessionKey(input) {
|
|
@@ -4184,7 +4240,7 @@ function deriveSessionKey(input) {
|
|
|
4184
4240
|
}
|
|
4185
4241
|
async function init(opts) {
|
|
4186
4242
|
if (initialized) {
|
|
4187
|
-
console.error(`${
|
|
4243
|
+
console.error(`${TAG13} already initialized`);
|
|
4188
4244
|
return;
|
|
4189
4245
|
}
|
|
4190
4246
|
configDir = opts.configDir;
|
|
@@ -4192,20 +4248,20 @@ async function init(opts) {
|
|
|
4192
4248
|
loadConfig(opts.accountConfig);
|
|
4193
4249
|
const accountIds = listCredentialAccountIds(configDir);
|
|
4194
4250
|
if (accountIds.length === 0) {
|
|
4195
|
-
console.error(`${
|
|
4251
|
+
console.error(`${TAG13} init: no stored WhatsApp credentials found`);
|
|
4196
4252
|
initialized = true;
|
|
4197
4253
|
return;
|
|
4198
4254
|
}
|
|
4199
|
-
console.error(`${
|
|
4255
|
+
console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
4200
4256
|
initialized = true;
|
|
4201
4257
|
for (const accountId of accountIds) {
|
|
4202
4258
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
4203
4259
|
if (accountCfg?.enabled === false) {
|
|
4204
|
-
console.error(`${
|
|
4260
|
+
console.error(`${TAG13} skipping disabled account=${accountId}`);
|
|
4205
4261
|
continue;
|
|
4206
4262
|
}
|
|
4207
4263
|
startConnection(accountId).catch((err) => {
|
|
4208
|
-
console.error(`${
|
|
4264
|
+
console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
4209
4265
|
});
|
|
4210
4266
|
}
|
|
4211
4267
|
}
|
|
@@ -4214,7 +4270,7 @@ async function startConnection(accountId) {
|
|
|
4214
4270
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
4215
4271
|
const hasAuth = await authExists(authDir);
|
|
4216
4272
|
if (!hasAuth) {
|
|
4217
|
-
console.error(`${
|
|
4273
|
+
console.error(`${TAG13} no credentials for account=${accountId}`);
|
|
4218
4274
|
return;
|
|
4219
4275
|
}
|
|
4220
4276
|
await stopConnection(accountId);
|
|
@@ -4250,11 +4306,11 @@ async function stopConnection(accountId) {
|
|
|
4250
4306
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
4251
4307
|
conn.sock.ws?.close?.();
|
|
4252
4308
|
} catch (err) {
|
|
4253
|
-
console.warn(`${
|
|
4309
|
+
console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
4254
4310
|
}
|
|
4255
4311
|
}
|
|
4256
4312
|
connections.delete(accountId);
|
|
4257
|
-
console.error(`${
|
|
4313
|
+
console.error(`${TAG13} stopped account=${accountId}`);
|
|
4258
4314
|
}
|
|
4259
4315
|
function getStatus() {
|
|
4260
4316
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -4295,9 +4351,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4295
4351
|
connections.set(accountId, conn);
|
|
4296
4352
|
try {
|
|
4297
4353
|
await sock.sendPresenceUpdate("available");
|
|
4298
|
-
console.error(`${
|
|
4354
|
+
console.error(`${TAG13} presence set to available account=${accountId}`);
|
|
4299
4355
|
} catch (err) {
|
|
4300
|
-
console.error(`${
|
|
4356
|
+
console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
|
|
4301
4357
|
}
|
|
4302
4358
|
await runInitQueries(sock, {
|
|
4303
4359
|
accountId,
|
|
@@ -4308,7 +4364,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4308
4364
|
});
|
|
4309
4365
|
monitorInbound(conn);
|
|
4310
4366
|
watchForDisconnect(conn);
|
|
4311
|
-
console.error(`${
|
|
4367
|
+
console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4312
4368
|
}
|
|
4313
4369
|
function reloadConfig(accountConfig) {
|
|
4314
4370
|
loadConfig(accountConfig);
|
|
@@ -4332,7 +4388,7 @@ async function shutdown() {
|
|
|
4332
4388
|
const ids = Array.from(connections.keys());
|
|
4333
4389
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
4334
4390
|
initialized = false;
|
|
4335
|
-
console.error(`${
|
|
4391
|
+
console.error(`${TAG13} shutdown complete`);
|
|
4336
4392
|
}
|
|
4337
4393
|
function loadConfig(accountConfig) {
|
|
4338
4394
|
try {
|
|
@@ -4344,12 +4400,12 @@ function loadConfig(accountConfig) {
|
|
|
4344
4400
|
if (parsed.success) {
|
|
4345
4401
|
whatsAppConfig = parsed.data;
|
|
4346
4402
|
} else {
|
|
4347
|
-
console.error(`${
|
|
4403
|
+
console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
|
|
4348
4404
|
whatsAppConfig = {};
|
|
4349
4405
|
}
|
|
4350
4406
|
}
|
|
4351
4407
|
} catch (err) {
|
|
4352
|
-
console.error(`${
|
|
4408
|
+
console.error(`${TAG13} config load error: ${String(err)}`);
|
|
4353
4409
|
whatsAppConfig = {};
|
|
4354
4410
|
}
|
|
4355
4411
|
}
|
|
@@ -4360,13 +4416,13 @@ async function connectWithReconnect(conn) {
|
|
|
4360
4416
|
let cycleError = null;
|
|
4361
4417
|
let connectedAt;
|
|
4362
4418
|
try {
|
|
4363
|
-
console.error(`${
|
|
4419
|
+
console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
4364
4420
|
const sock = await createWaSocket({
|
|
4365
4421
|
authDir: conn.authDir,
|
|
4366
4422
|
silent: true
|
|
4367
4423
|
});
|
|
4368
4424
|
conn.sock = sock;
|
|
4369
|
-
console.error(`${
|
|
4425
|
+
console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
4370
4426
|
await waitForConnection(sock);
|
|
4371
4427
|
const selfId = readSelfId(conn.authDir);
|
|
4372
4428
|
connectedAt = Date.now();
|
|
@@ -4376,12 +4432,12 @@ async function connectWithReconnect(conn) {
|
|
|
4376
4432
|
conn.lastConnectedAt = connectedAt;
|
|
4377
4433
|
conn.lastError = void 0;
|
|
4378
4434
|
conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
|
|
4379
|
-
console.error(`${
|
|
4435
|
+
console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4380
4436
|
try {
|
|
4381
4437
|
await sock.sendPresenceUpdate("available");
|
|
4382
|
-
console.error(`${
|
|
4438
|
+
console.error(`${TAG13} presence set to available account=${conn.accountId}`);
|
|
4383
4439
|
} catch (err) {
|
|
4384
|
-
console.error(`${
|
|
4440
|
+
console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
4385
4441
|
}
|
|
4386
4442
|
await runInitQueries(sock, {
|
|
4387
4443
|
accountId: conn.accountId,
|
|
@@ -4408,9 +4464,9 @@ async function connectWithReconnect(conn) {
|
|
|
4408
4464
|
}
|
|
4409
4465
|
const classification = classifyDisconnect(err);
|
|
4410
4466
|
conn.lastError = classification.message;
|
|
4411
|
-
console.error(`${
|
|
4467
|
+
console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
4412
4468
|
if (!classification.shouldRetry) {
|
|
4413
|
-
console.error(`${
|
|
4469
|
+
console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
4414
4470
|
return;
|
|
4415
4471
|
}
|
|
4416
4472
|
}
|
|
@@ -4423,7 +4479,7 @@ async function connectWithReconnect(conn) {
|
|
|
4423
4479
|
if (decision.action === "abort") {
|
|
4424
4480
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
4425
4481
|
console.error(
|
|
4426
|
-
`${
|
|
4482
|
+
`${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
4427
4483
|
);
|
|
4428
4484
|
conn.sessionStuckReason = stuckReason;
|
|
4429
4485
|
conn.lastError = stuckReason;
|
|
@@ -4432,17 +4488,17 @@ async function connectWithReconnect(conn) {
|
|
|
4432
4488
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
4433
4489
|
if (decision.reason === "short-lived") {
|
|
4434
4490
|
console.error(
|
|
4435
|
-
`${
|
|
4491
|
+
`${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
4436
4492
|
);
|
|
4437
4493
|
} else {
|
|
4438
4494
|
console.error(
|
|
4439
|
-
`${
|
|
4495
|
+
`${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
4440
4496
|
);
|
|
4441
4497
|
}
|
|
4442
4498
|
if (decision.reason === "short-lived" || cycleError) {
|
|
4443
4499
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
4444
4500
|
console.error(
|
|
4445
|
-
`${
|
|
4501
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4446
4502
|
);
|
|
4447
4503
|
await new Promise((resolve25) => {
|
|
4448
4504
|
const timer2 = setTimeout(resolve25, delay);
|
|
@@ -4476,11 +4532,11 @@ function watchForDisconnect(conn) {
|
|
|
4476
4532
|
conn.sock.ev.on("connection.update", (update) => {
|
|
4477
4533
|
if (update.connection === "close") {
|
|
4478
4534
|
if (connections.get(conn.accountId) !== conn) return;
|
|
4479
|
-
console.error(`${
|
|
4535
|
+
console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
|
|
4480
4536
|
conn.connected = false;
|
|
4481
4537
|
conn.sock = null;
|
|
4482
4538
|
connectWithReconnect(conn).catch((err) => {
|
|
4483
|
-
console.error(`${
|
|
4539
|
+
console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
4484
4540
|
});
|
|
4485
4541
|
}
|
|
4486
4542
|
});
|
|
@@ -4489,7 +4545,7 @@ async function getGroupMeta(conn, jid) {
|
|
|
4489
4545
|
const cached = conn.groupMetaCache.get(jid);
|
|
4490
4546
|
if (cached && cached.expires > Date.now()) return cached;
|
|
4491
4547
|
if (!conn.sock) return null;
|
|
4492
|
-
console.error(`${
|
|
4548
|
+
console.error(`${TAG13} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
|
|
4493
4549
|
try {
|
|
4494
4550
|
const meta = await conn.sock.groupMetadata(jid);
|
|
4495
4551
|
const participants = await Promise.all(
|
|
@@ -4504,12 +4560,12 @@ async function getGroupMeta(conn, jid) {
|
|
|
4504
4560
|
};
|
|
4505
4561
|
conn.groupMetaCache.set(jid, entry);
|
|
4506
4562
|
console.error(
|
|
4507
|
-
`${
|
|
4563
|
+
`${TAG13} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
4508
4564
|
);
|
|
4509
4565
|
return entry;
|
|
4510
4566
|
} catch (err) {
|
|
4511
4567
|
console.error(
|
|
4512
|
-
`${
|
|
4568
|
+
`${TAG13} group metadata fetch failed for ${jid}: ${err instanceof Error ? err.message : String(err)}, caching empty entry for ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
4513
4569
|
);
|
|
4514
4570
|
const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
|
|
4515
4571
|
conn.groupMetaCache.set(jid, emptyEntry);
|
|
@@ -4520,7 +4576,7 @@ function monitorInbound(conn) {
|
|
|
4520
4576
|
if (!conn.sock || !onInboundMessage) return;
|
|
4521
4577
|
const sock = conn.sock;
|
|
4522
4578
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
4523
|
-
console.error(`${
|
|
4579
|
+
console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
4524
4580
|
conn.debouncer = createInboundDebouncer({
|
|
4525
4581
|
debounceMs,
|
|
4526
4582
|
buildKey: (payload) => {
|
|
@@ -4533,7 +4589,7 @@ function monitorInbound(conn) {
|
|
|
4533
4589
|
onInboundMessage(entries[0]);
|
|
4534
4590
|
return;
|
|
4535
4591
|
}
|
|
4536
|
-
console.error(`${
|
|
4592
|
+
console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
4537
4593
|
const last = entries[entries.length - 1];
|
|
4538
4594
|
const mediaEntry = entries.find((e) => e.mediaPath);
|
|
4539
4595
|
const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
|
|
@@ -4546,7 +4602,7 @@ function monitorInbound(conn) {
|
|
|
4546
4602
|
});
|
|
4547
4603
|
},
|
|
4548
4604
|
onError: (err) => {
|
|
4549
|
-
console.error(`${
|
|
4605
|
+
console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
4550
4606
|
}
|
|
4551
4607
|
});
|
|
4552
4608
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -4574,7 +4630,7 @@ function monitorInbound(conn) {
|
|
|
4574
4630
|
});
|
|
4575
4631
|
const entries = messageStore.get(storeKey);
|
|
4576
4632
|
console.error(
|
|
4577
|
-
`${
|
|
4633
|
+
`${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
4578
4634
|
);
|
|
4579
4635
|
recordActivity({
|
|
4580
4636
|
accountId: conn.accountId,
|
|
@@ -4597,23 +4653,31 @@ function monitorInbound(conn) {
|
|
|
4597
4653
|
isOwnerMirror: fromMe && !isGroup
|
|
4598
4654
|
});
|
|
4599
4655
|
if (msg.key.id) {
|
|
4600
|
-
await
|
|
4656
|
+
const merged = await ensureWhatsAppConversation({
|
|
4601
4657
|
accountId: conn.accountId,
|
|
4602
|
-
remoteJid,
|
|
4603
4658
|
sessionKey,
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
senderPhone,
|
|
4607
|
-
selfPhone: conn.selfPhone,
|
|
4608
|
-
body: extracted.text,
|
|
4609
|
-
timestamp: ts,
|
|
4610
|
-
pushName: msg.pushName ?? void 0,
|
|
4611
|
-
quoted: extracted.quotedMessage ? {
|
|
4612
|
-
id: extracted.quotedMessage.id,
|
|
4613
|
-
body: extracted.quotedMessage.text,
|
|
4614
|
-
sender: extracted.quotedMessage.sender
|
|
4615
|
-
} : void 0
|
|
4659
|
+
agentType: sessionKeyAgentType,
|
|
4660
|
+
groupJid: isGroup ? remoteJid : void 0
|
|
4616
4661
|
});
|
|
4662
|
+
if (merged) {
|
|
4663
|
+
await persistWhatsAppMessage({
|
|
4664
|
+
accountId: conn.accountId,
|
|
4665
|
+
remoteJid,
|
|
4666
|
+
sessionKey,
|
|
4667
|
+
msgKeyId: msg.key.id,
|
|
4668
|
+
fromMe,
|
|
4669
|
+
senderPhone,
|
|
4670
|
+
selfPhone: conn.selfPhone,
|
|
4671
|
+
body: extracted.text,
|
|
4672
|
+
timestamp: ts,
|
|
4673
|
+
pushName: msg.pushName ?? void 0,
|
|
4674
|
+
quoted: extracted.quotedMessage ? {
|
|
4675
|
+
id: extracted.quotedMessage.id,
|
|
4676
|
+
body: extracted.quotedMessage.text,
|
|
4677
|
+
sender: extracted.quotedMessage.sender
|
|
4678
|
+
} : void 0
|
|
4679
|
+
});
|
|
4680
|
+
}
|
|
4617
4681
|
}
|
|
4618
4682
|
}
|
|
4619
4683
|
if (upsert.type === "append") {
|
|
@@ -4627,13 +4691,13 @@ function monitorInbound(conn) {
|
|
|
4627
4691
|
);
|
|
4628
4692
|
}
|
|
4629
4693
|
console.error(
|
|
4630
|
-
`${
|
|
4694
|
+
`${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
4631
4695
|
);
|
|
4632
4696
|
continue;
|
|
4633
4697
|
}
|
|
4634
4698
|
await handleInboundMessage(conn, msg);
|
|
4635
4699
|
} catch (err) {
|
|
4636
|
-
console.error(`${
|
|
4700
|
+
console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
4637
4701
|
}
|
|
4638
4702
|
}
|
|
4639
4703
|
});
|
|
@@ -4643,31 +4707,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4643
4707
|
const remoteJid = msg.key.remoteJid;
|
|
4644
4708
|
if (!remoteJid) return;
|
|
4645
4709
|
if (remoteJid === "status@broadcast") {
|
|
4646
|
-
console.error(`${
|
|
4710
|
+
console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
|
|
4647
4711
|
return;
|
|
4648
4712
|
}
|
|
4649
4713
|
if (!msg.message) {
|
|
4650
|
-
console.error(`${
|
|
4714
|
+
console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
4651
4715
|
return;
|
|
4652
4716
|
}
|
|
4653
4717
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
4654
4718
|
if (isDuplicateInbound(dedupKey)) {
|
|
4655
|
-
console.error(`${
|
|
4719
|
+
console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
4656
4720
|
return;
|
|
4657
4721
|
}
|
|
4658
4722
|
if (msg.key.fromMe) {
|
|
4659
4723
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
4660
|
-
console.error(`${
|
|
4724
|
+
console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
4661
4725
|
return;
|
|
4662
4726
|
}
|
|
4663
4727
|
const extracted2 = extractMessage(msg);
|
|
4664
4728
|
if (!extracted2.text) {
|
|
4665
|
-
console.error(`${
|
|
4729
|
+
console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
4666
4730
|
return;
|
|
4667
4731
|
}
|
|
4668
4732
|
const isGroup2 = isGroupJid(remoteJid);
|
|
4669
4733
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
4670
|
-
console.error(`${
|
|
4734
|
+
console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
4671
4735
|
const reply2 = async (text) => {
|
|
4672
4736
|
const currentSock = conn.sock;
|
|
4673
4737
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -4695,7 +4759,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4695
4759
|
}
|
|
4696
4760
|
const extracted = extractMessage(msg);
|
|
4697
4761
|
if (!extracted.text && !extracted.mediaType) {
|
|
4698
|
-
console.error(`${
|
|
4762
|
+
console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
4699
4763
|
return;
|
|
4700
4764
|
}
|
|
4701
4765
|
let mediaResult;
|
|
@@ -4705,7 +4769,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4705
4769
|
maxBytes: maxMb * 1024 * 1024
|
|
4706
4770
|
});
|
|
4707
4771
|
if (!mediaResult) {
|
|
4708
|
-
console.error(`${
|
|
4772
|
+
console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
4709
4773
|
}
|
|
4710
4774
|
}
|
|
4711
4775
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -4750,7 +4814,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4750
4814
|
});
|
|
4751
4815
|
}
|
|
4752
4816
|
console.error(
|
|
4753
|
-
`${
|
|
4817
|
+
`${TAG13} inbound account=${conn.accountId} from=${senderPhone} group=${isGroup} access=${accessResult.allowed ? "allowed" : "blocked"}(${accessResult.reason}) agent=${accessResult.agentType}` + (extracted.mediaType ? ` media=${extracted.mediaType}` : "") + (mediaResult ? ` mediaPath=${mediaResult.path}` : "") + (extracted.quotedMessage ? ` replyTo=${extracted.quotedMessage.id}` : "")
|
|
4754
4818
|
);
|
|
4755
4819
|
if (!accessResult.allowed) return;
|
|
4756
4820
|
let groupSubject;
|
|
@@ -4793,15 +4857,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4793
4857
|
if (accessResult.agentType === "public") {
|
|
4794
4858
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
4795
4859
|
if (!hoursResult.open) {
|
|
4796
|
-
console.error(`${
|
|
4860
|
+
console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
|
|
4797
4861
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
4798
4862
|
if (afterHoursMessage) {
|
|
4799
4863
|
try {
|
|
4800
4864
|
await reply(afterHoursMessage);
|
|
4801
|
-
console.error(`${
|
|
4865
|
+
console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
4802
4866
|
} catch (err) {
|
|
4803
4867
|
console.error(
|
|
4804
|
-
`${
|
|
4868
|
+
`${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4805
4869
|
);
|
|
4806
4870
|
}
|
|
4807
4871
|
}
|
|
@@ -5387,7 +5451,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
5387
5451
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
5388
5452
|
import { tmpdir } from "os";
|
|
5389
5453
|
import { join as join5 } from "path";
|
|
5390
|
-
var
|
|
5454
|
+
var TAG14 = "[voice]";
|
|
5391
5455
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5392
5456
|
"audio/ogg",
|
|
5393
5457
|
"audio/webm",
|
|
@@ -5419,7 +5483,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5419
5483
|
const bytes = file.size;
|
|
5420
5484
|
const mimeType = file.type;
|
|
5421
5485
|
console.error(
|
|
5422
|
-
`${
|
|
5486
|
+
`${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
5423
5487
|
);
|
|
5424
5488
|
let tempDir;
|
|
5425
5489
|
let tempPath;
|
|
@@ -5431,7 +5495,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5431
5495
|
await writeFile3(tempPath, buffer);
|
|
5432
5496
|
} catch (err) {
|
|
5433
5497
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5434
|
-
console.error(`${
|
|
5498
|
+
console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
|
|
5435
5499
|
return { ok: false, error: "Could not process voice note" };
|
|
5436
5500
|
}
|
|
5437
5501
|
try {
|
|
@@ -5439,7 +5503,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5439
5503
|
if (!sttResult) {
|
|
5440
5504
|
const elapsed2 = Date.now() - startMs;
|
|
5441
5505
|
console.error(
|
|
5442
|
-
`${
|
|
5506
|
+
`${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
5443
5507
|
);
|
|
5444
5508
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
5445
5509
|
}
|
|
@@ -5447,7 +5511,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5447
5511
|
const elapsed = Date.now() - startMs;
|
|
5448
5512
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
5449
5513
|
console.error(
|
|
5450
|
-
`${
|
|
5514
|
+
`${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
5451
5515
|
);
|
|
5452
5516
|
return {
|
|
5453
5517
|
ok: true,
|
|
@@ -5457,7 +5521,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5457
5521
|
const elapsed = Date.now() - startMs;
|
|
5458
5522
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5459
5523
|
console.error(
|
|
5460
|
-
`${
|
|
5524
|
+
`${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
5461
5525
|
);
|
|
5462
5526
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
5463
5527
|
} finally {
|
|
@@ -5492,7 +5556,7 @@ var VERDICT_DEFINITIONS = {
|
|
|
5492
5556
|
};
|
|
5493
5557
|
|
|
5494
5558
|
// app/lib/inbound-gateway.ts
|
|
5495
|
-
var
|
|
5559
|
+
var TAG15 = "[inbound-gateway]";
|
|
5496
5560
|
var GATEWAY_TIMEOUT_MS = 1e4;
|
|
5497
5561
|
var MIN_WORDS_FOR_PROCESSING = 5;
|
|
5498
5562
|
function defaultResult(rawText, latencyMs) {
|
|
@@ -5615,11 +5679,11 @@ async function processInbound(rawText, channel) {
|
|
|
5615
5679
|
};
|
|
5616
5680
|
result.fallthrough = false;
|
|
5617
5681
|
console.warn(
|
|
5618
|
-
`${
|
|
5682
|
+
`${TAG15} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
|
|
5619
5683
|
);
|
|
5620
5684
|
} else {
|
|
5621
5685
|
console.log(
|
|
5622
|
-
`${
|
|
5686
|
+
`${TAG15} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
|
|
5623
5687
|
);
|
|
5624
5688
|
}
|
|
5625
5689
|
return result;
|
|
@@ -5636,13 +5700,13 @@ async function processInbound(rawText, channel) {
|
|
|
5636
5700
|
const latencyMs = Date.now() - startMs;
|
|
5637
5701
|
if (llmResult.kind === "fallback") {
|
|
5638
5702
|
console.warn(
|
|
5639
|
-
`${
|
|
5703
|
+
`${TAG15} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
|
|
5640
5704
|
);
|
|
5641
5705
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5642
5706
|
}
|
|
5643
5707
|
if (llmResult.kind !== "ok-tool") {
|
|
5644
5708
|
console.warn(
|
|
5645
|
-
`${
|
|
5709
|
+
`${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
|
|
5646
5710
|
);
|
|
5647
5711
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5648
5712
|
}
|
|
@@ -5652,7 +5716,7 @@ async function processInbound(rawText, channel) {
|
|
|
5652
5716
|
const verdict = input.verdict;
|
|
5653
5717
|
if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
|
|
5654
5718
|
console.warn(
|
|
5655
|
-
`${
|
|
5719
|
+
`${TAG15} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
|
|
5656
5720
|
);
|
|
5657
5721
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5658
5722
|
}
|
|
@@ -5680,18 +5744,18 @@ async function processInbound(rawText, channel) {
|
|
|
5680
5744
|
fallthrough: false
|
|
5681
5745
|
};
|
|
5682
5746
|
console.log(
|
|
5683
|
-
`${
|
|
5747
|
+
`${TAG15} channel=${channel} verdict=${verdict} promptInjection=${promptInjectionRisk} intent=${intent} language=${language} complexity=${complexity} requiresHistory=${requiresHistory} rewrite=${!isAdmin && processedText !== rawText.trim()} searchQuery=${searchQuery ? "yes" : "null"} latency_ms=${latencyMs}`
|
|
5684
5748
|
);
|
|
5685
5749
|
if (verdict !== "clean") {
|
|
5686
5750
|
console.warn(
|
|
5687
|
-
`${
|
|
5751
|
+
`${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
|
|
5688
5752
|
);
|
|
5689
5753
|
}
|
|
5690
5754
|
return result;
|
|
5691
5755
|
} catch (err) {
|
|
5692
5756
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5693
5757
|
console.warn(
|
|
5694
|
-
`${
|
|
5758
|
+
`${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
|
|
5695
5759
|
);
|
|
5696
5760
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5697
5761
|
}
|
|
@@ -6784,7 +6848,7 @@ function checkTelegramAccess(params) {
|
|
|
6784
6848
|
}
|
|
6785
6849
|
|
|
6786
6850
|
// server/routes/telegram.ts
|
|
6787
|
-
var
|
|
6851
|
+
var TAG16 = "[telegram-webhook]";
|
|
6788
6852
|
var TELEGRAM_API = "https://api.telegram.org";
|
|
6789
6853
|
function getWebhookSecret(botType) {
|
|
6790
6854
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
@@ -6808,12 +6872,12 @@ async function handleInbound(params) {
|
|
|
6808
6872
|
const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
|
|
6809
6873
|
registerSession(sessionKey, agentType, accountId);
|
|
6810
6874
|
console.error(
|
|
6811
|
-
`${
|
|
6875
|
+
`${TAG16} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
|
|
6812
6876
|
);
|
|
6813
6877
|
const gatewayResult = await processInbound(text, gatewayChannel);
|
|
6814
6878
|
if (gatewayResult.screening.verdict === "discard") {
|
|
6815
6879
|
console.error(
|
|
6816
|
-
`${
|
|
6880
|
+
`${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
|
|
6817
6881
|
);
|
|
6818
6882
|
return;
|
|
6819
6883
|
}
|
|
@@ -6836,7 +6900,7 @@ async function handleInbound(params) {
|
|
|
6836
6900
|
}
|
|
6837
6901
|
} catch (err) {
|
|
6838
6902
|
console.error(
|
|
6839
|
-
`${
|
|
6903
|
+
`${TAG16} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6840
6904
|
);
|
|
6841
6905
|
responseText = "I'm having trouble right now. Please try again in a moment.";
|
|
6842
6906
|
}
|
|
@@ -6854,12 +6918,12 @@ async function handleInbound(params) {
|
|
|
6854
6918
|
const data = await res.json();
|
|
6855
6919
|
if (!data.ok) {
|
|
6856
6920
|
console.error(
|
|
6857
|
-
`${
|
|
6921
|
+
`${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
|
|
6858
6922
|
);
|
|
6859
6923
|
}
|
|
6860
6924
|
} catch (err) {
|
|
6861
6925
|
console.error(
|
|
6862
|
-
`${
|
|
6926
|
+
`${TAG16} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6863
6927
|
);
|
|
6864
6928
|
}
|
|
6865
6929
|
}
|
|
@@ -6867,28 +6931,28 @@ var app6 = new Hono();
|
|
|
6867
6931
|
app6.post("/webhook", async (c) => {
|
|
6868
6932
|
const botType = c.req.query("bot");
|
|
6869
6933
|
if (!botType || botType !== "public" && botType !== "admin") {
|
|
6870
|
-
console.error(`${
|
|
6934
|
+
console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
|
|
6871
6935
|
return c.json({ error: "Missing or invalid bot type" }, 400);
|
|
6872
6936
|
}
|
|
6873
6937
|
const storedSecret = getWebhookSecret(botType);
|
|
6874
6938
|
if (!storedSecret) {
|
|
6875
|
-
console.error(`${
|
|
6939
|
+
console.error(`${TAG16} secret=missing botType=${botType}`);
|
|
6876
6940
|
return c.json({ error: "Webhook not configured" }, 401);
|
|
6877
6941
|
}
|
|
6878
6942
|
const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
|
|
6879
6943
|
if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
|
|
6880
|
-
console.error(`${
|
|
6944
|
+
console.error(`${TAG16} secret=invalid botType=${botType}`);
|
|
6881
6945
|
return c.json({ error: "Unauthorized" }, 401);
|
|
6882
6946
|
}
|
|
6883
6947
|
const account = resolveAccount();
|
|
6884
6948
|
if (!account) {
|
|
6885
|
-
console.error(`${
|
|
6949
|
+
console.error(`${TAG16} config=no-account`);
|
|
6886
6950
|
return c.json({ error: "No account configured" }, 500);
|
|
6887
6951
|
}
|
|
6888
6952
|
const tgConfig = account.config.telegram ?? {};
|
|
6889
6953
|
const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
|
|
6890
6954
|
if (!botToken) {
|
|
6891
|
-
console.error(`${
|
|
6955
|
+
console.error(`${TAG16} config=no-token botType=${botType}`);
|
|
6892
6956
|
return c.json({ error: `No ${botType} bot token configured` }, 500);
|
|
6893
6957
|
}
|
|
6894
6958
|
let update;
|
|
@@ -6916,7 +6980,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6916
6980
|
}
|
|
6917
6981
|
const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
|
|
6918
6982
|
console.error(
|
|
6919
|
-
`${
|
|
6983
|
+
`${TAG16} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
|
|
6920
6984
|
);
|
|
6921
6985
|
if (!accessResult.allowed) {
|
|
6922
6986
|
return c.json({ ok: true });
|
|
@@ -6927,7 +6991,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6927
6991
|
headers: { "Content-Type": "application/json" },
|
|
6928
6992
|
body: JSON.stringify({ callback_query_id: callbackId })
|
|
6929
6993
|
}).catch((err) => {
|
|
6930
|
-
console.error(`${
|
|
6994
|
+
console.error(`${TAG16} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6931
6995
|
});
|
|
6932
6996
|
}
|
|
6933
6997
|
handleInbound({
|
|
@@ -6940,7 +7004,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6940
7004
|
agentType: accessResult.agentType
|
|
6941
7005
|
}).catch((err) => {
|
|
6942
7006
|
console.error(
|
|
6943
|
-
`${
|
|
7007
|
+
`${TAG16} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6944
7008
|
);
|
|
6945
7009
|
});
|
|
6946
7010
|
return c.json({ ok: true });
|
|
@@ -6954,14 +7018,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
|
|
|
6954
7018
|
|
|
6955
7019
|
// app/lib/whatsapp/login.ts
|
|
6956
7020
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6957
|
-
var
|
|
7021
|
+
var TAG17 = "[whatsapp:login]";
|
|
6958
7022
|
var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
|
|
6959
7023
|
var activeLogins = /* @__PURE__ */ new Map();
|
|
6960
7024
|
function closeSocket(sock) {
|
|
6961
7025
|
try {
|
|
6962
7026
|
sock.ws?.close?.();
|
|
6963
7027
|
} catch (err) {
|
|
6964
|
-
console.warn(`${
|
|
7028
|
+
console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
|
|
6965
7029
|
}
|
|
6966
7030
|
}
|
|
6967
7031
|
function resetActiveLogin(accountId) {
|
|
@@ -6984,7 +7048,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6984
7048
|
const current = activeLogins.get(accountId);
|
|
6985
7049
|
if (current?.id === login.id) {
|
|
6986
7050
|
current.connected = true;
|
|
6987
|
-
console.error(`${
|
|
7051
|
+
console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
|
|
6988
7052
|
}
|
|
6989
7053
|
return;
|
|
6990
7054
|
} catch (err) {
|
|
@@ -6994,7 +7058,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6994
7058
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6995
7059
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6996
7060
|
console.error(
|
|
6997
|
-
`${
|
|
7061
|
+
`${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6998
7062
|
);
|
|
6999
7063
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
7000
7064
|
} else {
|
|
@@ -7006,7 +7070,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7006
7070
|
attempt++;
|
|
7007
7071
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
7008
7072
|
console.error(
|
|
7009
|
-
`${
|
|
7073
|
+
`${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
7010
7074
|
);
|
|
7011
7075
|
closeSocket(current.sock);
|
|
7012
7076
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -7017,7 +7081,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7017
7081
|
current.sock = newSock;
|
|
7018
7082
|
} catch (sockErr) {
|
|
7019
7083
|
console.error(
|
|
7020
|
-
`${
|
|
7084
|
+
`${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
7021
7085
|
);
|
|
7022
7086
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
7023
7087
|
return;
|
|
@@ -7031,7 +7095,7 @@ async function startLogin(opts) {
|
|
|
7031
7095
|
const hasAuth = await authExists(authDir);
|
|
7032
7096
|
const selfId = readSelfId(authDir);
|
|
7033
7097
|
console.error(
|
|
7034
|
-
`${
|
|
7098
|
+
`${TAG17} startLogin account=${accountId} force=${!!force} hasAuth=${hasAuth}` + (existing0 ? ` activeLogin={id=${existing0.id.slice(0, 8)},age=${Math.round((Date.now() - existing0.startedAt) / 1e3)}s,hasQr=${!!existing0.qr}}` : " activeLogin=none")
|
|
7035
7099
|
);
|
|
7036
7100
|
if (hasAuth && !force) {
|
|
7037
7101
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -7043,7 +7107,7 @@ async function startLogin(opts) {
|
|
|
7043
7107
|
await clearAuth(authDir);
|
|
7044
7108
|
const existing = activeLogins.get(accountId);
|
|
7045
7109
|
if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
|
|
7046
|
-
console.error(`${
|
|
7110
|
+
console.error(`${TAG17} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
7047
7111
|
return {
|
|
7048
7112
|
qrDataUrl: existing.qrDataUrl,
|
|
7049
7113
|
qrRaw: existing.qr,
|
|
@@ -7051,7 +7115,7 @@ async function startLogin(opts) {
|
|
|
7051
7115
|
};
|
|
7052
7116
|
}
|
|
7053
7117
|
if (existing) {
|
|
7054
|
-
console.error(`${
|
|
7118
|
+
console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
|
|
7055
7119
|
}
|
|
7056
7120
|
resetActiveLogin(accountId);
|
|
7057
7121
|
let resolveQr = null;
|
|
@@ -7073,14 +7137,14 @@ async function startLogin(opts) {
|
|
|
7073
7137
|
onQr: (qr2) => {
|
|
7074
7138
|
loginQrCount++;
|
|
7075
7139
|
if (pendingQr) {
|
|
7076
|
-
console.error(`${
|
|
7140
|
+
console.error(`${TAG17} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
7077
7141
|
return;
|
|
7078
7142
|
}
|
|
7079
7143
|
pendingQr = qr2;
|
|
7080
7144
|
const current = activeLogins.get(accountId);
|
|
7081
7145
|
if (current && !current.qr) current.qr = qr2;
|
|
7082
7146
|
clearTimeout(qrTimer);
|
|
7083
|
-
console.error(`${
|
|
7147
|
+
console.error(`${TAG17} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
7084
7148
|
resolveQr?.(qr2);
|
|
7085
7149
|
}
|
|
7086
7150
|
});
|
|
@@ -7100,7 +7164,7 @@ async function startLogin(opts) {
|
|
|
7100
7164
|
activeLogins.set(accountId, login);
|
|
7101
7165
|
if (pendingQr && !login.qr) login.qr = pendingQr;
|
|
7102
7166
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
7103
|
-
console.error(`${
|
|
7167
|
+
console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
7104
7168
|
const current = activeLogins.get(accountId);
|
|
7105
7169
|
if (current?.id === login.id) {
|
|
7106
7170
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -7125,7 +7189,7 @@ async function waitForLogin(opts) {
|
|
|
7125
7189
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
7126
7190
|
const login = activeLogins.get(accountId);
|
|
7127
7191
|
console.error(
|
|
7128
|
-
`${
|
|
7192
|
+
`${TAG17} waitForLogin account=${accountId} timeout=${timeoutMs}ms` + (login ? ` login={id=${login.id.slice(0, 8)},age=${Math.round((Date.now() - login.startedAt) / 1e3)}s,connected=${login.connected},hasQr=${!!login.qr}}` : " login=none")
|
|
7129
7193
|
);
|
|
7130
7194
|
if (!login) {
|
|
7131
7195
|
return { connected: false, message: "No active WhatsApp login in progress." };
|
|
@@ -7138,7 +7202,7 @@ async function waitForLogin(opts) {
|
|
|
7138
7202
|
while (Date.now() < deadline) {
|
|
7139
7203
|
if (login.connected) {
|
|
7140
7204
|
const selfId = readSelfId(login.authDir);
|
|
7141
|
-
console.error(`${
|
|
7205
|
+
console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
7142
7206
|
const sock = login.sock;
|
|
7143
7207
|
const authDir = login.authDir;
|
|
7144
7208
|
activeLogins.delete(accountId);
|
|
@@ -7158,7 +7222,7 @@ async function waitForLogin(opts) {
|
|
|
7158
7222
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
7159
7223
|
}
|
|
7160
7224
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
7161
|
-
console.error(`${
|
|
7225
|
+
console.error(`${TAG17} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
|
|
7162
7226
|
resetActiveLogin(accountId);
|
|
7163
7227
|
return { connected: false, message: "Login timed out. Try generating a new QR." };
|
|
7164
7228
|
}
|
|
@@ -7272,17 +7336,17 @@ function serializeWhatsAppSchema() {
|
|
|
7272
7336
|
}
|
|
7273
7337
|
|
|
7274
7338
|
// server/routes/whatsapp.ts
|
|
7275
|
-
var
|
|
7339
|
+
var TAG18 = "[whatsapp:api]";
|
|
7276
7340
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
7277
7341
|
var app7 = new Hono();
|
|
7278
7342
|
app7.get("/status", (c) => {
|
|
7279
7343
|
try {
|
|
7280
7344
|
const status = getStatus();
|
|
7281
7345
|
const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
|
|
7282
|
-
console.error(`${
|
|
7346
|
+
console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
|
|
7283
7347
|
return c.json({ accounts: status });
|
|
7284
7348
|
} catch (err) {
|
|
7285
|
-
console.error(`${
|
|
7349
|
+
console.error(`${TAG18} status error: ${String(err)}`);
|
|
7286
7350
|
return c.json({ error: String(err) }, 500);
|
|
7287
7351
|
}
|
|
7288
7352
|
});
|
|
@@ -7293,10 +7357,10 @@ app7.post("/login/start", async (c) => {
|
|
|
7293
7357
|
const force = body.force ?? false;
|
|
7294
7358
|
const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
7295
7359
|
const result = await startLogin({ accountId, authDir, force });
|
|
7296
|
-
console.error(`${
|
|
7360
|
+
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
7297
7361
|
return c.json(result);
|
|
7298
7362
|
} catch (err) {
|
|
7299
|
-
console.error(`${
|
|
7363
|
+
console.error(`${TAG18} login/start error: ${String(err)}`);
|
|
7300
7364
|
return c.json({ error: String(err) }, 500);
|
|
7301
7365
|
}
|
|
7302
7366
|
});
|
|
@@ -7311,7 +7375,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7311
7375
|
try {
|
|
7312
7376
|
await registerLoginSocket(accountId, result.sock, result.authDir);
|
|
7313
7377
|
} catch (regErr) {
|
|
7314
|
-
console.error(`${
|
|
7378
|
+
console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
|
|
7315
7379
|
}
|
|
7316
7380
|
try {
|
|
7317
7381
|
const account = resolveAccount();
|
|
@@ -7319,16 +7383,16 @@ app7.post("/login/wait", async (c) => {
|
|
|
7319
7383
|
const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
|
|
7320
7384
|
configPersisted = persistResult.ok;
|
|
7321
7385
|
if (!persistResult.ok) {
|
|
7322
|
-
console.error(`${
|
|
7386
|
+
console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
|
|
7323
7387
|
}
|
|
7324
7388
|
} else {
|
|
7325
|
-
console.error(`${
|
|
7389
|
+
console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
|
|
7326
7390
|
}
|
|
7327
7391
|
} catch (persistErr) {
|
|
7328
|
-
console.error(`${
|
|
7392
|
+
console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
|
|
7329
7393
|
}
|
|
7330
7394
|
}
|
|
7331
|
-
console.error(`${
|
|
7395
|
+
console.error(`${TAG18} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
|
|
7332
7396
|
return c.json({
|
|
7333
7397
|
connected: result.connected,
|
|
7334
7398
|
message: result.message,
|
|
@@ -7336,7 +7400,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7336
7400
|
configPersisted
|
|
7337
7401
|
});
|
|
7338
7402
|
} catch (err) {
|
|
7339
|
-
console.error(`${
|
|
7403
|
+
console.error(`${TAG18} login/wait error: ${String(err)}`);
|
|
7340
7404
|
return c.json({ error: String(err) }, 500);
|
|
7341
7405
|
}
|
|
7342
7406
|
});
|
|
@@ -7347,7 +7411,7 @@ app7.post("/disconnect", async (c) => {
|
|
|
7347
7411
|
await stopConnection(accountId);
|
|
7348
7412
|
return c.json({ disconnected: true, accountId });
|
|
7349
7413
|
} catch (err) {
|
|
7350
|
-
console.error(`${
|
|
7414
|
+
console.error(`${TAG18} disconnect error: ${String(err)}`);
|
|
7351
7415
|
return c.json({ error: String(err) }, 500);
|
|
7352
7416
|
}
|
|
7353
7417
|
});
|
|
@@ -7358,7 +7422,7 @@ app7.post("/reconnect", async (c) => {
|
|
|
7358
7422
|
await startConnection(accountId);
|
|
7359
7423
|
return c.json({ reconnecting: true, accountId });
|
|
7360
7424
|
} catch (err) {
|
|
7361
|
-
console.error(`${
|
|
7425
|
+
console.error(`${TAG18} reconnect error: ${String(err)}`);
|
|
7362
7426
|
return c.json({ error: String(err) }, 500);
|
|
7363
7427
|
}
|
|
7364
7428
|
});
|
|
@@ -7377,7 +7441,7 @@ app7.post("/send", async (c) => {
|
|
|
7377
7441
|
const result = await sendTextMessage(sock, to, text, { accountId });
|
|
7378
7442
|
return c.json(result);
|
|
7379
7443
|
} catch (err) {
|
|
7380
|
-
console.error(`${
|
|
7444
|
+
console.error(`${TAG18} send error: ${String(err)}`);
|
|
7381
7445
|
return c.json({ error: String(err) }, 500);
|
|
7382
7446
|
}
|
|
7383
7447
|
});
|
|
@@ -7398,7 +7462,7 @@ app7.post("/config", async (c) => {
|
|
|
7398
7462
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
7399
7463
|
}
|
|
7400
7464
|
const result = addAdminPhone(account.accountDir, phone);
|
|
7401
|
-
console.error(`${
|
|
7465
|
+
console.error(`${TAG18} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7402
7466
|
return c.json(result, result.ok ? 200 : 400);
|
|
7403
7467
|
}
|
|
7404
7468
|
case "remove-admin-phone": {
|
|
@@ -7406,12 +7470,12 @@ app7.post("/config", async (c) => {
|
|
|
7406
7470
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
7407
7471
|
}
|
|
7408
7472
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
7409
|
-
console.error(`${
|
|
7473
|
+
console.error(`${TAG18} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7410
7474
|
return c.json(result, result.ok ? 200 : 400);
|
|
7411
7475
|
}
|
|
7412
7476
|
case "list-admin-phones": {
|
|
7413
7477
|
const phones = readAdminPhones(account.accountDir);
|
|
7414
|
-
console.error(`${
|
|
7478
|
+
console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
|
|
7415
7479
|
return c.json({ ok: true, phones });
|
|
7416
7480
|
}
|
|
7417
7481
|
case "set-public-agent": {
|
|
@@ -7419,14 +7483,14 @@ app7.post("/config", async (c) => {
|
|
|
7419
7483
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
7420
7484
|
}
|
|
7421
7485
|
const result = setPublicAgent(account.accountDir, slug);
|
|
7422
|
-
console.error(`${
|
|
7486
|
+
console.error(`${TAG18} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
7423
7487
|
return c.json(result, result.ok ? 200 : 400);
|
|
7424
7488
|
}
|
|
7425
7489
|
case "get-public-agent": {
|
|
7426
7490
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7427
7491
|
const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
|
|
7428
7492
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
|
|
7429
|
-
console.error(`${
|
|
7493
|
+
console.error(`${TAG18} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
7430
7494
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
7431
7495
|
}
|
|
7432
7496
|
case "set-group-public-agent": {
|
|
@@ -7438,7 +7502,7 @@ app7.post("/config", async (c) => {
|
|
|
7438
7502
|
}
|
|
7439
7503
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7440
7504
|
const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
|
|
7441
|
-
console.error(`${
|
|
7505
|
+
console.error(`${TAG18} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
|
|
7442
7506
|
return c.json(result, result.ok ? 200 : 400);
|
|
7443
7507
|
}
|
|
7444
7508
|
case "unset-group-public-agent": {
|
|
@@ -7447,7 +7511,7 @@ app7.post("/config", async (c) => {
|
|
|
7447
7511
|
}
|
|
7448
7512
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7449
7513
|
const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
|
|
7450
|
-
console.error(`${
|
|
7514
|
+
console.error(`${TAG18} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
|
|
7451
7515
|
return c.json(result, result.ok ? 200 : 400);
|
|
7452
7516
|
}
|
|
7453
7517
|
case "list-public-agents": {
|
|
@@ -7464,26 +7528,26 @@ app7.post("/config", async (c) => {
|
|
|
7464
7528
|
const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
|
|
7465
7529
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
7466
7530
|
} catch {
|
|
7467
|
-
console.error(`${
|
|
7531
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
7468
7532
|
}
|
|
7469
7533
|
}
|
|
7470
7534
|
} catch (err) {
|
|
7471
|
-
console.error(`${
|
|
7535
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
7472
7536
|
}
|
|
7473
7537
|
}
|
|
7474
|
-
console.error(`${
|
|
7538
|
+
console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
|
|
7475
7539
|
return c.json({ ok: true, agents });
|
|
7476
7540
|
}
|
|
7477
7541
|
case "schema": {
|
|
7478
7542
|
const text = serializeWhatsAppSchema();
|
|
7479
|
-
console.error(`${
|
|
7543
|
+
console.error(`${TAG18} config action=schema`);
|
|
7480
7544
|
return c.json({ ok: true, text });
|
|
7481
7545
|
}
|
|
7482
7546
|
case "list-groups": {
|
|
7483
7547
|
const groupAccountId = accountId ?? "default";
|
|
7484
7548
|
const sock = getSocket(groupAccountId);
|
|
7485
7549
|
if (!sock) {
|
|
7486
|
-
console.error(`${
|
|
7550
|
+
console.error(`${TAG18} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
7487
7551
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
7488
7552
|
}
|
|
7489
7553
|
try {
|
|
@@ -7493,10 +7557,10 @@ app7.post("/config", async (c) => {
|
|
|
7493
7557
|
name: g.subject ?? g.id,
|
|
7494
7558
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
7495
7559
|
}));
|
|
7496
|
-
console.error(`${
|
|
7560
|
+
console.error(`${TAG18} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
7497
7561
|
return c.json({ ok: true, groups });
|
|
7498
7562
|
} catch (err) {
|
|
7499
|
-
console.error(`${
|
|
7563
|
+
console.error(`${TAG18} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
7500
7564
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
7501
7565
|
}
|
|
7502
7566
|
}
|
|
@@ -7506,12 +7570,12 @@ app7.post("/config", async (c) => {
|
|
|
7506
7570
|
}
|
|
7507
7571
|
const result = updateConfig(account.accountDir, fields);
|
|
7508
7572
|
const fieldNames = Object.keys(fields);
|
|
7509
|
-
console.error(`${
|
|
7573
|
+
console.error(`${TAG18} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
7510
7574
|
return c.json(result, result.ok ? 200 : 400);
|
|
7511
7575
|
}
|
|
7512
7576
|
case "get-config": {
|
|
7513
7577
|
const waConfig = getConfig(account.accountDir);
|
|
7514
|
-
console.error(`${
|
|
7578
|
+
console.error(`${TAG18} config action=get-config`);
|
|
7515
7579
|
return c.json({ ok: true, config: waConfig });
|
|
7516
7580
|
}
|
|
7517
7581
|
default:
|
|
@@ -7521,7 +7585,7 @@ app7.post("/config", async (c) => {
|
|
|
7521
7585
|
);
|
|
7522
7586
|
}
|
|
7523
7587
|
} catch (err) {
|
|
7524
|
-
console.error(`${
|
|
7588
|
+
console.error(`${TAG18} config error: ${String(err)}`);
|
|
7525
7589
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
7526
7590
|
}
|
|
7527
7591
|
});
|
|
@@ -7543,16 +7607,16 @@ app7.post("/send-document", async (c) => {
|
|
|
7543
7607
|
const accountResolved = realpathSync2(accountDir);
|
|
7544
7608
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
7545
7609
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
7546
|
-
console.error(`${
|
|
7610
|
+
console.error(`${TAG18} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
7547
7611
|
return c.json({ error: "Access denied: file is outside the account directory" }, 403);
|
|
7548
7612
|
}
|
|
7549
7613
|
} catch (err) {
|
|
7550
7614
|
const code = err.code;
|
|
7551
7615
|
if (code === "ENOENT") {
|
|
7552
|
-
console.error(`${
|
|
7616
|
+
console.error(`${TAG18} send-document ENOENT path=${filePath}`);
|
|
7553
7617
|
return c.json({ error: `File not found: ${filePath}` }, 404);
|
|
7554
7618
|
}
|
|
7555
|
-
console.error(`${
|
|
7619
|
+
console.error(`${TAG18} send-document path error: ${String(err)}`);
|
|
7556
7620
|
return c.json({ error: String(err) }, 500);
|
|
7557
7621
|
}
|
|
7558
7622
|
const fileStat = await stat3(resolvedPath);
|
|
@@ -7574,11 +7638,11 @@ app7.post("/send-document", async (c) => {
|
|
|
7574
7638
|
caption
|
|
7575
7639
|
}, { accountId });
|
|
7576
7640
|
console.error(
|
|
7577
|
-
`${
|
|
7641
|
+
`${TAG18} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
7578
7642
|
);
|
|
7579
7643
|
return c.json(result);
|
|
7580
7644
|
} catch (err) {
|
|
7581
|
-
console.error(`${
|
|
7645
|
+
console.error(`${TAG18} send-document error: ${String(err)}`);
|
|
7582
7646
|
return c.json({ error: String(err) }, 500);
|
|
7583
7647
|
}
|
|
7584
7648
|
});
|
|
@@ -7588,11 +7652,11 @@ app7.get("/activity", (c) => {
|
|
|
7588
7652
|
const result = getChannelActivity(accountId);
|
|
7589
7653
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
7590
7654
|
console.error(
|
|
7591
|
-
`${
|
|
7655
|
+
`${TAG18} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
7592
7656
|
);
|
|
7593
7657
|
return c.json(result);
|
|
7594
7658
|
} catch (err) {
|
|
7595
|
-
console.error(`${
|
|
7659
|
+
console.error(`${TAG18} activity error: ${String(err)}`);
|
|
7596
7660
|
return c.json({ error: String(err) }, 500);
|
|
7597
7661
|
}
|
|
7598
7662
|
});
|
|
@@ -7611,10 +7675,10 @@ app7.get("/conversations", (c) => {
|
|
|
7611
7675
|
};
|
|
7612
7676
|
});
|
|
7613
7677
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
7614
|
-
console.error(`${
|
|
7678
|
+
console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
|
|
7615
7679
|
return c.json({ conversations });
|
|
7616
7680
|
} catch (err) {
|
|
7617
|
-
console.error(`${
|
|
7681
|
+
console.error(`${TAG18} conversations error: ${String(err)}`);
|
|
7618
7682
|
return c.json({ error: String(err) }, 500);
|
|
7619
7683
|
}
|
|
7620
7684
|
});
|
|
@@ -7629,10 +7693,10 @@ app7.get("/messages", (c) => {
|
|
|
7629
7693
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
7630
7694
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
7631
7695
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
7632
|
-
console.error(`${
|
|
7696
|
+
console.error(`${TAG18} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
7633
7697
|
return c.json({ messages });
|
|
7634
7698
|
} catch (err) {
|
|
7635
|
-
console.error(`${
|
|
7699
|
+
console.error(`${TAG18} messages error: ${String(err)}`);
|
|
7636
7700
|
return c.json({ error: String(err) }, 500);
|
|
7637
7701
|
}
|
|
7638
7702
|
});
|
|
@@ -7644,12 +7708,12 @@ app7.get("/group-info", async (c) => {
|
|
|
7644
7708
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
7645
7709
|
}
|
|
7646
7710
|
if (!isGroupJid(jid)) {
|
|
7647
|
-
console.error(`${
|
|
7711
|
+
console.error(`${TAG18} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
7648
7712
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
7649
7713
|
}
|
|
7650
7714
|
const sock = getSocket(accountId);
|
|
7651
7715
|
if (!sock) {
|
|
7652
|
-
console.error(`${
|
|
7716
|
+
console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
|
|
7653
7717
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
7654
7718
|
}
|
|
7655
7719
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -7662,10 +7726,10 @@ app7.get("/group-info", async (c) => {
|
|
|
7662
7726
|
participantCount: meta.participants.length,
|
|
7663
7727
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
7664
7728
|
};
|
|
7665
|
-
console.error(`${
|
|
7729
|
+
console.error(`${TAG18} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
7666
7730
|
return c.json(result);
|
|
7667
7731
|
} catch (err) {
|
|
7668
|
-
console.error(`${
|
|
7732
|
+
console.error(`${TAG18} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
7669
7733
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
7670
7734
|
}
|
|
7671
7735
|
});
|
|
@@ -8030,7 +8094,8 @@ app9.post("/", async (c) => {
|
|
|
8030
8094
|
"unknown",
|
|
8031
8095
|
"graph-labels-in-graph",
|
|
8032
8096
|
"graph-default-view",
|
|
8033
|
-
"graph-nav"
|
|
8097
|
+
"graph-nav",
|
|
8098
|
+
"event"
|
|
8034
8099
|
]);
|
|
8035
8100
|
const kind = allowedKinds.has(kindRaw) ? kindRaw : "unknown";
|
|
8036
8101
|
const msg = truncate(body.msg, MAX_MSG_LEN);
|
|
@@ -8044,29 +8109,42 @@ app9.post("/", async (c) => {
|
|
|
8044
8109
|
const stackTrunc = truncate(body.stack, MAX_STACK_LEN);
|
|
8045
8110
|
const head = stackHead(body.stack);
|
|
8046
8111
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
8047
|
-
|
|
8048
|
-
|
|
8049
|
-
|
|
8050
|
-
|
|
8051
|
-
|
|
8052
|
-
|
|
8053
|
-
|
|
8054
|
-
ip
|
|
8055
|
-
|
|
8056
|
-
|
|
8057
|
-
|
|
8058
|
-
ua
|
|
8059
|
-
|
|
8060
|
-
|
|
8061
|
-
|
|
8062
|
-
|
|
8063
|
-
|
|
8064
|
-
|
|
8065
|
-
|
|
8066
|
-
|
|
8067
|
-
|
|
8068
|
-
|
|
8069
|
-
|
|
8112
|
+
if (kind === "event") {
|
|
8113
|
+
const source = typeof body.source === "string" ? truncate(body.source, 64) : "unknown";
|
|
8114
|
+
const extra = Object.entries(body).filter(([k]) => !["kind", "source", "msg", "url", "ua", "version", "stack", "filename", "lineno", "colno", "tag", "status"].includes(k)).map(([k, v]) => {
|
|
8115
|
+
const safe = typeof v === "string" ? truncate(v, 200) : typeof v === "number" || typeof v === "boolean" ? String(v) : JSON.stringify(v).slice(0, 200);
|
|
8116
|
+
return `${k}=${safe}`;
|
|
8117
|
+
}).join(" ");
|
|
8118
|
+
console.log(
|
|
8119
|
+
`[client-event] ts=${ts} ip=${ip} source=${source} version=${version || "unknown"}${extra ? " " + extra : ""}`
|
|
8120
|
+
);
|
|
8121
|
+
} else {
|
|
8122
|
+
console.error(
|
|
8123
|
+
`[client-error] ts=${ts} ip=${ip} kind=${kind} url=${JSON.stringify(url)} version=${version || "unknown"} ua=${JSON.stringify(ua)} msg=${JSON.stringify(msg)} stack-head=${JSON.stringify(head)} file-line-col=${JSON.stringify(fileLineCol)}`
|
|
8124
|
+
);
|
|
8125
|
+
}
|
|
8126
|
+
if (kind !== "event") {
|
|
8127
|
+
rotateIfNeeded();
|
|
8128
|
+
try {
|
|
8129
|
+
const payload = {
|
|
8130
|
+
ts,
|
|
8131
|
+
ip,
|
|
8132
|
+
kind,
|
|
8133
|
+
url,
|
|
8134
|
+
version,
|
|
8135
|
+
ua,
|
|
8136
|
+
msg,
|
|
8137
|
+
stack: stackTrunc,
|
|
8138
|
+
filename: file,
|
|
8139
|
+
lineno: line,
|
|
8140
|
+
colno: col,
|
|
8141
|
+
tag: typeof body.tag === "string" ? truncate(body.tag, 32) : void 0,
|
|
8142
|
+
status: typeof body.status === "number" ? body.status : void 0
|
|
8143
|
+
};
|
|
8144
|
+
appendFileSync2(CLIENT_ERRORS_LOG, JSON.stringify(payload) + "\n", "utf-8");
|
|
8145
|
+
} catch (err) {
|
|
8146
|
+
console.error(`[client-error] append failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
8147
|
+
}
|
|
8070
8148
|
}
|
|
8071
8149
|
return c.json({ ok: true });
|
|
8072
8150
|
});
|
|
@@ -8546,7 +8624,7 @@ var app11 = new Hono();
|
|
|
8546
8624
|
app11.post("/cancel", requireAdminSession, async (c) => {
|
|
8547
8625
|
const session_key = c.var.sessionKey;
|
|
8548
8626
|
try {
|
|
8549
|
-
const { interruptClient: interruptClient2 } = await import("./client-pool-
|
|
8627
|
+
const { interruptClient: interruptClient2 } = await import("./client-pool-N2Y57223.js");
|
|
8550
8628
|
await interruptClient2(session_key);
|
|
8551
8629
|
return c.json({ ok: true });
|
|
8552
8630
|
} catch (err) {
|
|
@@ -9426,16 +9504,26 @@ app17.get("/", requireAdminSession, async (c) => {
|
|
|
9426
9504
|
sessionKey: null,
|
|
9427
9505
|
name: r.name,
|
|
9428
9506
|
updatedAt: r.updatedAt,
|
|
9429
|
-
phase: "flushed"
|
|
9507
|
+
phase: "flushed",
|
|
9508
|
+
channel: r.channel
|
|
9430
9509
|
}));
|
|
9431
9510
|
const inProgressRows = inProgressRaw.map((r) => ({
|
|
9432
9511
|
conversationId: null,
|
|
9433
9512
|
sessionKey: r.sessionKey,
|
|
9434
9513
|
name: null,
|
|
9435
9514
|
updatedAt: new Date(r.createdAt).toISOString(),
|
|
9436
|
-
phase: "pre-flush"
|
|
9515
|
+
phase: "pre-flush",
|
|
9516
|
+
channel: "webchat"
|
|
9437
9517
|
}));
|
|
9438
9518
|
const sessions = [...flushedRows, ...inProgressRows].sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
|
|
9519
|
+
const channelCounts = sessions.reduce((acc, s) => {
|
|
9520
|
+
const k = s.channel ?? "unknown";
|
|
9521
|
+
acc[k] = (acc[k] ?? 0) + 1;
|
|
9522
|
+
return acc;
|
|
9523
|
+
}, {});
|
|
9524
|
+
console.error(
|
|
9525
|
+
`[conversations-list] render rows=${sessions.length} channels=${JSON.stringify(channelCounts)}`
|
|
9526
|
+
);
|
|
9439
9527
|
return c.json({ sessions });
|
|
9440
9528
|
} catch (err) {
|
|
9441
9529
|
console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9699,13 +9787,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
|
|
|
9699
9787
|
// server/routes/admin/device-browser.ts
|
|
9700
9788
|
var app19 = new Hono();
|
|
9701
9789
|
app19.post("/navigate", async (c) => {
|
|
9702
|
-
const
|
|
9790
|
+
const TAG20 = "[device-url:click]";
|
|
9703
9791
|
let body;
|
|
9704
9792
|
try {
|
|
9705
9793
|
body = await c.req.json();
|
|
9706
9794
|
} catch (err) {
|
|
9707
9795
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9708
|
-
console.error(`${
|
|
9796
|
+
console.error(`${TAG20} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
|
|
9709
9797
|
return c.json(
|
|
9710
9798
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
|
|
9711
9799
|
400
|
|
@@ -9715,7 +9803,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9715
9803
|
const intent = typeof body.intent === "string" ? body.intent : "";
|
|
9716
9804
|
const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
|
|
9717
9805
|
if (!url) {
|
|
9718
|
-
console.error(`${
|
|
9806
|
+
console.error(`${TAG20} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
|
|
9719
9807
|
return c.json(
|
|
9720
9808
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
|
|
9721
9809
|
400
|
|
@@ -9725,7 +9813,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9725
9813
|
try {
|
|
9726
9814
|
parsed = new URL(url);
|
|
9727
9815
|
} catch {
|
|
9728
|
-
console.error(`${
|
|
9816
|
+
console.error(`${TAG20} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
|
|
9729
9817
|
return c.json(
|
|
9730
9818
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
|
|
9731
9819
|
400
|
|
@@ -9733,7 +9821,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9733
9821
|
}
|
|
9734
9822
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
9735
9823
|
console.error(
|
|
9736
|
-
`${
|
|
9824
|
+
`${TAG20} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
|
|
9737
9825
|
);
|
|
9738
9826
|
return c.json(
|
|
9739
9827
|
{
|
|
@@ -9749,7 +9837,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9749
9837
|
const cdpOk = await ensureCdp(transport);
|
|
9750
9838
|
if (!cdpOk) {
|
|
9751
9839
|
console.error(
|
|
9752
|
-
`${
|
|
9840
|
+
`${TAG20} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
|
|
9753
9841
|
);
|
|
9754
9842
|
return c.json(
|
|
9755
9843
|
{
|
|
@@ -9765,7 +9853,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9765
9853
|
const browser = outcome.result === "ok" ? "vnc" : "fallback";
|
|
9766
9854
|
const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
|
|
9767
9855
|
console.error(
|
|
9768
|
-
`${
|
|
9856
|
+
`${TAG20} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
|
|
9769
9857
|
);
|
|
9770
9858
|
if (outcome.result !== "ok") {
|
|
9771
9859
|
return c.json(
|
|
@@ -9796,18 +9884,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9796
9884
|
]);
|
|
9797
9885
|
var app20 = new Hono();
|
|
9798
9886
|
app20.post("/", async (c) => {
|
|
9799
|
-
const
|
|
9887
|
+
const TAG20 = "[admin:events]";
|
|
9800
9888
|
let body;
|
|
9801
9889
|
try {
|
|
9802
9890
|
body = await c.req.json();
|
|
9803
9891
|
} catch (err) {
|
|
9804
9892
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9805
|
-
console.error(`${
|
|
9893
|
+
console.error(`${TAG20} reject reason=body-not-json detail=${detail}`);
|
|
9806
9894
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
9807
9895
|
}
|
|
9808
9896
|
const event = typeof body.event === "string" ? body.event : "";
|
|
9809
9897
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
9810
|
-
console.error(`${
|
|
9898
|
+
console.error(`${TAG20} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
9811
9899
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
9812
9900
|
}
|
|
9813
9901
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -9831,7 +9919,7 @@ var events_default = app20;
|
|
|
9831
9919
|
// server/routes/admin/cloudflare.ts
|
|
9832
9920
|
import { homedir } from "os";
|
|
9833
9921
|
import { resolve as resolve17 } from "path";
|
|
9834
|
-
import { readFileSync as
|
|
9922
|
+
import { readFileSync as readFileSync16 } from "fs";
|
|
9835
9923
|
|
|
9836
9924
|
// app/lib/dns-label.ts
|
|
9837
9925
|
var VALID_LABEL = /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/;
|
|
@@ -9869,14 +9957,131 @@ function addAliasDomain(hostname2) {
|
|
|
9869
9957
|
writeFileSync9(ALIAS_DOMAINS_PATH, JSON.stringify([...existing], null, 2) + "\n", "utf-8");
|
|
9870
9958
|
}
|
|
9871
9959
|
|
|
9960
|
+
// app/lib/action-relay-queue.ts
|
|
9961
|
+
import {
|
|
9962
|
+
existsSync as existsSync20,
|
|
9963
|
+
mkdirSync as mkdirSync9,
|
|
9964
|
+
readdirSync as readdirSync5,
|
|
9965
|
+
readFileSync as readFileSync15,
|
|
9966
|
+
unlinkSync as unlinkSync2,
|
|
9967
|
+
writeFileSync as writeFileSync10
|
|
9968
|
+
} from "fs";
|
|
9969
|
+
import { join as join9 } from "path";
|
|
9970
|
+
var TAG19 = "[action-relay-queue]";
|
|
9971
|
+
var FILE_PREFIX = "action-completion-relay-";
|
|
9972
|
+
var FILE_SUFFIX = ".json";
|
|
9973
|
+
function queueDir(accountDir) {
|
|
9974
|
+
return join9(accountDir, "queue");
|
|
9975
|
+
}
|
|
9976
|
+
function relayFilePath(accountDir, actionId) {
|
|
9977
|
+
return join9(queueDir(accountDir), `${FILE_PREFIX}${actionId}${FILE_SUFFIX}`);
|
|
9978
|
+
}
|
|
9979
|
+
function enqueueActionCompletionRelay(opts) {
|
|
9980
|
+
const dir = queueDir(opts.accountDir);
|
|
9981
|
+
mkdirSync9(dir, { recursive: true });
|
|
9982
|
+
const filePath = relayFilePath(opts.accountDir, opts.actionId);
|
|
9983
|
+
const record = {
|
|
9984
|
+
actionId: opts.actionId,
|
|
9985
|
+
conversationId: opts.conversationId,
|
|
9986
|
+
message: opts.message,
|
|
9987
|
+
queuedAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
9988
|
+
};
|
|
9989
|
+
const body = JSON.stringify(record);
|
|
9990
|
+
try {
|
|
9991
|
+
writeFileSync10(filePath, body, { flag: "wx", encoding: "utf-8" });
|
|
9992
|
+
console.log(
|
|
9993
|
+
`${TAG19} phase=enqueued actionId=${opts.actionId} conversationId=${opts.conversationId} path=${filePath} bytes=${Buffer.byteLength(body, "utf-8")}`
|
|
9994
|
+
);
|
|
9995
|
+
return { enqueued: true, path: filePath };
|
|
9996
|
+
} catch (e) {
|
|
9997
|
+
if (e.code === "EEXIST") {
|
|
9998
|
+
console.log(
|
|
9999
|
+
`${TAG19} phase=enqueue-skipped actionId=${opts.actionId} reason=already-queued`
|
|
10000
|
+
);
|
|
10001
|
+
return { enqueued: false, reason: "already-queued", path: filePath };
|
|
10002
|
+
}
|
|
10003
|
+
throw e;
|
|
10004
|
+
}
|
|
10005
|
+
}
|
|
10006
|
+
function consumeActionCompletionRelays(accountDir) {
|
|
10007
|
+
const dir = queueDir(accountDir);
|
|
10008
|
+
if (!existsSync20(dir)) return [];
|
|
10009
|
+
let entries;
|
|
10010
|
+
try {
|
|
10011
|
+
entries = readdirSync5(dir);
|
|
10012
|
+
} catch (e) {
|
|
10013
|
+
console.error(
|
|
10014
|
+
`${TAG19} phase=readdir-failed dir=${dir} error=${e instanceof Error ? e.message : String(e)}`
|
|
10015
|
+
);
|
|
10016
|
+
return [];
|
|
10017
|
+
}
|
|
10018
|
+
const records = [];
|
|
10019
|
+
for (const name of entries) {
|
|
10020
|
+
if (!name.startsWith(FILE_PREFIX) || !name.endsWith(FILE_SUFFIX)) continue;
|
|
10021
|
+
const filePath = join9(dir, name);
|
|
10022
|
+
let raw;
|
|
10023
|
+
try {
|
|
10024
|
+
raw = readFileSync15(filePath, "utf-8");
|
|
10025
|
+
} catch (e) {
|
|
10026
|
+
console.error(
|
|
10027
|
+
`${TAG19} phase=read-failed file=${name} error=${e instanceof Error ? e.message : String(e)}`
|
|
10028
|
+
);
|
|
10029
|
+
continue;
|
|
10030
|
+
}
|
|
10031
|
+
let parsed;
|
|
10032
|
+
try {
|
|
10033
|
+
parsed = JSON.parse(raw);
|
|
10034
|
+
} catch (e) {
|
|
10035
|
+
console.error(
|
|
10036
|
+
`${TAG19} phase=parse-failed file=${name} error=${e instanceof Error ? e.message : String(e)}`
|
|
10037
|
+
);
|
|
10038
|
+
continue;
|
|
10039
|
+
}
|
|
10040
|
+
if (!isActionCompletionRelay(parsed)) {
|
|
10041
|
+
console.error(
|
|
10042
|
+
`${TAG19} phase=shape-invalid file=${name} keys=${parsed && typeof parsed === "object" ? Object.keys(parsed).join(",") : "non-object"}`
|
|
10043
|
+
);
|
|
10044
|
+
continue;
|
|
10045
|
+
}
|
|
10046
|
+
records.push({ rec: parsed, filePath });
|
|
10047
|
+
}
|
|
10048
|
+
records.sort((a, b) => a.rec.queuedAt.localeCompare(b.rec.queuedAt));
|
|
10049
|
+
const out = [];
|
|
10050
|
+
const now = Date.now();
|
|
10051
|
+
for (const { rec, filePath } of records) {
|
|
10052
|
+
const queuedAtMs = Date.parse(rec.queuedAt);
|
|
10053
|
+
const ageMs = Number.isFinite(queuedAtMs) ? now - queuedAtMs : 0;
|
|
10054
|
+
out.push({ ...rec, ageMs, filePath });
|
|
10055
|
+
}
|
|
10056
|
+
return out;
|
|
10057
|
+
}
|
|
10058
|
+
function deleteConsumedRelay(filePath) {
|
|
10059
|
+
try {
|
|
10060
|
+
unlinkSync2(filePath);
|
|
10061
|
+
} catch (e) {
|
|
10062
|
+
const code = e.code;
|
|
10063
|
+
if (code !== "ENOENT") {
|
|
10064
|
+
console.error(
|
|
10065
|
+
`${TAG19} phase=unlink-failed file=${filePath} error=${e instanceof Error ? e.message : String(e)}`
|
|
10066
|
+
);
|
|
10067
|
+
}
|
|
10068
|
+
}
|
|
10069
|
+
}
|
|
10070
|
+
function isActionCompletionRelay(value) {
|
|
10071
|
+
if (!value || typeof value !== "object") return false;
|
|
10072
|
+
const v = value;
|
|
10073
|
+
return typeof v.actionId === "string" && typeof v.conversationId === "string" && typeof v.message === "string" && typeof v.queuedAt === "string";
|
|
10074
|
+
}
|
|
10075
|
+
|
|
9872
10076
|
// server/routes/admin/cloudflare.ts
|
|
10077
|
+
import { existsSync as existsSyncFs, readFileSync as readFileSyncFs } from "fs";
|
|
9873
10078
|
var SETUP_TIMEOUT_MS = 10 * 60 * 1e3;
|
|
9874
10079
|
var DOMAINS_TIMEOUT_MS = 40 * 1e3;
|
|
9875
10080
|
function loadBrandInfo() {
|
|
9876
10081
|
const platformRoot2 = process.env.MAXY_PLATFORM_ROOT ?? resolve17(process.cwd(), "..");
|
|
9877
10082
|
const brandPath = resolve17(platformRoot2, "config", "brand.json");
|
|
9878
10083
|
try {
|
|
9879
|
-
const parsed = JSON.parse(
|
|
10084
|
+
const parsed = JSON.parse(readFileSync16(brandPath, "utf-8"));
|
|
9880
10085
|
const hostname2 = typeof parsed.hostname === "string" && parsed.hostname ? parsed.hostname : "maxy";
|
|
9881
10086
|
const configDir2 = typeof parsed.configDir === "string" && parsed.configDir ? parsed.configDir : ".maxy";
|
|
9882
10087
|
return { hostname: hostname2, configDir: configDir2 };
|
|
@@ -10172,13 +10377,79 @@ actionId: ${actionId}`,
|
|
|
10172
10377
|
};
|
|
10173
10378
|
return ok(success);
|
|
10174
10379
|
});
|
|
10380
|
+
var RELAY_MAX_BODY = 8 * 1024;
|
|
10381
|
+
var RELAY_MAX_MESSAGE = 2048;
|
|
10382
|
+
var ACTION_ID_RE = /^[a-z0-9-]+$/;
|
|
10383
|
+
app21.post("/relay-completion", requireAdminSession, async (c) => {
|
|
10384
|
+
const sessionKey = c.var.sessionKey;
|
|
10385
|
+
let raw;
|
|
10386
|
+
try {
|
|
10387
|
+
raw = await c.req.text();
|
|
10388
|
+
} catch {
|
|
10389
|
+
return c.json({ ok: false, reason: "invalid-body" }, 400);
|
|
10390
|
+
}
|
|
10391
|
+
if (Buffer.byteLength(raw, "utf-8") > RELAY_MAX_BODY) {
|
|
10392
|
+
return c.json({ ok: false, reason: "body-too-large" }, 413);
|
|
10393
|
+
}
|
|
10394
|
+
let body;
|
|
10395
|
+
try {
|
|
10396
|
+
body = JSON.parse(raw);
|
|
10397
|
+
} catch {
|
|
10398
|
+
return c.json({ ok: false, reason: "invalid-json" }, 400);
|
|
10399
|
+
}
|
|
10400
|
+
if (!body || typeof body !== "object") {
|
|
10401
|
+
return c.json({ ok: false, reason: "invalid-body" }, 400);
|
|
10402
|
+
}
|
|
10403
|
+
const { actionId, message } = body;
|
|
10404
|
+
if (typeof actionId !== "string" || !ACTION_ID_RE.test(actionId) || actionId.length > 200) {
|
|
10405
|
+
console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=invalid-actionid sessionKey=${sessionKey.slice(-8)}`);
|
|
10406
|
+
return c.json({ ok: false, reason: "invalid-actionid" }, 400);
|
|
10407
|
+
}
|
|
10408
|
+
if (typeof message !== "string" || message.length === 0 || message.length > RELAY_MAX_MESSAGE) {
|
|
10409
|
+
return c.json({ ok: false, reason: "invalid-message" }, 400);
|
|
10410
|
+
}
|
|
10411
|
+
const conversationId = getConversationIdForSession(sessionKey);
|
|
10412
|
+
if (!conversationId) {
|
|
10413
|
+
console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=missing-conversation-id sessionKey=${sessionKey.slice(-8)}`);
|
|
10414
|
+
return c.json({ ok: false, reason: "missing-conversation-id" }, 400);
|
|
10415
|
+
}
|
|
10416
|
+
const account = resolveAccount();
|
|
10417
|
+
if (!account) {
|
|
10418
|
+
console.error(`[cloudflare-relay-completion] phase=enqueue-skipped reason=missing-account-dir actionId=${actionId}`);
|
|
10419
|
+
return c.json({ ok: false, reason: "missing-account-dir" }, 500);
|
|
10420
|
+
}
|
|
10421
|
+
const logPath2 = actionLogPath(actionId);
|
|
10422
|
+
let auditOutcome = "log-absent";
|
|
10423
|
+
if (existsSyncFs(logPath2)) {
|
|
10424
|
+
try {
|
|
10425
|
+
const lines = readFileSyncFs(logPath2, "utf-8").split("\n");
|
|
10426
|
+
const reconciled = reconcileCloudflareSetupFromLog(lines);
|
|
10427
|
+
auditOutcome = reconciled ? reconciled.kind : "log-incomplete";
|
|
10428
|
+
} catch (e) {
|
|
10429
|
+
auditOutcome = `read-failed:${e instanceof Error ? e.message : String(e)}`;
|
|
10430
|
+
}
|
|
10431
|
+
}
|
|
10432
|
+
console.log(`[cloudflare-relay-completion] phase=enqueue-attempt actionId=${actionId} auditOutcome=${auditOutcome} conversationId=${conversationId}`);
|
|
10433
|
+
try {
|
|
10434
|
+
enqueueActionCompletionRelay({
|
|
10435
|
+
accountDir: account.accountDir,
|
|
10436
|
+
actionId,
|
|
10437
|
+
conversationId,
|
|
10438
|
+
message
|
|
10439
|
+
});
|
|
10440
|
+
} catch (e) {
|
|
10441
|
+
console.error(`[cloudflare-relay-completion] phase=enqueue-failed actionId=${actionId} error=${e instanceof Error ? e.message : String(e)}`);
|
|
10442
|
+
return c.json({ ok: false, reason: "enqueue-failed" }, 500);
|
|
10443
|
+
}
|
|
10444
|
+
return c.body(null, 204);
|
|
10445
|
+
});
|
|
10175
10446
|
var cloudflare_default = app21;
|
|
10176
10447
|
|
|
10177
10448
|
// server/routes/admin/files.ts
|
|
10178
10449
|
import { createReadStream as createReadStream3 } from "fs";
|
|
10179
10450
|
import { readdir as readdir2, readFile as readFile4, stat as stat4, mkdir as mkdir3, writeFile as writeFile4, unlink as unlink2 } from "fs/promises";
|
|
10180
10451
|
import { realpathSync as realpathSync4 } from "fs";
|
|
10181
|
-
import { basename as basename6, dirname as dirname8, join as
|
|
10452
|
+
import { basename as basename6, dirname as dirname8, join as join10, resolve as resolve19, sep as sep2 } from "path";
|
|
10182
10453
|
import { Readable as Readable2 } from "stream";
|
|
10183
10454
|
|
|
10184
10455
|
// app/lib/data-path.ts
|
|
@@ -10536,7 +10807,7 @@ async function cascadeDeleteDocument(params) {
|
|
|
10536
10807
|
var UUID_RE3 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
10537
10808
|
async function readMeta(absDir, baseName) {
|
|
10538
10809
|
try {
|
|
10539
|
-
const raw = await readFile4(
|
|
10810
|
+
const raw = await readFile4(join10(absDir, `${baseName}.meta.json`), "utf8");
|
|
10540
10811
|
const parsed = JSON.parse(raw);
|
|
10541
10812
|
if (typeof parsed?.filename === "string") {
|
|
10542
10813
|
return { filename: parsed.filename, mimeType: typeof parsed.mimeType === "string" ? parsed.mimeType : void 0 };
|
|
@@ -10574,7 +10845,7 @@ async function readAccountNames() {
|
|
|
10574
10845
|
}
|
|
10575
10846
|
async function enrich(absolute, entry, accountNames) {
|
|
10576
10847
|
if (entry.kind === "directory" && UUID_RE3.test(entry.name)) {
|
|
10577
|
-
const meta = await readMeta(
|
|
10848
|
+
const meta = await readMeta(join10(absolute, entry.name), entry.name);
|
|
10578
10849
|
if (meta?.filename) {
|
|
10579
10850
|
entry.displayName = meta.filename;
|
|
10580
10851
|
entry.mimeType = meta.mimeType;
|
|
@@ -10633,7 +10904,7 @@ app22.get("/", requireAdminSession, async (c) => {
|
|
|
10633
10904
|
continue;
|
|
10634
10905
|
}
|
|
10635
10906
|
try {
|
|
10636
|
-
const entryPath =
|
|
10907
|
+
const entryPath = join10(absolute, name);
|
|
10637
10908
|
const s = await stat4(entryPath);
|
|
10638
10909
|
entries.push({
|
|
10639
10910
|
name,
|
|
@@ -10806,7 +11077,7 @@ app22.delete("/", requireAdminSession, async (c) => {
|
|
|
10806
11077
|
}
|
|
10807
11078
|
const dot = base.lastIndexOf(".");
|
|
10808
11079
|
const stem = dot === -1 ? base : base.slice(0, dot);
|
|
10809
|
-
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ?
|
|
11080
|
+
const sidecarPath = UUID_RE3.test(stem) && base !== `${stem}.meta.json` ? join10(dirname8(absolute), `${stem}.meta.json`) : null;
|
|
10810
11081
|
await unlink2(absolute);
|
|
10811
11082
|
if (sidecarPath) {
|
|
10812
11083
|
try {
|
|
@@ -11157,6 +11428,8 @@ function plainProperties(properties) {
|
|
|
11157
11428
|
// server/routes/admin/graph-search.ts
|
|
11158
11429
|
var DEFAULT_LIMIT = 20;
|
|
11159
11430
|
var MAX_LIMIT = 2e3;
|
|
11431
|
+
var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
|
|
11432
|
+
var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
|
|
11160
11433
|
var app23 = new Hono();
|
|
11161
11434
|
app23.get("/", requireAdminSession, async (c) => {
|
|
11162
11435
|
const sessionKey = c.var.sessionKey;
|
|
@@ -11175,13 +11448,15 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11175
11448
|
}
|
|
11176
11449
|
const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
|
|
11177
11450
|
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
|
|
11451
|
+
const wantsBodyFulltext = labels.some((l) => CONVERSATION_PARENT_LABELS.has(l));
|
|
11452
|
+
const expandedLabels = wantsBodyFulltext ? Array.from(/* @__PURE__ */ new Set([...labels, ...MESSAGE_FAMILY_LABELS])) : labels;
|
|
11178
11453
|
const started = Date.now();
|
|
11179
11454
|
const session = getSession();
|
|
11180
11455
|
try {
|
|
11181
11456
|
const res = await hybrid(session, embed, {
|
|
11182
11457
|
query: q,
|
|
11183
11458
|
accountId,
|
|
11184
|
-
labels,
|
|
11459
|
+
labels: expandedLabels,
|
|
11185
11460
|
limit,
|
|
11186
11461
|
degradeOnEmbedFailure: true
|
|
11187
11462
|
});
|
|
@@ -11190,11 +11465,59 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11190
11465
|
console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
|
|
11191
11466
|
console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
|
|
11192
11467
|
}
|
|
11468
|
+
let labelMatchCount = 0;
|
|
11469
|
+
let bodyFulltextCount = 0;
|
|
11470
|
+
let resolvedResults = res.results;
|
|
11471
|
+
if (wantsBodyFulltext) {
|
|
11472
|
+
const userLabelSet = new Set(labels);
|
|
11473
|
+
const seen = /* @__PURE__ */ new Map();
|
|
11474
|
+
for (const r of res.results) {
|
|
11475
|
+
const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
|
|
11476
|
+
if (isMessage) continue;
|
|
11477
|
+
if (r.labels.some((l) => userLabelSet.has(l))) {
|
|
11478
|
+
const existing = seen.get(r.nodeId);
|
|
11479
|
+
if (!existing || existing.score < r.score) {
|
|
11480
|
+
seen.set(r.nodeId, r);
|
|
11481
|
+
}
|
|
11482
|
+
labelMatchCount++;
|
|
11483
|
+
}
|
|
11484
|
+
}
|
|
11485
|
+
for (const r of res.results) {
|
|
11486
|
+
const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
|
|
11487
|
+
if (!isMessage) continue;
|
|
11488
|
+
const parent = r.related.find(
|
|
11489
|
+
(rel) => rel.relationship === "PART_OF" && rel.direction === "outgoing" && rel.labels.some((l) => userLabelSet.has(l))
|
|
11490
|
+
);
|
|
11491
|
+
if (!parent) continue;
|
|
11492
|
+
const existing = seen.get(parent.nodeId);
|
|
11493
|
+
if (existing) {
|
|
11494
|
+
if (existing.score < r.score) {
|
|
11495
|
+
seen.set(parent.nodeId, { ...existing, score: r.score });
|
|
11496
|
+
}
|
|
11497
|
+
} else {
|
|
11498
|
+
seen.set(parent.nodeId, {
|
|
11499
|
+
nodeId: parent.nodeId,
|
|
11500
|
+
labels: parent.labels,
|
|
11501
|
+
properties: parent.properties,
|
|
11502
|
+
score: r.score,
|
|
11503
|
+
related: []
|
|
11504
|
+
});
|
|
11505
|
+
}
|
|
11506
|
+
bodyFulltextCount++;
|
|
11507
|
+
}
|
|
11508
|
+
resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
|
|
11509
|
+
console.error(
|
|
11510
|
+
`[graph-search] label-match query="${q}" hits=${labelMatchCount}`
|
|
11511
|
+
);
|
|
11512
|
+
console.error(
|
|
11513
|
+
`[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
|
|
11514
|
+
);
|
|
11515
|
+
}
|
|
11193
11516
|
console.error(
|
|
11194
|
-
`[graph-search] query="${q}" labels=${labels.join(",")} limit=${limit} mode=${res.mode} results=${res.results.length} expand-ms=${res.expandMs} total-ms=${total}`
|
|
11517
|
+
`[graph-search] query="${q}" labels=${labels.join(",")} expanded=${expandedLabels.length > labels.length ? 1 : 0} limit=${limit} mode=${res.mode} raw-results=${res.results.length} resolved-results=${resolvedResults.length} expand-ms=${res.expandMs} total-ms=${total}`
|
|
11195
11518
|
);
|
|
11196
11519
|
return c.json({
|
|
11197
|
-
results:
|
|
11520
|
+
results: resolvedResults,
|
|
11198
11521
|
mode: res.mode,
|
|
11199
11522
|
embedError: res.embedError,
|
|
11200
11523
|
elapsedMs: total
|
|
@@ -11393,6 +11716,10 @@ async function handleDefault(c, accountId) {
|
|
|
11393
11716
|
const includeTrashed = c.req.query("includeTrashed") === "1";
|
|
11394
11717
|
const includeAgentActions = c.req.query("includeAgentActions") === "1";
|
|
11395
11718
|
const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
|
|
11719
|
+
const rawChannel = c.req.query("channel") ?? "";
|
|
11720
|
+
const channel = rawChannel.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11721
|
+
const rawMessageSublabel = c.req.query("messageSublabel") ?? "";
|
|
11722
|
+
const messageSublabel = rawMessageSublabel.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11396
11723
|
if (labels.length === 0) {
|
|
11397
11724
|
console.error(
|
|
11398
11725
|
`[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
|
|
@@ -11424,7 +11751,7 @@ async function handleDefault(c, accountId) {
|
|
|
11424
11751
|
try {
|
|
11425
11752
|
const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
|
|
11426
11753
|
const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
|
|
11427
|
-
const cypherParams = { accountId, labels, agentActionLabels };
|
|
11754
|
+
const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
|
|
11428
11755
|
const result = await session.executeRead(async (tx) => {
|
|
11429
11756
|
const countResult = await tx.run(countCypher, cypherParams);
|
|
11430
11757
|
const matchedRaw = countResult.records[0]?.get("matched");
|
|
@@ -11456,8 +11783,10 @@ async function handleDefault(c, accountId) {
|
|
|
11456
11783
|
const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
|
|
11457
11784
|
const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
|
|
11458
11785
|
const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
|
|
11786
|
+
const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
|
|
11787
|
+
const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
|
|
11459
11788
|
console.error(
|
|
11460
|
-
`[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
11789
|
+
`[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix}${channelSuffix}${sublabelSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
11461
11790
|
);
|
|
11462
11791
|
return c.json({ nodes, edges });
|
|
11463
11792
|
} catch (err) {
|
|
@@ -11553,13 +11882,22 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
11553
11882
|
}
|
|
11554
11883
|
}
|
|
11555
11884
|
}
|
|
11885
|
+
var SUBFACET_PREDICATE = `
|
|
11886
|
+
AND (size($channel) = 0
|
|
11887
|
+
OR NOT any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation'])
|
|
11888
|
+
OR n.channel IS NULL
|
|
11889
|
+
OR n.channel IN $channel)
|
|
11890
|
+
AND (size($messageSublabel) = 0
|
|
11891
|
+
OR NOT any(lbl IN labels(n) WHERE lbl IN ['Message','UserMessage','AssistantMessage','WhatsAppMessage'])
|
|
11892
|
+
OR any(lbl IN labels(n) WHERE lbl IN $messageSublabel))
|
|
11893
|
+
`;
|
|
11556
11894
|
var DEFAULT_COUNT_CYPHER = `
|
|
11557
11895
|
MATCH (n)
|
|
11558
11896
|
WHERE n.accountId = $accountId
|
|
11559
11897
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11560
11898
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11561
11899
|
AND NOT n:Trashed
|
|
11562
|
-
AND n.deletedAt IS NULL
|
|
11900
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11563
11901
|
RETURN count(n) AS matched
|
|
11564
11902
|
`;
|
|
11565
11903
|
var CONVERSATION_PROPS_PROJECTION = `CASE
|
|
@@ -11573,7 +11911,7 @@ var DEFAULT_FETCH_CYPHER = `
|
|
|
11573
11911
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11574
11912
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11575
11913
|
AND NOT n:Trashed
|
|
11576
|
-
AND n.deletedAt IS NULL
|
|
11914
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11577
11915
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11578
11916
|
UNWIND nodes AS n
|
|
11579
11917
|
OPTIONAL MATCH (n)-[r]-(m)
|
|
@@ -11595,7 +11933,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
|
|
|
11595
11933
|
WHERE n.accountId = $accountId
|
|
11596
11934
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11597
11935
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11598
|
-
AND n.deletedAt IS NULL
|
|
11936
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11599
11937
|
RETURN count(n) AS matched
|
|
11600
11938
|
`;
|
|
11601
11939
|
var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
@@ -11603,7 +11941,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
|
11603
11941
|
WHERE n.accountId = $accountId
|
|
11604
11942
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11605
11943
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11606
|
-
AND n.deletedAt IS NULL
|
|
11944
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11607
11945
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11608
11946
|
UNWIND nodes AS n
|
|
11609
11947
|
OPTIONAL MATCH (n)-[r]-(m)
|
|
@@ -12197,7 +12535,7 @@ var adherence_default = app30;
|
|
|
12197
12535
|
import neo4j3 from "neo4j-driver";
|
|
12198
12536
|
import { readFile as readFile5, readdir as readdir3, stat as stat5 } from "fs/promises";
|
|
12199
12537
|
import { resolve as resolve20, relative as relative2, isAbsolute } from "path";
|
|
12200
|
-
import { existsSync as
|
|
12538
|
+
import { existsSync as existsSync21 } from "fs";
|
|
12201
12539
|
var LIMIT = 50;
|
|
12202
12540
|
var TEXT_MIME_PREFIXES = ["text/", "application/json", "application/markdown"];
|
|
12203
12541
|
var ADMIN_AGENT_FILES = ["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"];
|
|
@@ -12345,7 +12683,7 @@ async function fetchAgentTemplateRows(accountDir) {
|
|
|
12345
12683
|
async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
12346
12684
|
const names = /* @__PURE__ */ new Set();
|
|
12347
12685
|
for (const dir of [overrideDir, bundledDir]) {
|
|
12348
|
-
if (!
|
|
12686
|
+
if (!existsSync21(dir)) continue;
|
|
12349
12687
|
try {
|
|
12350
12688
|
const entries = await readdir3(dir);
|
|
12351
12689
|
for (const entry of entries) {
|
|
@@ -12360,7 +12698,7 @@ async function unionSpecialistFilenames(overrideDir, bundledDir) {
|
|
|
12360
12698
|
}
|
|
12361
12699
|
async function readAgentTemplateRow(inp) {
|
|
12362
12700
|
let chosenPath = null;
|
|
12363
|
-
if (
|
|
12701
|
+
if (existsSync21(inp.overridePath)) {
|
|
12364
12702
|
try {
|
|
12365
12703
|
validateFilePathInAccount(inp.overridePath, inp.overrideRoot);
|
|
12366
12704
|
chosenPath = inp.overridePath;
|
|
@@ -12371,7 +12709,7 @@ async function readAgentTemplateRow(inp) {
|
|
|
12371
12709
|
);
|
|
12372
12710
|
return null;
|
|
12373
12711
|
}
|
|
12374
|
-
} else if (
|
|
12712
|
+
} else if (existsSync21(inp.bundledPath)) {
|
|
12375
12713
|
if (!isWithin(inp.bundledPath, inp.bundledRoot)) {
|
|
12376
12714
|
console.error(
|
|
12377
12715
|
`[admin/sidebar-artefacts] agent-template-read-failed agent=${inp.displayName} kind=${inp.logName} error="bundled path outside PLATFORM_ROOT"`
|
|
@@ -12413,7 +12751,7 @@ var sidebar_artefacts_default = app31;
|
|
|
12413
12751
|
// server/routes/admin/sidebar-artefact-save.ts
|
|
12414
12752
|
import { mkdir as mkdir4, readdir as readdir4, stat as stat6, writeFile as writeFile5 } from "fs/promises";
|
|
12415
12753
|
import { resolve as resolve21 } from "path";
|
|
12416
|
-
import { existsSync as
|
|
12754
|
+
import { existsSync as existsSync22 } from "fs";
|
|
12417
12755
|
var ADMIN_AGENT_FILES2 = /* @__PURE__ */ new Set(["IDENTITY.md", "SOUL.md", "KNOWLEDGE.md"]);
|
|
12418
12756
|
var UUID_RE4 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
12419
12757
|
var app32 = new Hono();
|
|
@@ -12477,7 +12815,7 @@ async function resolveSavePath(id, accountId, accountDir) {
|
|
|
12477
12815
|
}
|
|
12478
12816
|
if (UUID_RE4.test(id)) {
|
|
12479
12817
|
const dir = resolve21(ATTACHMENTS_ROOT, accountId, id);
|
|
12480
|
-
if (!
|
|
12818
|
+
if (!existsSync22(dir)) {
|
|
12481
12819
|
return { kind: "reject", status: 400, reason: "not-found" };
|
|
12482
12820
|
}
|
|
12483
12821
|
try {
|
|
@@ -12501,7 +12839,7 @@ var sidebar_artefact_save_default = app32;
|
|
|
12501
12839
|
|
|
12502
12840
|
// server/routes/admin/sidebar-artefact-content.ts
|
|
12503
12841
|
import { readFile as readFile6, readdir as readdir5 } from "fs/promises";
|
|
12504
|
-
import { existsSync as
|
|
12842
|
+
import { existsSync as existsSync23 } from "fs";
|
|
12505
12843
|
import { resolve as resolve22 } from "path";
|
|
12506
12844
|
var UUID_RE5 = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
|
|
12507
12845
|
var app33 = new Hono();
|
|
@@ -12515,7 +12853,7 @@ app33.get("/", requireAdminSession, async (c) => {
|
|
|
12515
12853
|
return new Response("Not found", { status: 404 });
|
|
12516
12854
|
}
|
|
12517
12855
|
const dir = resolve22(ATTACHMENTS_ROOT, accountId, id);
|
|
12518
|
-
if (!
|
|
12856
|
+
if (!existsSync23(dir)) {
|
|
12519
12857
|
console.error(`[admin/sidebar-artefact-content] not-found id=${id.slice(0, 8)}`);
|
|
12520
12858
|
return new Response("Not found", { status: 404 });
|
|
12521
12859
|
}
|
|
@@ -12577,7 +12915,7 @@ app34.route("/sidebar-artefact-content", sidebar_artefact_content_default);
|
|
|
12577
12915
|
var admin_default = app34;
|
|
12578
12916
|
|
|
12579
12917
|
// server/routes/sites.ts
|
|
12580
|
-
import { existsSync as
|
|
12918
|
+
import { existsSync as existsSync24, readFileSync as readFileSync17, realpathSync as realpathSync5, statSync as statSync8 } from "fs";
|
|
12581
12919
|
import { resolve as resolve23 } from "path";
|
|
12582
12920
|
var SAFE_SEG_RE = /^[a-z0-9_][a-z0-9_.-]{0,99}$/i;
|
|
12583
12921
|
var MIME = {
|
|
@@ -12644,7 +12982,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
12644
12982
|
}
|
|
12645
12983
|
let stat7;
|
|
12646
12984
|
try {
|
|
12647
|
-
stat7 =
|
|
12985
|
+
stat7 = existsSync24(filePath) ? statSync8(filePath) : null;
|
|
12648
12986
|
} catch {
|
|
12649
12987
|
stat7 = null;
|
|
12650
12988
|
}
|
|
@@ -12657,7 +12995,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
12657
12995
|
console.error(`[sites] path-traversal-rejected path=${reqPath} reason=escape status=403`);
|
|
12658
12996
|
return c.text("Forbidden", 403);
|
|
12659
12997
|
}
|
|
12660
|
-
if (!
|
|
12998
|
+
if (!existsSync24(filePath)) {
|
|
12661
12999
|
console.error(`[sites] not-found path=${reqPath} status=404`);
|
|
12662
13000
|
return c.text("Not found", 404);
|
|
12663
13001
|
}
|
|
@@ -12676,7 +13014,7 @@ app35.get("/:rel{.*}", (c) => {
|
|
|
12676
13014
|
}
|
|
12677
13015
|
let body;
|
|
12678
13016
|
try {
|
|
12679
|
-
body =
|
|
13017
|
+
body = readFileSync17(realPath);
|
|
12680
13018
|
} catch (err) {
|
|
12681
13019
|
const code = err?.code;
|
|
12682
13020
|
if (code === "EISDIR") {
|
|
@@ -12808,14 +13146,14 @@ function clientFrom(c) {
|
|
|
12808
13146
|
);
|
|
12809
13147
|
}
|
|
12810
13148
|
var PLATFORM_ROOT7 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
12811
|
-
var BRAND_JSON_PATH = PLATFORM_ROOT7 ?
|
|
13149
|
+
var BRAND_JSON_PATH = PLATFORM_ROOT7 ? join11(PLATFORM_ROOT7, "config", "brand.json") : "";
|
|
12812
13150
|
var BRAND = { productName: "Maxy", hostname: "maxy", configDir: ".maxy", domain: "getmaxy.com" };
|
|
12813
|
-
if (BRAND_JSON_PATH && !
|
|
13151
|
+
if (BRAND_JSON_PATH && !existsSync25(BRAND_JSON_PATH)) {
|
|
12814
13152
|
console.error(`[brand] WARNING: brand.json not found at ${BRAND_JSON_PATH} \u2014 using Maxy defaults`);
|
|
12815
13153
|
}
|
|
12816
|
-
if (BRAND_JSON_PATH &&
|
|
13154
|
+
if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
|
|
12817
13155
|
try {
|
|
12818
|
-
const parsed = JSON.parse(
|
|
13156
|
+
const parsed = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
|
|
12819
13157
|
BRAND = { ...BRAND, ...parsed };
|
|
12820
13158
|
} catch (err) {
|
|
12821
13159
|
console.error(`[brand] Failed to parse brand.json: ${err.message}`);
|
|
@@ -12834,11 +13172,11 @@ var brandLoginOpts = {
|
|
|
12834
13172
|
bodyFont: BRAND.defaultFonts?.body,
|
|
12835
13173
|
logoContainsName: !!BRAND.logoContainsName
|
|
12836
13174
|
};
|
|
12837
|
-
var ALIAS_DOMAINS_PATH2 =
|
|
13175
|
+
var ALIAS_DOMAINS_PATH2 = join11(homedir2(), BRAND.configDir, "alias-domains.json");
|
|
12838
13176
|
function loadAliasDomains() {
|
|
12839
13177
|
try {
|
|
12840
|
-
if (!
|
|
12841
|
-
const parsed = JSON.parse(
|
|
13178
|
+
if (!existsSync25(ALIAS_DOMAINS_PATH2)) return null;
|
|
13179
|
+
const parsed = JSON.parse(readFileSync18(ALIAS_DOMAINS_PATH2, "utf-8"));
|
|
12842
13180
|
if (!Array.isArray(parsed)) {
|
|
12843
13181
|
console.error("[alias-domains] malformed alias-domains.json \u2014 expected array");
|
|
12844
13182
|
return null;
|
|
@@ -13184,14 +13522,14 @@ app36.get("/agent-assets/:slug/:filename", (c) => {
|
|
|
13184
13522
|
console.error(`[agent-assets] path-traversal-rejected slug=${slug} file=${filename}`);
|
|
13185
13523
|
return c.text("Forbidden", 403);
|
|
13186
13524
|
}
|
|
13187
|
-
if (!
|
|
13525
|
+
if (!existsSync25(filePath)) {
|
|
13188
13526
|
console.error(`[agent-assets] serve slug=${slug} file=${filename} status=404`);
|
|
13189
13527
|
return c.text("Not found", 404);
|
|
13190
13528
|
}
|
|
13191
13529
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
13192
13530
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
13193
13531
|
console.log(`[agent-assets] serve slug=${slug} file=${filename} status=200`);
|
|
13194
|
-
const body =
|
|
13532
|
+
const body = readFileSync18(filePath);
|
|
13195
13533
|
return c.body(body, 200, {
|
|
13196
13534
|
"Content-Type": contentType,
|
|
13197
13535
|
"Cache-Control": "public, max-age=3600"
|
|
@@ -13214,14 +13552,14 @@ app36.get("/generated/:filename", (c) => {
|
|
|
13214
13552
|
console.error(`[generated] serve file=${filename} status=403`);
|
|
13215
13553
|
return c.text("Forbidden", 403);
|
|
13216
13554
|
}
|
|
13217
|
-
if (!
|
|
13555
|
+
if (!existsSync25(filePath)) {
|
|
13218
13556
|
console.error(`[generated] serve file=${filename} status=404`);
|
|
13219
13557
|
return c.text("Not found", 404);
|
|
13220
13558
|
}
|
|
13221
13559
|
const ext = "." + filename.split(".").pop()?.toLowerCase();
|
|
13222
13560
|
const contentType = IMAGE_MIME[ext] || "application/octet-stream";
|
|
13223
13561
|
console.log(`[generated] serve file=${filename} status=200`);
|
|
13224
|
-
const body =
|
|
13562
|
+
const body = readFileSync18(filePath);
|
|
13225
13563
|
return c.body(body, 200, {
|
|
13226
13564
|
"Content-Type": contentType,
|
|
13227
13565
|
"Cache-Control": "public, max-age=86400"
|
|
@@ -13231,9 +13569,9 @@ app36.route("/sites", sites_default);
|
|
|
13231
13569
|
var htmlCache = /* @__PURE__ */ new Map();
|
|
13232
13570
|
var brandLogoPath = "/brand/maxy-monochrome.png";
|
|
13233
13571
|
var brandIconPath = "/brand/maxy-monochrome.png";
|
|
13234
|
-
if (BRAND_JSON_PATH &&
|
|
13572
|
+
if (BRAND_JSON_PATH && existsSync25(BRAND_JSON_PATH)) {
|
|
13235
13573
|
try {
|
|
13236
|
-
const fullBrand = JSON.parse(
|
|
13574
|
+
const fullBrand = JSON.parse(readFileSync18(BRAND_JSON_PATH, "utf-8"));
|
|
13237
13575
|
if (fullBrand.assets?.logo) brandLogoPath = `/brand/${fullBrand.assets.logo}`;
|
|
13238
13576
|
brandIconPath = fullBrand.assets?.icon ? `/brand/${fullBrand.assets.icon}` : brandLogoPath;
|
|
13239
13577
|
} catch {
|
|
@@ -13250,9 +13588,9 @@ var brandScript = `<script>window.__BRAND__=${JSON.stringify({
|
|
|
13250
13588
|
function readInstalledVersion() {
|
|
13251
13589
|
try {
|
|
13252
13590
|
if (!PLATFORM_ROOT7) return "unknown";
|
|
13253
|
-
const versionFile =
|
|
13254
|
-
if (!
|
|
13255
|
-
const content =
|
|
13591
|
+
const versionFile = join11(PLATFORM_ROOT7, "config", `.${BRAND.hostname}-version`);
|
|
13592
|
+
if (!existsSync25(versionFile)) return "unknown";
|
|
13593
|
+
const content = readFileSync18(versionFile, "utf-8").trim();
|
|
13256
13594
|
return content || "unknown";
|
|
13257
13595
|
} catch {
|
|
13258
13596
|
return "unknown";
|
|
@@ -13293,7 +13631,7 @@ var clientErrorReporterScript = `<script>
|
|
|
13293
13631
|
function cachedHtml(file) {
|
|
13294
13632
|
let html = htmlCache.get(file);
|
|
13295
13633
|
if (!html) {
|
|
13296
|
-
html =
|
|
13634
|
+
html = readFileSync18(resolve24(process.cwd(), "public", file), "utf-8");
|
|
13297
13635
|
const productNameEsc = escapeHtml(BRAND.productName);
|
|
13298
13636
|
html = html.replace(/<title>([^<]*)<\/title>/, (_match, inner) => `<title>${escapeHtml(inner).replace(/Maxy/g, productNameEsc)}</title>`);
|
|
13299
13637
|
html = html.replace('href="/favicon.ico"', `href="${escapeHtml(brandFaviconPath)}"`);
|
|
@@ -13309,26 +13647,26 @@ ${clientErrorReporterScript}
|
|
|
13309
13647
|
}
|
|
13310
13648
|
var brandedHtmlCache = /* @__PURE__ */ new Map();
|
|
13311
13649
|
function loadBrandingCache(agentSlug) {
|
|
13312
|
-
const configDir2 =
|
|
13650
|
+
const configDir2 = join11(homedir2(), BRAND.configDir);
|
|
13313
13651
|
try {
|
|
13314
|
-
const accountJsonPath =
|
|
13315
|
-
if (!
|
|
13316
|
-
const account = JSON.parse(
|
|
13652
|
+
const accountJsonPath = join11(configDir2, "account.json");
|
|
13653
|
+
if (!existsSync25(accountJsonPath)) return null;
|
|
13654
|
+
const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
|
|
13317
13655
|
const accountId = account.accountId;
|
|
13318
13656
|
if (!accountId) return null;
|
|
13319
|
-
const cachePath =
|
|
13320
|
-
if (!
|
|
13321
|
-
return JSON.parse(
|
|
13657
|
+
const cachePath = join11(configDir2, "branding-cache", accountId, `${agentSlug}.json`);
|
|
13658
|
+
if (!existsSync25(cachePath)) return null;
|
|
13659
|
+
return JSON.parse(readFileSync18(cachePath, "utf-8"));
|
|
13322
13660
|
} catch {
|
|
13323
13661
|
return null;
|
|
13324
13662
|
}
|
|
13325
13663
|
}
|
|
13326
13664
|
function resolveDefaultSlug() {
|
|
13327
13665
|
try {
|
|
13328
|
-
const configDir2 =
|
|
13329
|
-
const accountJsonPath =
|
|
13330
|
-
if (!
|
|
13331
|
-
const account = JSON.parse(
|
|
13666
|
+
const configDir2 = join11(homedir2(), BRAND.configDir);
|
|
13667
|
+
const accountJsonPath = join11(configDir2, "account.json");
|
|
13668
|
+
if (!existsSync25(accountJsonPath)) return null;
|
|
13669
|
+
const account = JSON.parse(readFileSync18(accountJsonPath, "utf-8"));
|
|
13332
13670
|
return account.defaultAgent || null;
|
|
13333
13671
|
} catch {
|
|
13334
13672
|
return null;
|
|
@@ -13401,7 +13739,7 @@ app36.use("/vnc-popout.html", logViewerFetch);
|
|
|
13401
13739
|
app36.get("/vnc-popout.html", (c) => {
|
|
13402
13740
|
let html = htmlCache.get("vnc-popout.html");
|
|
13403
13741
|
if (!html) {
|
|
13404
|
-
html =
|
|
13742
|
+
html = readFileSync18(resolve24(process.cwd(), "public", "vnc-popout.html"), "utf-8");
|
|
13405
13743
|
const name = escapeHtml(BRAND.productName);
|
|
13406
13744
|
html = html.replace("<title>Browser \u2014 Maxy</title>", `<title>${name}</title>`);
|
|
13407
13745
|
html = html.replace("</head>", ` ${brandScript}
|
|
@@ -13491,8 +13829,8 @@ try {
|
|
|
13491
13829
|
(async () => {
|
|
13492
13830
|
try {
|
|
13493
13831
|
let userId = "";
|
|
13494
|
-
if (
|
|
13495
|
-
const users = JSON.parse(
|
|
13832
|
+
if (existsSync25(USERS_FILE)) {
|
|
13833
|
+
const users = JSON.parse(readFileSync18(USERS_FILE, "utf-8").trim() || "[]");
|
|
13496
13834
|
userId = users[0]?.userId ?? "";
|
|
13497
13835
|
}
|
|
13498
13836
|
await backfillNullUserIdConversations(userId);
|
|
@@ -13552,6 +13890,48 @@ for (const dir of bootEnabled) {
|
|
|
13552
13890
|
bootDelivered.push(dir);
|
|
13553
13891
|
}
|
|
13554
13892
|
console.error(`[plugins] readiness enabled=${bootEnabled.length} delivered=${bootDelivered.length} dist-missing=[${bootDistMissing.join(",")}] missing=[${bootMissing.join(",")}]`);
|
|
13893
|
+
(async () => {
|
|
13894
|
+
if (!bootAccount) {
|
|
13895
|
+
console.log("[action-completion-relay] phase=consumed-skip reason=no-account");
|
|
13896
|
+
return;
|
|
13897
|
+
}
|
|
13898
|
+
let records;
|
|
13899
|
+
try {
|
|
13900
|
+
records = consumeActionCompletionRelays(bootAccount.accountDir);
|
|
13901
|
+
} catch (err) {
|
|
13902
|
+
console.error(`[action-completion-relay] phase=consume-failed error=${err instanceof Error ? err.message : String(err)}`);
|
|
13903
|
+
return;
|
|
13904
|
+
}
|
|
13905
|
+
if (records.length === 0) {
|
|
13906
|
+
console.log(`[action-completion-relay] phase=consumed-empty accountId=${bootAccount.accountId.slice(0, 8)}\u2026`);
|
|
13907
|
+
return;
|
|
13908
|
+
}
|
|
13909
|
+
console.log(`[action-completion-relay] phase=consume-batch count=${records.length} accountId=${bootAccount.accountId.slice(0, 8)}\u2026`);
|
|
13910
|
+
for (const rec of records) {
|
|
13911
|
+
const sessionKey = `cloudflare-relay-boot:${rec.actionId}`;
|
|
13912
|
+
let outcome = "injected";
|
|
13913
|
+
let dispatchError;
|
|
13914
|
+
try {
|
|
13915
|
+
registerSession(sessionKey, "admin", bootAccount.accountId);
|
|
13916
|
+
setConversationIdForSession(sessionKey, rec.conversationId);
|
|
13917
|
+
for await (const _ev of invokeAgent({ type: "admin" }, rec.message, sessionKey)) {
|
|
13918
|
+
}
|
|
13919
|
+
} catch (err) {
|
|
13920
|
+
outcome = "dispatch-failed";
|
|
13921
|
+
dispatchError = err instanceof Error ? err.message : String(err);
|
|
13922
|
+
} finally {
|
|
13923
|
+
unregisterSession(sessionKey);
|
|
13924
|
+
}
|
|
13925
|
+
if (outcome === "injected") {
|
|
13926
|
+
deleteConsumedRelay(rec.filePath);
|
|
13927
|
+
console.log(`[action-completion-relay] phase=consumed actionId=${rec.actionId} conversationId=${rec.conversationId} ageMs=${rec.ageMs} outcome=injected`);
|
|
13928
|
+
} else {
|
|
13929
|
+
console.error(`[action-completion-relay] phase=consumed actionId=${rec.actionId} conversationId=${rec.conversationId} ageMs=${rec.ageMs} outcome=${outcome} reason=${JSON.stringify(dispatchError ?? "")} fileRetained=${JSON.stringify(rec.filePath)}`);
|
|
13930
|
+
}
|
|
13931
|
+
}
|
|
13932
|
+
})().catch((err) => {
|
|
13933
|
+
console.error(`[action-completion-relay] boot-drain rejected: ${err instanceof Error ? err.message : String(err)}`);
|
|
13934
|
+
});
|
|
13555
13935
|
if (bootAccountConfig?.whatsapp) {
|
|
13556
13936
|
console.error(`[whatsapp:boot] loading whatsapp config from account.json publicAgent=${bootPublicAgent ?? "none"}`);
|
|
13557
13937
|
} else {
|
|
@@ -13559,7 +13939,7 @@ if (bootAccountConfig?.whatsapp) {
|
|
|
13559
13939
|
}
|
|
13560
13940
|
init({
|
|
13561
13941
|
configDir: configDirForWhatsApp,
|
|
13562
|
-
platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ??
|
|
13942
|
+
platformRoot: resolve24(process.env.MAXY_PLATFORM_ROOT ?? join11(__dirname, "..")),
|
|
13563
13943
|
accountConfig: bootAccountConfig,
|
|
13564
13944
|
onMessage: async (msg) => {
|
|
13565
13945
|
try {
|