@rubytech/create-maxy 1.0.806 → 1.0.807
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/docs/references/graph.md +42 -0
- package/payload/platform/plugins/docs/references/internals.md +3 -1
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
- package/payload/server/chunk-LSUMH6OF.js +9993 -0
- package/payload/server/chunk-YULDSPAC.js +3484 -0
- package/payload/server/client-pool-LXE7RIRT.js +31 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/neo4j-migrations-HEECOAGK.js +128 -0
- package/payload/server/public/assets/admin-CTM9Vb-j.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 +318 -201
- 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
|
@@ -50,7 +50,7 @@ import {
|
|
|
50
50
|
vncLog,
|
|
51
51
|
waitForExit,
|
|
52
52
|
writeChromiumWrapper
|
|
53
|
-
} from "./chunk-
|
|
53
|
+
} from "./chunk-LSUMH6OF.js";
|
|
54
54
|
import {
|
|
55
55
|
ACCOUNTS_DIR,
|
|
56
56
|
GREETING_DIRECTIVE,
|
|
@@ -65,6 +65,7 @@ import {
|
|
|
65
65
|
deleteAgentProjection,
|
|
66
66
|
deleteConversation,
|
|
67
67
|
embed,
|
|
68
|
+
ensureConversation,
|
|
68
69
|
fetchBranding,
|
|
69
70
|
findGroupBySlug,
|
|
70
71
|
findRecentConversation,
|
|
@@ -113,7 +114,7 @@ import {
|
|
|
113
114
|
verifyAndGetConversationUpdatedAt,
|
|
114
115
|
verifyConversationOwnership,
|
|
115
116
|
writeAdminUserAndPerson
|
|
116
|
-
} from "./chunk-
|
|
117
|
+
} from "./chunk-YULDSPAC.js";
|
|
117
118
|
import {
|
|
118
119
|
__commonJS,
|
|
119
120
|
__toESM
|
|
@@ -3739,6 +3740,37 @@ function sanitizeReason(err) {
|
|
|
3739
3740
|
return `${err.name}:${msg}`;
|
|
3740
3741
|
}
|
|
3741
3742
|
|
|
3743
|
+
// app/lib/whatsapp/ensure-conversation.ts
|
|
3744
|
+
var TAG8 = "[whatsapp-persist]";
|
|
3745
|
+
async function ensureWhatsAppConversation(input) {
|
|
3746
|
+
const t0 = Date.now();
|
|
3747
|
+
try {
|
|
3748
|
+
const result = await ensureConversation(
|
|
3749
|
+
input.accountId,
|
|
3750
|
+
input.agentType,
|
|
3751
|
+
input.sessionKey
|
|
3752
|
+
);
|
|
3753
|
+
const ms = Date.now() - t0;
|
|
3754
|
+
if (!result.conversationId) {
|
|
3755
|
+
console.error(
|
|
3756
|
+
`${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=null-conversationId ms=${ms}`
|
|
3757
|
+
);
|
|
3758
|
+
return null;
|
|
3759
|
+
}
|
|
3760
|
+
console.error(
|
|
3761
|
+
`${TAG8} conversation-merged sessionKey=${input.sessionKey} agentType=${input.agentType} channel=whatsapp created=${result.created} ms=${ms}`
|
|
3762
|
+
);
|
|
3763
|
+
return { conversationId: result.conversationId, created: result.created };
|
|
3764
|
+
} catch (err) {
|
|
3765
|
+
const ms = Date.now() - t0;
|
|
3766
|
+
const reason = err instanceof Error ? `${err.name}:${err.message.slice(0, 200)}` : String(err).slice(0, 200);
|
|
3767
|
+
console.error(
|
|
3768
|
+
`${TAG8} conversation-merged FAIL sessionKey=${input.sessionKey} reason=${reason} ms=${ms}`
|
|
3769
|
+
);
|
|
3770
|
+
return null;
|
|
3771
|
+
}
|
|
3772
|
+
}
|
|
3773
|
+
|
|
3742
3774
|
// app/lib/whatsapp/inbound/media.ts
|
|
3743
3775
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3744
3776
|
import { writeFile, mkdir } from "fs/promises";
|
|
@@ -3748,7 +3780,7 @@ import {
|
|
|
3748
3780
|
downloadContentFromMessage,
|
|
3749
3781
|
normalizeMessageContent as normalizeMessageContent2
|
|
3750
3782
|
} from "@whiskeysockets/baileys";
|
|
3751
|
-
var
|
|
3783
|
+
var TAG9 = "[whatsapp:media]";
|
|
3752
3784
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
3753
3785
|
function mimeToExt(mimetype) {
|
|
3754
3786
|
const map = {
|
|
@@ -3804,25 +3836,25 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3804
3836
|
}
|
|
3805
3837
|
);
|
|
3806
3838
|
if (!buffer || buffer.length === 0) {
|
|
3807
|
-
console.error(`${
|
|
3839
|
+
console.error(`${TAG9} primary download returned empty, trying direct fallback`);
|
|
3808
3840
|
const downloadable = getDownloadableContent(content);
|
|
3809
3841
|
if (downloadable) {
|
|
3810
3842
|
try {
|
|
3811
3843
|
const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
3812
3844
|
buffer = await streamToBuffer(stream);
|
|
3813
3845
|
} catch (fallbackErr) {
|
|
3814
|
-
console.error(`${
|
|
3846
|
+
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
3815
3847
|
}
|
|
3816
3848
|
}
|
|
3817
3849
|
}
|
|
3818
3850
|
if (!buffer || buffer.length === 0) {
|
|
3819
|
-
console.error(`${
|
|
3851
|
+
console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
3820
3852
|
return void 0;
|
|
3821
3853
|
}
|
|
3822
3854
|
if (buffer.length > maxBytes) {
|
|
3823
3855
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
3824
3856
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
3825
|
-
console.error(`${
|
|
3857
|
+
console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
3826
3858
|
return void 0;
|
|
3827
3859
|
}
|
|
3828
3860
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
@@ -3831,20 +3863,20 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3831
3863
|
const filePath = join4(MEDIA_DIR, filename);
|
|
3832
3864
|
await writeFile(filePath, buffer);
|
|
3833
3865
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
3834
|
-
console.error(`${
|
|
3866
|
+
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
3835
3867
|
return {
|
|
3836
3868
|
path: filePath,
|
|
3837
3869
|
mimetype: mimetype ?? "application/octet-stream",
|
|
3838
3870
|
size: buffer.length
|
|
3839
3871
|
};
|
|
3840
3872
|
} catch (err) {
|
|
3841
|
-
console.error(`${
|
|
3873
|
+
console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
3842
3874
|
return void 0;
|
|
3843
3875
|
}
|
|
3844
3876
|
}
|
|
3845
3877
|
|
|
3846
3878
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
3847
|
-
var
|
|
3879
|
+
var TAG10 = "[whatsapp:debounce]";
|
|
3848
3880
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
3849
3881
|
function createInboundDebouncer(opts) {
|
|
3850
3882
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -3875,7 +3907,7 @@ function createInboundDebouncer(opts) {
|
|
|
3875
3907
|
pending.delete(key);
|
|
3876
3908
|
const batchSize = batch.entries.length;
|
|
3877
3909
|
try {
|
|
3878
|
-
console.error(`${
|
|
3910
|
+
console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
|
|
3879
3911
|
const result = onFlush(batch.entries);
|
|
3880
3912
|
if (result && typeof result.catch === "function") {
|
|
3881
3913
|
result.catch(onError);
|
|
@@ -3947,7 +3979,7 @@ function createInboundDebouncer(opts) {
|
|
|
3947
3979
|
}
|
|
3948
3980
|
|
|
3949
3981
|
// app/lib/whatsapp/opening-hours.ts
|
|
3950
|
-
var
|
|
3982
|
+
var TAG11 = "[whatsapp:hours]";
|
|
3951
3983
|
async function isBusinessOpen(accountId) {
|
|
3952
3984
|
try {
|
|
3953
3985
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -3965,7 +3997,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3965
3997
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
3966
3998
|
);
|
|
3967
3999
|
if (result.records.length === 0) {
|
|
3968
|
-
console.error(`${
|
|
4000
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3969
4001
|
return { open: true, reason: "no opening hours configured" };
|
|
3970
4002
|
}
|
|
3971
4003
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -3974,7 +4006,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3974
4006
|
closes: String(r.get("closes")).trim()
|
|
3975
4007
|
}));
|
|
3976
4008
|
if (specs.length === 0) {
|
|
3977
|
-
console.error(`${
|
|
4009
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3978
4010
|
return { open: true, reason: "no opening hours configured" };
|
|
3979
4011
|
}
|
|
3980
4012
|
for (const spec of specs) {
|
|
@@ -3984,7 +4016,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3984
4016
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
3985
4017
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
3986
4018
|
console.error(
|
|
3987
|
-
`${
|
|
4019
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3988
4020
|
);
|
|
3989
4021
|
return {
|
|
3990
4022
|
open: true,
|
|
@@ -3997,7 +4029,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3997
4029
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
3998
4030
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
3999
4031
|
console.error(
|
|
4000
|
-
`${
|
|
4032
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
4001
4033
|
);
|
|
4002
4034
|
return {
|
|
4003
4035
|
open: true,
|
|
@@ -4011,7 +4043,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4011
4043
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
4012
4044
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
4013
4045
|
console.error(
|
|
4014
|
-
`${
|
|
4046
|
+
`${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
4015
4047
|
);
|
|
4016
4048
|
return {
|
|
4017
4049
|
open: false,
|
|
@@ -4025,7 +4057,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4025
4057
|
}
|
|
4026
4058
|
} catch (err) {
|
|
4027
4059
|
console.error(
|
|
4028
|
-
`${
|
|
4060
|
+
`${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
4029
4061
|
);
|
|
4030
4062
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
4031
4063
|
}
|
|
@@ -4071,7 +4103,7 @@ import { execFile } from "child_process";
|
|
|
4071
4103
|
import { unlink, stat } from "fs/promises";
|
|
4072
4104
|
import { promisify } from "util";
|
|
4073
4105
|
var execFileAsync = promisify(execFile);
|
|
4074
|
-
var
|
|
4106
|
+
var TAG12 = "[stt]";
|
|
4075
4107
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
4076
4108
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
4077
4109
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -4082,11 +4114,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4082
4114
|
const s = await stat(audioPath);
|
|
4083
4115
|
audioBytes = s.size;
|
|
4084
4116
|
} catch {
|
|
4085
|
-
console.error(`${
|
|
4117
|
+
console.error(`${TAG12} failed: file not readable path=${audioPath}`);
|
|
4086
4118
|
return void 0;
|
|
4087
4119
|
}
|
|
4088
4120
|
console.error(
|
|
4089
|
-
`${
|
|
4121
|
+
`${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
4090
4122
|
);
|
|
4091
4123
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
4092
4124
|
try {
|
|
@@ -4105,7 +4137,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4105
4137
|
], { timeout: 3e4 });
|
|
4106
4138
|
} catch (err) {
|
|
4107
4139
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4108
|
-
console.error(`${
|
|
4140
|
+
console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
|
|
4109
4141
|
return void 0;
|
|
4110
4142
|
}
|
|
4111
4143
|
try {
|
|
@@ -4123,20 +4155,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4123
4155
|
const text = stdout.trim();
|
|
4124
4156
|
const durationMs = Date.now() - startMs;
|
|
4125
4157
|
if (!text) {
|
|
4126
|
-
console.error(`${
|
|
4158
|
+
console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
4127
4159
|
return void 0;
|
|
4128
4160
|
}
|
|
4129
4161
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
4130
4162
|
const language = langMatch?.[1] ?? "unknown";
|
|
4131
4163
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
4132
4164
|
console.error(
|
|
4133
|
-
`${
|
|
4165
|
+
`${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
4134
4166
|
);
|
|
4135
4167
|
return { text, language, durationMs };
|
|
4136
4168
|
} catch (err) {
|
|
4137
4169
|
const durationMs = Date.now() - startMs;
|
|
4138
4170
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4139
|
-
console.error(`${
|
|
4171
|
+
console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
4140
4172
|
return void 0;
|
|
4141
4173
|
} finally {
|
|
4142
4174
|
unlink(wavPath).catch(() => {
|
|
@@ -4145,7 +4177,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4145
4177
|
}
|
|
4146
4178
|
|
|
4147
4179
|
// app/lib/whatsapp/manager.ts
|
|
4148
|
-
var
|
|
4180
|
+
var TAG13 = "[whatsapp:manager]";
|
|
4149
4181
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
4150
4182
|
var MESSAGE_STORE_MAX = 500;
|
|
4151
4183
|
var GROUP_META_TTL = 5 * 60 * 1e3;
|
|
@@ -4165,7 +4197,7 @@ function storeMessage(storeKey, entry) {
|
|
|
4165
4197
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
4166
4198
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
4167
4199
|
entries.splice(0, trimmed);
|
|
4168
|
-
console.error(`${
|
|
4200
|
+
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
4169
4201
|
}
|
|
4170
4202
|
}
|
|
4171
4203
|
function deriveSessionKey(input) {
|
|
@@ -4184,7 +4216,7 @@ function deriveSessionKey(input) {
|
|
|
4184
4216
|
}
|
|
4185
4217
|
async function init(opts) {
|
|
4186
4218
|
if (initialized) {
|
|
4187
|
-
console.error(`${
|
|
4219
|
+
console.error(`${TAG13} already initialized`);
|
|
4188
4220
|
return;
|
|
4189
4221
|
}
|
|
4190
4222
|
configDir = opts.configDir;
|
|
@@ -4192,20 +4224,20 @@ async function init(opts) {
|
|
|
4192
4224
|
loadConfig(opts.accountConfig);
|
|
4193
4225
|
const accountIds = listCredentialAccountIds(configDir);
|
|
4194
4226
|
if (accountIds.length === 0) {
|
|
4195
|
-
console.error(`${
|
|
4227
|
+
console.error(`${TAG13} init: no stored WhatsApp credentials found`);
|
|
4196
4228
|
initialized = true;
|
|
4197
4229
|
return;
|
|
4198
4230
|
}
|
|
4199
|
-
console.error(`${
|
|
4231
|
+
console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
4200
4232
|
initialized = true;
|
|
4201
4233
|
for (const accountId of accountIds) {
|
|
4202
4234
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
4203
4235
|
if (accountCfg?.enabled === false) {
|
|
4204
|
-
console.error(`${
|
|
4236
|
+
console.error(`${TAG13} skipping disabled account=${accountId}`);
|
|
4205
4237
|
continue;
|
|
4206
4238
|
}
|
|
4207
4239
|
startConnection(accountId).catch((err) => {
|
|
4208
|
-
console.error(`${
|
|
4240
|
+
console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
4209
4241
|
});
|
|
4210
4242
|
}
|
|
4211
4243
|
}
|
|
@@ -4214,7 +4246,7 @@ async function startConnection(accountId) {
|
|
|
4214
4246
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
4215
4247
|
const hasAuth = await authExists(authDir);
|
|
4216
4248
|
if (!hasAuth) {
|
|
4217
|
-
console.error(`${
|
|
4249
|
+
console.error(`${TAG13} no credentials for account=${accountId}`);
|
|
4218
4250
|
return;
|
|
4219
4251
|
}
|
|
4220
4252
|
await stopConnection(accountId);
|
|
@@ -4250,11 +4282,11 @@ async function stopConnection(accountId) {
|
|
|
4250
4282
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
4251
4283
|
conn.sock.ws?.close?.();
|
|
4252
4284
|
} catch (err) {
|
|
4253
|
-
console.warn(`${
|
|
4285
|
+
console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
4254
4286
|
}
|
|
4255
4287
|
}
|
|
4256
4288
|
connections.delete(accountId);
|
|
4257
|
-
console.error(`${
|
|
4289
|
+
console.error(`${TAG13} stopped account=${accountId}`);
|
|
4258
4290
|
}
|
|
4259
4291
|
function getStatus() {
|
|
4260
4292
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -4295,9 +4327,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4295
4327
|
connections.set(accountId, conn);
|
|
4296
4328
|
try {
|
|
4297
4329
|
await sock.sendPresenceUpdate("available");
|
|
4298
|
-
console.error(`${
|
|
4330
|
+
console.error(`${TAG13} presence set to available account=${accountId}`);
|
|
4299
4331
|
} catch (err) {
|
|
4300
|
-
console.error(`${
|
|
4332
|
+
console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
|
|
4301
4333
|
}
|
|
4302
4334
|
await runInitQueries(sock, {
|
|
4303
4335
|
accountId,
|
|
@@ -4308,7 +4340,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4308
4340
|
});
|
|
4309
4341
|
monitorInbound(conn);
|
|
4310
4342
|
watchForDisconnect(conn);
|
|
4311
|
-
console.error(`${
|
|
4343
|
+
console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4312
4344
|
}
|
|
4313
4345
|
function reloadConfig(accountConfig) {
|
|
4314
4346
|
loadConfig(accountConfig);
|
|
@@ -4332,7 +4364,7 @@ async function shutdown() {
|
|
|
4332
4364
|
const ids = Array.from(connections.keys());
|
|
4333
4365
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
4334
4366
|
initialized = false;
|
|
4335
|
-
console.error(`${
|
|
4367
|
+
console.error(`${TAG13} shutdown complete`);
|
|
4336
4368
|
}
|
|
4337
4369
|
function loadConfig(accountConfig) {
|
|
4338
4370
|
try {
|
|
@@ -4344,12 +4376,12 @@ function loadConfig(accountConfig) {
|
|
|
4344
4376
|
if (parsed.success) {
|
|
4345
4377
|
whatsAppConfig = parsed.data;
|
|
4346
4378
|
} else {
|
|
4347
|
-
console.error(`${
|
|
4379
|
+
console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
|
|
4348
4380
|
whatsAppConfig = {};
|
|
4349
4381
|
}
|
|
4350
4382
|
}
|
|
4351
4383
|
} catch (err) {
|
|
4352
|
-
console.error(`${
|
|
4384
|
+
console.error(`${TAG13} config load error: ${String(err)}`);
|
|
4353
4385
|
whatsAppConfig = {};
|
|
4354
4386
|
}
|
|
4355
4387
|
}
|
|
@@ -4360,13 +4392,13 @@ async function connectWithReconnect(conn) {
|
|
|
4360
4392
|
let cycleError = null;
|
|
4361
4393
|
let connectedAt;
|
|
4362
4394
|
try {
|
|
4363
|
-
console.error(`${
|
|
4395
|
+
console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
4364
4396
|
const sock = await createWaSocket({
|
|
4365
4397
|
authDir: conn.authDir,
|
|
4366
4398
|
silent: true
|
|
4367
4399
|
});
|
|
4368
4400
|
conn.sock = sock;
|
|
4369
|
-
console.error(`${
|
|
4401
|
+
console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
4370
4402
|
await waitForConnection(sock);
|
|
4371
4403
|
const selfId = readSelfId(conn.authDir);
|
|
4372
4404
|
connectedAt = Date.now();
|
|
@@ -4376,12 +4408,12 @@ async function connectWithReconnect(conn) {
|
|
|
4376
4408
|
conn.lastConnectedAt = connectedAt;
|
|
4377
4409
|
conn.lastError = void 0;
|
|
4378
4410
|
conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
|
|
4379
|
-
console.error(`${
|
|
4411
|
+
console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4380
4412
|
try {
|
|
4381
4413
|
await sock.sendPresenceUpdate("available");
|
|
4382
|
-
console.error(`${
|
|
4414
|
+
console.error(`${TAG13} presence set to available account=${conn.accountId}`);
|
|
4383
4415
|
} catch (err) {
|
|
4384
|
-
console.error(`${
|
|
4416
|
+
console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
4385
4417
|
}
|
|
4386
4418
|
await runInitQueries(sock, {
|
|
4387
4419
|
accountId: conn.accountId,
|
|
@@ -4408,9 +4440,9 @@ async function connectWithReconnect(conn) {
|
|
|
4408
4440
|
}
|
|
4409
4441
|
const classification = classifyDisconnect(err);
|
|
4410
4442
|
conn.lastError = classification.message;
|
|
4411
|
-
console.error(`${
|
|
4443
|
+
console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
4412
4444
|
if (!classification.shouldRetry) {
|
|
4413
|
-
console.error(`${
|
|
4445
|
+
console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
4414
4446
|
return;
|
|
4415
4447
|
}
|
|
4416
4448
|
}
|
|
@@ -4423,7 +4455,7 @@ async function connectWithReconnect(conn) {
|
|
|
4423
4455
|
if (decision.action === "abort") {
|
|
4424
4456
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
4425
4457
|
console.error(
|
|
4426
|
-
`${
|
|
4458
|
+
`${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
4427
4459
|
);
|
|
4428
4460
|
conn.sessionStuckReason = stuckReason;
|
|
4429
4461
|
conn.lastError = stuckReason;
|
|
@@ -4432,17 +4464,17 @@ async function connectWithReconnect(conn) {
|
|
|
4432
4464
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
4433
4465
|
if (decision.reason === "short-lived") {
|
|
4434
4466
|
console.error(
|
|
4435
|
-
`${
|
|
4467
|
+
`${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
4436
4468
|
);
|
|
4437
4469
|
} else {
|
|
4438
4470
|
console.error(
|
|
4439
|
-
`${
|
|
4471
|
+
`${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
4440
4472
|
);
|
|
4441
4473
|
}
|
|
4442
4474
|
if (decision.reason === "short-lived" || cycleError) {
|
|
4443
4475
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
4444
4476
|
console.error(
|
|
4445
|
-
`${
|
|
4477
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4446
4478
|
);
|
|
4447
4479
|
await new Promise((resolve25) => {
|
|
4448
4480
|
const timer2 = setTimeout(resolve25, delay);
|
|
@@ -4476,11 +4508,11 @@ function watchForDisconnect(conn) {
|
|
|
4476
4508
|
conn.sock.ev.on("connection.update", (update) => {
|
|
4477
4509
|
if (update.connection === "close") {
|
|
4478
4510
|
if (connections.get(conn.accountId) !== conn) return;
|
|
4479
|
-
console.error(`${
|
|
4511
|
+
console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
|
|
4480
4512
|
conn.connected = false;
|
|
4481
4513
|
conn.sock = null;
|
|
4482
4514
|
connectWithReconnect(conn).catch((err) => {
|
|
4483
|
-
console.error(`${
|
|
4515
|
+
console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
4484
4516
|
});
|
|
4485
4517
|
}
|
|
4486
4518
|
});
|
|
@@ -4489,7 +4521,7 @@ async function getGroupMeta(conn, jid) {
|
|
|
4489
4521
|
const cached = conn.groupMetaCache.get(jid);
|
|
4490
4522
|
if (cached && cached.expires > Date.now()) return cached;
|
|
4491
4523
|
if (!conn.sock) return null;
|
|
4492
|
-
console.error(`${
|
|
4524
|
+
console.error(`${TAG13} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
|
|
4493
4525
|
try {
|
|
4494
4526
|
const meta = await conn.sock.groupMetadata(jid);
|
|
4495
4527
|
const participants = await Promise.all(
|
|
@@ -4504,12 +4536,12 @@ async function getGroupMeta(conn, jid) {
|
|
|
4504
4536
|
};
|
|
4505
4537
|
conn.groupMetaCache.set(jid, entry);
|
|
4506
4538
|
console.error(
|
|
4507
|
-
`${
|
|
4539
|
+
`${TAG13} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
4508
4540
|
);
|
|
4509
4541
|
return entry;
|
|
4510
4542
|
} catch (err) {
|
|
4511
4543
|
console.error(
|
|
4512
|
-
`${
|
|
4544
|
+
`${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
4545
|
);
|
|
4514
4546
|
const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
|
|
4515
4547
|
conn.groupMetaCache.set(jid, emptyEntry);
|
|
@@ -4520,7 +4552,7 @@ function monitorInbound(conn) {
|
|
|
4520
4552
|
if (!conn.sock || !onInboundMessage) return;
|
|
4521
4553
|
const sock = conn.sock;
|
|
4522
4554
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
4523
|
-
console.error(`${
|
|
4555
|
+
console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
4524
4556
|
conn.debouncer = createInboundDebouncer({
|
|
4525
4557
|
debounceMs,
|
|
4526
4558
|
buildKey: (payload) => {
|
|
@@ -4533,7 +4565,7 @@ function monitorInbound(conn) {
|
|
|
4533
4565
|
onInboundMessage(entries[0]);
|
|
4534
4566
|
return;
|
|
4535
4567
|
}
|
|
4536
|
-
console.error(`${
|
|
4568
|
+
console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
4537
4569
|
const last = entries[entries.length - 1];
|
|
4538
4570
|
const mediaEntry = entries.find((e) => e.mediaPath);
|
|
4539
4571
|
const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
|
|
@@ -4546,7 +4578,7 @@ function monitorInbound(conn) {
|
|
|
4546
4578
|
});
|
|
4547
4579
|
},
|
|
4548
4580
|
onError: (err) => {
|
|
4549
|
-
console.error(`${
|
|
4581
|
+
console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
4550
4582
|
}
|
|
4551
4583
|
});
|
|
4552
4584
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -4574,7 +4606,7 @@ function monitorInbound(conn) {
|
|
|
4574
4606
|
});
|
|
4575
4607
|
const entries = messageStore.get(storeKey);
|
|
4576
4608
|
console.error(
|
|
4577
|
-
`${
|
|
4609
|
+
`${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
4578
4610
|
);
|
|
4579
4611
|
recordActivity({
|
|
4580
4612
|
accountId: conn.accountId,
|
|
@@ -4597,23 +4629,31 @@ function monitorInbound(conn) {
|
|
|
4597
4629
|
isOwnerMirror: fromMe && !isGroup
|
|
4598
4630
|
});
|
|
4599
4631
|
if (msg.key.id) {
|
|
4600
|
-
await
|
|
4632
|
+
const merged = await ensureWhatsAppConversation({
|
|
4601
4633
|
accountId: conn.accountId,
|
|
4602
|
-
remoteJid,
|
|
4603
4634
|
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
|
|
4635
|
+
agentType: sessionKeyAgentType,
|
|
4636
|
+
groupJid: isGroup ? remoteJid : void 0
|
|
4616
4637
|
});
|
|
4638
|
+
if (merged) {
|
|
4639
|
+
await persistWhatsAppMessage({
|
|
4640
|
+
accountId: conn.accountId,
|
|
4641
|
+
remoteJid,
|
|
4642
|
+
sessionKey,
|
|
4643
|
+
msgKeyId: msg.key.id,
|
|
4644
|
+
fromMe,
|
|
4645
|
+
senderPhone,
|
|
4646
|
+
selfPhone: conn.selfPhone,
|
|
4647
|
+
body: extracted.text,
|
|
4648
|
+
timestamp: ts,
|
|
4649
|
+
pushName: msg.pushName ?? void 0,
|
|
4650
|
+
quoted: extracted.quotedMessage ? {
|
|
4651
|
+
id: extracted.quotedMessage.id,
|
|
4652
|
+
body: extracted.quotedMessage.text,
|
|
4653
|
+
sender: extracted.quotedMessage.sender
|
|
4654
|
+
} : void 0
|
|
4655
|
+
});
|
|
4656
|
+
}
|
|
4617
4657
|
}
|
|
4618
4658
|
}
|
|
4619
4659
|
if (upsert.type === "append") {
|
|
@@ -4627,13 +4667,13 @@ function monitorInbound(conn) {
|
|
|
4627
4667
|
);
|
|
4628
4668
|
}
|
|
4629
4669
|
console.error(
|
|
4630
|
-
`${
|
|
4670
|
+
`${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
4631
4671
|
);
|
|
4632
4672
|
continue;
|
|
4633
4673
|
}
|
|
4634
4674
|
await handleInboundMessage(conn, msg);
|
|
4635
4675
|
} catch (err) {
|
|
4636
|
-
console.error(`${
|
|
4676
|
+
console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
4637
4677
|
}
|
|
4638
4678
|
}
|
|
4639
4679
|
});
|
|
@@ -4643,31 +4683,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4643
4683
|
const remoteJid = msg.key.remoteJid;
|
|
4644
4684
|
if (!remoteJid) return;
|
|
4645
4685
|
if (remoteJid === "status@broadcast") {
|
|
4646
|
-
console.error(`${
|
|
4686
|
+
console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
|
|
4647
4687
|
return;
|
|
4648
4688
|
}
|
|
4649
4689
|
if (!msg.message) {
|
|
4650
|
-
console.error(`${
|
|
4690
|
+
console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
4651
4691
|
return;
|
|
4652
4692
|
}
|
|
4653
4693
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
4654
4694
|
if (isDuplicateInbound(dedupKey)) {
|
|
4655
|
-
console.error(`${
|
|
4695
|
+
console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
4656
4696
|
return;
|
|
4657
4697
|
}
|
|
4658
4698
|
if (msg.key.fromMe) {
|
|
4659
4699
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
4660
|
-
console.error(`${
|
|
4700
|
+
console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
4661
4701
|
return;
|
|
4662
4702
|
}
|
|
4663
4703
|
const extracted2 = extractMessage(msg);
|
|
4664
4704
|
if (!extracted2.text) {
|
|
4665
|
-
console.error(`${
|
|
4705
|
+
console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
4666
4706
|
return;
|
|
4667
4707
|
}
|
|
4668
4708
|
const isGroup2 = isGroupJid(remoteJid);
|
|
4669
4709
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
4670
|
-
console.error(`${
|
|
4710
|
+
console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
4671
4711
|
const reply2 = async (text) => {
|
|
4672
4712
|
const currentSock = conn.sock;
|
|
4673
4713
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -4695,7 +4735,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4695
4735
|
}
|
|
4696
4736
|
const extracted = extractMessage(msg);
|
|
4697
4737
|
if (!extracted.text && !extracted.mediaType) {
|
|
4698
|
-
console.error(`${
|
|
4738
|
+
console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
4699
4739
|
return;
|
|
4700
4740
|
}
|
|
4701
4741
|
let mediaResult;
|
|
@@ -4705,7 +4745,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4705
4745
|
maxBytes: maxMb * 1024 * 1024
|
|
4706
4746
|
});
|
|
4707
4747
|
if (!mediaResult) {
|
|
4708
|
-
console.error(`${
|
|
4748
|
+
console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
4709
4749
|
}
|
|
4710
4750
|
}
|
|
4711
4751
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -4750,7 +4790,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4750
4790
|
});
|
|
4751
4791
|
}
|
|
4752
4792
|
console.error(
|
|
4753
|
-
`${
|
|
4793
|
+
`${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
4794
|
);
|
|
4755
4795
|
if (!accessResult.allowed) return;
|
|
4756
4796
|
let groupSubject;
|
|
@@ -4793,15 +4833,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4793
4833
|
if (accessResult.agentType === "public") {
|
|
4794
4834
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
4795
4835
|
if (!hoursResult.open) {
|
|
4796
|
-
console.error(`${
|
|
4836
|
+
console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
|
|
4797
4837
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
4798
4838
|
if (afterHoursMessage) {
|
|
4799
4839
|
try {
|
|
4800
4840
|
await reply(afterHoursMessage);
|
|
4801
|
-
console.error(`${
|
|
4841
|
+
console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
4802
4842
|
} catch (err) {
|
|
4803
4843
|
console.error(
|
|
4804
|
-
`${
|
|
4844
|
+
`${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4805
4845
|
);
|
|
4806
4846
|
}
|
|
4807
4847
|
}
|
|
@@ -5387,7 +5427,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
5387
5427
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
5388
5428
|
import { tmpdir } from "os";
|
|
5389
5429
|
import { join as join5 } from "path";
|
|
5390
|
-
var
|
|
5430
|
+
var TAG14 = "[voice]";
|
|
5391
5431
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5392
5432
|
"audio/ogg",
|
|
5393
5433
|
"audio/webm",
|
|
@@ -5419,7 +5459,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5419
5459
|
const bytes = file.size;
|
|
5420
5460
|
const mimeType = file.type;
|
|
5421
5461
|
console.error(
|
|
5422
|
-
`${
|
|
5462
|
+
`${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
5423
5463
|
);
|
|
5424
5464
|
let tempDir;
|
|
5425
5465
|
let tempPath;
|
|
@@ -5431,7 +5471,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5431
5471
|
await writeFile3(tempPath, buffer);
|
|
5432
5472
|
} catch (err) {
|
|
5433
5473
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5434
|
-
console.error(`${
|
|
5474
|
+
console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
|
|
5435
5475
|
return { ok: false, error: "Could not process voice note" };
|
|
5436
5476
|
}
|
|
5437
5477
|
try {
|
|
@@ -5439,7 +5479,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5439
5479
|
if (!sttResult) {
|
|
5440
5480
|
const elapsed2 = Date.now() - startMs;
|
|
5441
5481
|
console.error(
|
|
5442
|
-
`${
|
|
5482
|
+
`${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
5443
5483
|
);
|
|
5444
5484
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
5445
5485
|
}
|
|
@@ -5447,7 +5487,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5447
5487
|
const elapsed = Date.now() - startMs;
|
|
5448
5488
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
5449
5489
|
console.error(
|
|
5450
|
-
`${
|
|
5490
|
+
`${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
5451
5491
|
);
|
|
5452
5492
|
return {
|
|
5453
5493
|
ok: true,
|
|
@@ -5457,7 +5497,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5457
5497
|
const elapsed = Date.now() - startMs;
|
|
5458
5498
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5459
5499
|
console.error(
|
|
5460
|
-
`${
|
|
5500
|
+
`${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
5461
5501
|
);
|
|
5462
5502
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
5463
5503
|
} finally {
|
|
@@ -5492,7 +5532,7 @@ var VERDICT_DEFINITIONS = {
|
|
|
5492
5532
|
};
|
|
5493
5533
|
|
|
5494
5534
|
// app/lib/inbound-gateway.ts
|
|
5495
|
-
var
|
|
5535
|
+
var TAG15 = "[inbound-gateway]";
|
|
5496
5536
|
var GATEWAY_TIMEOUT_MS = 1e4;
|
|
5497
5537
|
var MIN_WORDS_FOR_PROCESSING = 5;
|
|
5498
5538
|
function defaultResult(rawText, latencyMs) {
|
|
@@ -5615,11 +5655,11 @@ async function processInbound(rawText, channel) {
|
|
|
5615
5655
|
};
|
|
5616
5656
|
result.fallthrough = false;
|
|
5617
5657
|
console.warn(
|
|
5618
|
-
`${
|
|
5658
|
+
`${TAG15} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
|
|
5619
5659
|
);
|
|
5620
5660
|
} else {
|
|
5621
5661
|
console.log(
|
|
5622
|
-
`${
|
|
5662
|
+
`${TAG15} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
|
|
5623
5663
|
);
|
|
5624
5664
|
}
|
|
5625
5665
|
return result;
|
|
@@ -5636,13 +5676,13 @@ async function processInbound(rawText, channel) {
|
|
|
5636
5676
|
const latencyMs = Date.now() - startMs;
|
|
5637
5677
|
if (llmResult.kind === "fallback") {
|
|
5638
5678
|
console.warn(
|
|
5639
|
-
`${
|
|
5679
|
+
`${TAG15} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
|
|
5640
5680
|
);
|
|
5641
5681
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5642
5682
|
}
|
|
5643
5683
|
if (llmResult.kind !== "ok-tool") {
|
|
5644
5684
|
console.warn(
|
|
5645
|
-
`${
|
|
5685
|
+
`${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
|
|
5646
5686
|
);
|
|
5647
5687
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5648
5688
|
}
|
|
@@ -5652,7 +5692,7 @@ async function processInbound(rawText, channel) {
|
|
|
5652
5692
|
const verdict = input.verdict;
|
|
5653
5693
|
if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
|
|
5654
5694
|
console.warn(
|
|
5655
|
-
`${
|
|
5695
|
+
`${TAG15} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
|
|
5656
5696
|
);
|
|
5657
5697
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5658
5698
|
}
|
|
@@ -5680,18 +5720,18 @@ async function processInbound(rawText, channel) {
|
|
|
5680
5720
|
fallthrough: false
|
|
5681
5721
|
};
|
|
5682
5722
|
console.log(
|
|
5683
|
-
`${
|
|
5723
|
+
`${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
5724
|
);
|
|
5685
5725
|
if (verdict !== "clean") {
|
|
5686
5726
|
console.warn(
|
|
5687
|
-
`${
|
|
5727
|
+
`${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
|
|
5688
5728
|
);
|
|
5689
5729
|
}
|
|
5690
5730
|
return result;
|
|
5691
5731
|
} catch (err) {
|
|
5692
5732
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5693
5733
|
console.warn(
|
|
5694
|
-
`${
|
|
5734
|
+
`${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
|
|
5695
5735
|
);
|
|
5696
5736
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5697
5737
|
}
|
|
@@ -6784,7 +6824,7 @@ function checkTelegramAccess(params) {
|
|
|
6784
6824
|
}
|
|
6785
6825
|
|
|
6786
6826
|
// server/routes/telegram.ts
|
|
6787
|
-
var
|
|
6827
|
+
var TAG16 = "[telegram-webhook]";
|
|
6788
6828
|
var TELEGRAM_API = "https://api.telegram.org";
|
|
6789
6829
|
function getWebhookSecret(botType) {
|
|
6790
6830
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
@@ -6808,12 +6848,12 @@ async function handleInbound(params) {
|
|
|
6808
6848
|
const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
|
|
6809
6849
|
registerSession(sessionKey, agentType, accountId);
|
|
6810
6850
|
console.error(
|
|
6811
|
-
`${
|
|
6851
|
+
`${TAG16} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
|
|
6812
6852
|
);
|
|
6813
6853
|
const gatewayResult = await processInbound(text, gatewayChannel);
|
|
6814
6854
|
if (gatewayResult.screening.verdict === "discard") {
|
|
6815
6855
|
console.error(
|
|
6816
|
-
`${
|
|
6856
|
+
`${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
|
|
6817
6857
|
);
|
|
6818
6858
|
return;
|
|
6819
6859
|
}
|
|
@@ -6836,7 +6876,7 @@ async function handleInbound(params) {
|
|
|
6836
6876
|
}
|
|
6837
6877
|
} catch (err) {
|
|
6838
6878
|
console.error(
|
|
6839
|
-
`${
|
|
6879
|
+
`${TAG16} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6840
6880
|
);
|
|
6841
6881
|
responseText = "I'm having trouble right now. Please try again in a moment.";
|
|
6842
6882
|
}
|
|
@@ -6854,12 +6894,12 @@ async function handleInbound(params) {
|
|
|
6854
6894
|
const data = await res.json();
|
|
6855
6895
|
if (!data.ok) {
|
|
6856
6896
|
console.error(
|
|
6857
|
-
`${
|
|
6897
|
+
`${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
|
|
6858
6898
|
);
|
|
6859
6899
|
}
|
|
6860
6900
|
} catch (err) {
|
|
6861
6901
|
console.error(
|
|
6862
|
-
`${
|
|
6902
|
+
`${TAG16} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6863
6903
|
);
|
|
6864
6904
|
}
|
|
6865
6905
|
}
|
|
@@ -6867,28 +6907,28 @@ var app6 = new Hono();
|
|
|
6867
6907
|
app6.post("/webhook", async (c) => {
|
|
6868
6908
|
const botType = c.req.query("bot");
|
|
6869
6909
|
if (!botType || botType !== "public" && botType !== "admin") {
|
|
6870
|
-
console.error(`${
|
|
6910
|
+
console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
|
|
6871
6911
|
return c.json({ error: "Missing or invalid bot type" }, 400);
|
|
6872
6912
|
}
|
|
6873
6913
|
const storedSecret = getWebhookSecret(botType);
|
|
6874
6914
|
if (!storedSecret) {
|
|
6875
|
-
console.error(`${
|
|
6915
|
+
console.error(`${TAG16} secret=missing botType=${botType}`);
|
|
6876
6916
|
return c.json({ error: "Webhook not configured" }, 401);
|
|
6877
6917
|
}
|
|
6878
6918
|
const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
|
|
6879
6919
|
if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
|
|
6880
|
-
console.error(`${
|
|
6920
|
+
console.error(`${TAG16} secret=invalid botType=${botType}`);
|
|
6881
6921
|
return c.json({ error: "Unauthorized" }, 401);
|
|
6882
6922
|
}
|
|
6883
6923
|
const account = resolveAccount();
|
|
6884
6924
|
if (!account) {
|
|
6885
|
-
console.error(`${
|
|
6925
|
+
console.error(`${TAG16} config=no-account`);
|
|
6886
6926
|
return c.json({ error: "No account configured" }, 500);
|
|
6887
6927
|
}
|
|
6888
6928
|
const tgConfig = account.config.telegram ?? {};
|
|
6889
6929
|
const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
|
|
6890
6930
|
if (!botToken) {
|
|
6891
|
-
console.error(`${
|
|
6931
|
+
console.error(`${TAG16} config=no-token botType=${botType}`);
|
|
6892
6932
|
return c.json({ error: `No ${botType} bot token configured` }, 500);
|
|
6893
6933
|
}
|
|
6894
6934
|
let update;
|
|
@@ -6916,7 +6956,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6916
6956
|
}
|
|
6917
6957
|
const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
|
|
6918
6958
|
console.error(
|
|
6919
|
-
`${
|
|
6959
|
+
`${TAG16} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
|
|
6920
6960
|
);
|
|
6921
6961
|
if (!accessResult.allowed) {
|
|
6922
6962
|
return c.json({ ok: true });
|
|
@@ -6927,7 +6967,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6927
6967
|
headers: { "Content-Type": "application/json" },
|
|
6928
6968
|
body: JSON.stringify({ callback_query_id: callbackId })
|
|
6929
6969
|
}).catch((err) => {
|
|
6930
|
-
console.error(`${
|
|
6970
|
+
console.error(`${TAG16} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6931
6971
|
});
|
|
6932
6972
|
}
|
|
6933
6973
|
handleInbound({
|
|
@@ -6940,7 +6980,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6940
6980
|
agentType: accessResult.agentType
|
|
6941
6981
|
}).catch((err) => {
|
|
6942
6982
|
console.error(
|
|
6943
|
-
`${
|
|
6983
|
+
`${TAG16} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6944
6984
|
);
|
|
6945
6985
|
});
|
|
6946
6986
|
return c.json({ ok: true });
|
|
@@ -6954,14 +6994,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
|
|
|
6954
6994
|
|
|
6955
6995
|
// app/lib/whatsapp/login.ts
|
|
6956
6996
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6957
|
-
var
|
|
6997
|
+
var TAG17 = "[whatsapp:login]";
|
|
6958
6998
|
var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
|
|
6959
6999
|
var activeLogins = /* @__PURE__ */ new Map();
|
|
6960
7000
|
function closeSocket(sock) {
|
|
6961
7001
|
try {
|
|
6962
7002
|
sock.ws?.close?.();
|
|
6963
7003
|
} catch (err) {
|
|
6964
|
-
console.warn(`${
|
|
7004
|
+
console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
|
|
6965
7005
|
}
|
|
6966
7006
|
}
|
|
6967
7007
|
function resetActiveLogin(accountId) {
|
|
@@ -6984,7 +7024,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6984
7024
|
const current = activeLogins.get(accountId);
|
|
6985
7025
|
if (current?.id === login.id) {
|
|
6986
7026
|
current.connected = true;
|
|
6987
|
-
console.error(`${
|
|
7027
|
+
console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
|
|
6988
7028
|
}
|
|
6989
7029
|
return;
|
|
6990
7030
|
} catch (err) {
|
|
@@ -6994,7 +7034,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6994
7034
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6995
7035
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6996
7036
|
console.error(
|
|
6997
|
-
`${
|
|
7037
|
+
`${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6998
7038
|
);
|
|
6999
7039
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
7000
7040
|
} else {
|
|
@@ -7006,7 +7046,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7006
7046
|
attempt++;
|
|
7007
7047
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
7008
7048
|
console.error(
|
|
7009
|
-
`${
|
|
7049
|
+
`${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
7010
7050
|
);
|
|
7011
7051
|
closeSocket(current.sock);
|
|
7012
7052
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -7017,7 +7057,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
7017
7057
|
current.sock = newSock;
|
|
7018
7058
|
} catch (sockErr) {
|
|
7019
7059
|
console.error(
|
|
7020
|
-
`${
|
|
7060
|
+
`${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
7021
7061
|
);
|
|
7022
7062
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
7023
7063
|
return;
|
|
@@ -7031,7 +7071,7 @@ async function startLogin(opts) {
|
|
|
7031
7071
|
const hasAuth = await authExists(authDir);
|
|
7032
7072
|
const selfId = readSelfId(authDir);
|
|
7033
7073
|
console.error(
|
|
7034
|
-
`${
|
|
7074
|
+
`${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
7075
|
);
|
|
7036
7076
|
if (hasAuth && !force) {
|
|
7037
7077
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -7043,7 +7083,7 @@ async function startLogin(opts) {
|
|
|
7043
7083
|
await clearAuth(authDir);
|
|
7044
7084
|
const existing = activeLogins.get(accountId);
|
|
7045
7085
|
if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
|
|
7046
|
-
console.error(`${
|
|
7086
|
+
console.error(`${TAG17} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
7047
7087
|
return {
|
|
7048
7088
|
qrDataUrl: existing.qrDataUrl,
|
|
7049
7089
|
qrRaw: existing.qr,
|
|
@@ -7051,7 +7091,7 @@ async function startLogin(opts) {
|
|
|
7051
7091
|
};
|
|
7052
7092
|
}
|
|
7053
7093
|
if (existing) {
|
|
7054
|
-
console.error(`${
|
|
7094
|
+
console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
|
|
7055
7095
|
}
|
|
7056
7096
|
resetActiveLogin(accountId);
|
|
7057
7097
|
let resolveQr = null;
|
|
@@ -7073,14 +7113,14 @@ async function startLogin(opts) {
|
|
|
7073
7113
|
onQr: (qr2) => {
|
|
7074
7114
|
loginQrCount++;
|
|
7075
7115
|
if (pendingQr) {
|
|
7076
|
-
console.error(`${
|
|
7116
|
+
console.error(`${TAG17} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
7077
7117
|
return;
|
|
7078
7118
|
}
|
|
7079
7119
|
pendingQr = qr2;
|
|
7080
7120
|
const current = activeLogins.get(accountId);
|
|
7081
7121
|
if (current && !current.qr) current.qr = qr2;
|
|
7082
7122
|
clearTimeout(qrTimer);
|
|
7083
|
-
console.error(`${
|
|
7123
|
+
console.error(`${TAG17} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
7084
7124
|
resolveQr?.(qr2);
|
|
7085
7125
|
}
|
|
7086
7126
|
});
|
|
@@ -7100,7 +7140,7 @@ async function startLogin(opts) {
|
|
|
7100
7140
|
activeLogins.set(accountId, login);
|
|
7101
7141
|
if (pendingQr && !login.qr) login.qr = pendingQr;
|
|
7102
7142
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
7103
|
-
console.error(`${
|
|
7143
|
+
console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
7104
7144
|
const current = activeLogins.get(accountId);
|
|
7105
7145
|
if (current?.id === login.id) {
|
|
7106
7146
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -7125,7 +7165,7 @@ async function waitForLogin(opts) {
|
|
|
7125
7165
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
7126
7166
|
const login = activeLogins.get(accountId);
|
|
7127
7167
|
console.error(
|
|
7128
|
-
`${
|
|
7168
|
+
`${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
7169
|
);
|
|
7130
7170
|
if (!login) {
|
|
7131
7171
|
return { connected: false, message: "No active WhatsApp login in progress." };
|
|
@@ -7138,7 +7178,7 @@ async function waitForLogin(opts) {
|
|
|
7138
7178
|
while (Date.now() < deadline) {
|
|
7139
7179
|
if (login.connected) {
|
|
7140
7180
|
const selfId = readSelfId(login.authDir);
|
|
7141
|
-
console.error(`${
|
|
7181
|
+
console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
7142
7182
|
const sock = login.sock;
|
|
7143
7183
|
const authDir = login.authDir;
|
|
7144
7184
|
activeLogins.delete(accountId);
|
|
@@ -7158,7 +7198,7 @@ async function waitForLogin(opts) {
|
|
|
7158
7198
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
7159
7199
|
}
|
|
7160
7200
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
7161
|
-
console.error(`${
|
|
7201
|
+
console.error(`${TAG17} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
|
|
7162
7202
|
resetActiveLogin(accountId);
|
|
7163
7203
|
return { connected: false, message: "Login timed out. Try generating a new QR." };
|
|
7164
7204
|
}
|
|
@@ -7272,17 +7312,17 @@ function serializeWhatsAppSchema() {
|
|
|
7272
7312
|
}
|
|
7273
7313
|
|
|
7274
7314
|
// server/routes/whatsapp.ts
|
|
7275
|
-
var
|
|
7315
|
+
var TAG18 = "[whatsapp:api]";
|
|
7276
7316
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
7277
7317
|
var app7 = new Hono();
|
|
7278
7318
|
app7.get("/status", (c) => {
|
|
7279
7319
|
try {
|
|
7280
7320
|
const status = getStatus();
|
|
7281
7321
|
const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
|
|
7282
|
-
console.error(`${
|
|
7322
|
+
console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
|
|
7283
7323
|
return c.json({ accounts: status });
|
|
7284
7324
|
} catch (err) {
|
|
7285
|
-
console.error(`${
|
|
7325
|
+
console.error(`${TAG18} status error: ${String(err)}`);
|
|
7286
7326
|
return c.json({ error: String(err) }, 500);
|
|
7287
7327
|
}
|
|
7288
7328
|
});
|
|
@@ -7293,10 +7333,10 @@ app7.post("/login/start", async (c) => {
|
|
|
7293
7333
|
const force = body.force ?? false;
|
|
7294
7334
|
const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
7295
7335
|
const result = await startLogin({ accountId, authDir, force });
|
|
7296
|
-
console.error(`${
|
|
7336
|
+
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
7297
7337
|
return c.json(result);
|
|
7298
7338
|
} catch (err) {
|
|
7299
|
-
console.error(`${
|
|
7339
|
+
console.error(`${TAG18} login/start error: ${String(err)}`);
|
|
7300
7340
|
return c.json({ error: String(err) }, 500);
|
|
7301
7341
|
}
|
|
7302
7342
|
});
|
|
@@ -7311,7 +7351,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7311
7351
|
try {
|
|
7312
7352
|
await registerLoginSocket(accountId, result.sock, result.authDir);
|
|
7313
7353
|
} catch (regErr) {
|
|
7314
|
-
console.error(`${
|
|
7354
|
+
console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
|
|
7315
7355
|
}
|
|
7316
7356
|
try {
|
|
7317
7357
|
const account = resolveAccount();
|
|
@@ -7319,16 +7359,16 @@ app7.post("/login/wait", async (c) => {
|
|
|
7319
7359
|
const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
|
|
7320
7360
|
configPersisted = persistResult.ok;
|
|
7321
7361
|
if (!persistResult.ok) {
|
|
7322
|
-
console.error(`${
|
|
7362
|
+
console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
|
|
7323
7363
|
}
|
|
7324
7364
|
} else {
|
|
7325
|
-
console.error(`${
|
|
7365
|
+
console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
|
|
7326
7366
|
}
|
|
7327
7367
|
} catch (persistErr) {
|
|
7328
|
-
console.error(`${
|
|
7368
|
+
console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
|
|
7329
7369
|
}
|
|
7330
7370
|
}
|
|
7331
|
-
console.error(`${
|
|
7371
|
+
console.error(`${TAG18} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
|
|
7332
7372
|
return c.json({
|
|
7333
7373
|
connected: result.connected,
|
|
7334
7374
|
message: result.message,
|
|
@@ -7336,7 +7376,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7336
7376
|
configPersisted
|
|
7337
7377
|
});
|
|
7338
7378
|
} catch (err) {
|
|
7339
|
-
console.error(`${
|
|
7379
|
+
console.error(`${TAG18} login/wait error: ${String(err)}`);
|
|
7340
7380
|
return c.json({ error: String(err) }, 500);
|
|
7341
7381
|
}
|
|
7342
7382
|
});
|
|
@@ -7347,7 +7387,7 @@ app7.post("/disconnect", async (c) => {
|
|
|
7347
7387
|
await stopConnection(accountId);
|
|
7348
7388
|
return c.json({ disconnected: true, accountId });
|
|
7349
7389
|
} catch (err) {
|
|
7350
|
-
console.error(`${
|
|
7390
|
+
console.error(`${TAG18} disconnect error: ${String(err)}`);
|
|
7351
7391
|
return c.json({ error: String(err) }, 500);
|
|
7352
7392
|
}
|
|
7353
7393
|
});
|
|
@@ -7358,7 +7398,7 @@ app7.post("/reconnect", async (c) => {
|
|
|
7358
7398
|
await startConnection(accountId);
|
|
7359
7399
|
return c.json({ reconnecting: true, accountId });
|
|
7360
7400
|
} catch (err) {
|
|
7361
|
-
console.error(`${
|
|
7401
|
+
console.error(`${TAG18} reconnect error: ${String(err)}`);
|
|
7362
7402
|
return c.json({ error: String(err) }, 500);
|
|
7363
7403
|
}
|
|
7364
7404
|
});
|
|
@@ -7377,7 +7417,7 @@ app7.post("/send", async (c) => {
|
|
|
7377
7417
|
const result = await sendTextMessage(sock, to, text, { accountId });
|
|
7378
7418
|
return c.json(result);
|
|
7379
7419
|
} catch (err) {
|
|
7380
|
-
console.error(`${
|
|
7420
|
+
console.error(`${TAG18} send error: ${String(err)}`);
|
|
7381
7421
|
return c.json({ error: String(err) }, 500);
|
|
7382
7422
|
}
|
|
7383
7423
|
});
|
|
@@ -7398,7 +7438,7 @@ app7.post("/config", async (c) => {
|
|
|
7398
7438
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
7399
7439
|
}
|
|
7400
7440
|
const result = addAdminPhone(account.accountDir, phone);
|
|
7401
|
-
console.error(`${
|
|
7441
|
+
console.error(`${TAG18} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7402
7442
|
return c.json(result, result.ok ? 200 : 400);
|
|
7403
7443
|
}
|
|
7404
7444
|
case "remove-admin-phone": {
|
|
@@ -7406,12 +7446,12 @@ app7.post("/config", async (c) => {
|
|
|
7406
7446
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
7407
7447
|
}
|
|
7408
7448
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
7409
|
-
console.error(`${
|
|
7449
|
+
console.error(`${TAG18} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7410
7450
|
return c.json(result, result.ok ? 200 : 400);
|
|
7411
7451
|
}
|
|
7412
7452
|
case "list-admin-phones": {
|
|
7413
7453
|
const phones = readAdminPhones(account.accountDir);
|
|
7414
|
-
console.error(`${
|
|
7454
|
+
console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
|
|
7415
7455
|
return c.json({ ok: true, phones });
|
|
7416
7456
|
}
|
|
7417
7457
|
case "set-public-agent": {
|
|
@@ -7419,14 +7459,14 @@ app7.post("/config", async (c) => {
|
|
|
7419
7459
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
7420
7460
|
}
|
|
7421
7461
|
const result = setPublicAgent(account.accountDir, slug);
|
|
7422
|
-
console.error(`${
|
|
7462
|
+
console.error(`${TAG18} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
7423
7463
|
return c.json(result, result.ok ? 200 : 400);
|
|
7424
7464
|
}
|
|
7425
7465
|
case "get-public-agent": {
|
|
7426
7466
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7427
7467
|
const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
|
|
7428
7468
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
|
|
7429
|
-
console.error(`${
|
|
7469
|
+
console.error(`${TAG18} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
7430
7470
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
7431
7471
|
}
|
|
7432
7472
|
case "set-group-public-agent": {
|
|
@@ -7438,7 +7478,7 @@ app7.post("/config", async (c) => {
|
|
|
7438
7478
|
}
|
|
7439
7479
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7440
7480
|
const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
|
|
7441
|
-
console.error(`${
|
|
7481
|
+
console.error(`${TAG18} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
|
|
7442
7482
|
return c.json(result, result.ok ? 200 : 400);
|
|
7443
7483
|
}
|
|
7444
7484
|
case "unset-group-public-agent": {
|
|
@@ -7447,7 +7487,7 @@ app7.post("/config", async (c) => {
|
|
|
7447
7487
|
}
|
|
7448
7488
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7449
7489
|
const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
|
|
7450
|
-
console.error(`${
|
|
7490
|
+
console.error(`${TAG18} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
|
|
7451
7491
|
return c.json(result, result.ok ? 200 : 400);
|
|
7452
7492
|
}
|
|
7453
7493
|
case "list-public-agents": {
|
|
@@ -7464,26 +7504,26 @@ app7.post("/config", async (c) => {
|
|
|
7464
7504
|
const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
|
|
7465
7505
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
7466
7506
|
} catch {
|
|
7467
|
-
console.error(`${
|
|
7507
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
7468
7508
|
}
|
|
7469
7509
|
}
|
|
7470
7510
|
} catch (err) {
|
|
7471
|
-
console.error(`${
|
|
7511
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
7472
7512
|
}
|
|
7473
7513
|
}
|
|
7474
|
-
console.error(`${
|
|
7514
|
+
console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
|
|
7475
7515
|
return c.json({ ok: true, agents });
|
|
7476
7516
|
}
|
|
7477
7517
|
case "schema": {
|
|
7478
7518
|
const text = serializeWhatsAppSchema();
|
|
7479
|
-
console.error(`${
|
|
7519
|
+
console.error(`${TAG18} config action=schema`);
|
|
7480
7520
|
return c.json({ ok: true, text });
|
|
7481
7521
|
}
|
|
7482
7522
|
case "list-groups": {
|
|
7483
7523
|
const groupAccountId = accountId ?? "default";
|
|
7484
7524
|
const sock = getSocket(groupAccountId);
|
|
7485
7525
|
if (!sock) {
|
|
7486
|
-
console.error(`${
|
|
7526
|
+
console.error(`${TAG18} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
7487
7527
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
7488
7528
|
}
|
|
7489
7529
|
try {
|
|
@@ -7493,10 +7533,10 @@ app7.post("/config", async (c) => {
|
|
|
7493
7533
|
name: g.subject ?? g.id,
|
|
7494
7534
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
7495
7535
|
}));
|
|
7496
|
-
console.error(`${
|
|
7536
|
+
console.error(`${TAG18} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
7497
7537
|
return c.json({ ok: true, groups });
|
|
7498
7538
|
} catch (err) {
|
|
7499
|
-
console.error(`${
|
|
7539
|
+
console.error(`${TAG18} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
7500
7540
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
7501
7541
|
}
|
|
7502
7542
|
}
|
|
@@ -7506,12 +7546,12 @@ app7.post("/config", async (c) => {
|
|
|
7506
7546
|
}
|
|
7507
7547
|
const result = updateConfig(account.accountDir, fields);
|
|
7508
7548
|
const fieldNames = Object.keys(fields);
|
|
7509
|
-
console.error(`${
|
|
7549
|
+
console.error(`${TAG18} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
7510
7550
|
return c.json(result, result.ok ? 200 : 400);
|
|
7511
7551
|
}
|
|
7512
7552
|
case "get-config": {
|
|
7513
7553
|
const waConfig = getConfig(account.accountDir);
|
|
7514
|
-
console.error(`${
|
|
7554
|
+
console.error(`${TAG18} config action=get-config`);
|
|
7515
7555
|
return c.json({ ok: true, config: waConfig });
|
|
7516
7556
|
}
|
|
7517
7557
|
default:
|
|
@@ -7521,7 +7561,7 @@ app7.post("/config", async (c) => {
|
|
|
7521
7561
|
);
|
|
7522
7562
|
}
|
|
7523
7563
|
} catch (err) {
|
|
7524
|
-
console.error(`${
|
|
7564
|
+
console.error(`${TAG18} config error: ${String(err)}`);
|
|
7525
7565
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
7526
7566
|
}
|
|
7527
7567
|
});
|
|
@@ -7543,16 +7583,16 @@ app7.post("/send-document", async (c) => {
|
|
|
7543
7583
|
const accountResolved = realpathSync2(accountDir);
|
|
7544
7584
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
7545
7585
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
7546
|
-
console.error(`${
|
|
7586
|
+
console.error(`${TAG18} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
7547
7587
|
return c.json({ error: "Access denied: file is outside the account directory" }, 403);
|
|
7548
7588
|
}
|
|
7549
7589
|
} catch (err) {
|
|
7550
7590
|
const code = err.code;
|
|
7551
7591
|
if (code === "ENOENT") {
|
|
7552
|
-
console.error(`${
|
|
7592
|
+
console.error(`${TAG18} send-document ENOENT path=${filePath}`);
|
|
7553
7593
|
return c.json({ error: `File not found: ${filePath}` }, 404);
|
|
7554
7594
|
}
|
|
7555
|
-
console.error(`${
|
|
7595
|
+
console.error(`${TAG18} send-document path error: ${String(err)}`);
|
|
7556
7596
|
return c.json({ error: String(err) }, 500);
|
|
7557
7597
|
}
|
|
7558
7598
|
const fileStat = await stat3(resolvedPath);
|
|
@@ -7574,11 +7614,11 @@ app7.post("/send-document", async (c) => {
|
|
|
7574
7614
|
caption
|
|
7575
7615
|
}, { accountId });
|
|
7576
7616
|
console.error(
|
|
7577
|
-
`${
|
|
7617
|
+
`${TAG18} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
7578
7618
|
);
|
|
7579
7619
|
return c.json(result);
|
|
7580
7620
|
} catch (err) {
|
|
7581
|
-
console.error(`${
|
|
7621
|
+
console.error(`${TAG18} send-document error: ${String(err)}`);
|
|
7582
7622
|
return c.json({ error: String(err) }, 500);
|
|
7583
7623
|
}
|
|
7584
7624
|
});
|
|
@@ -7588,11 +7628,11 @@ app7.get("/activity", (c) => {
|
|
|
7588
7628
|
const result = getChannelActivity(accountId);
|
|
7589
7629
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
7590
7630
|
console.error(
|
|
7591
|
-
`${
|
|
7631
|
+
`${TAG18} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
7592
7632
|
);
|
|
7593
7633
|
return c.json(result);
|
|
7594
7634
|
} catch (err) {
|
|
7595
|
-
console.error(`${
|
|
7635
|
+
console.error(`${TAG18} activity error: ${String(err)}`);
|
|
7596
7636
|
return c.json({ error: String(err) }, 500);
|
|
7597
7637
|
}
|
|
7598
7638
|
});
|
|
@@ -7611,10 +7651,10 @@ app7.get("/conversations", (c) => {
|
|
|
7611
7651
|
};
|
|
7612
7652
|
});
|
|
7613
7653
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
7614
|
-
console.error(`${
|
|
7654
|
+
console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
|
|
7615
7655
|
return c.json({ conversations });
|
|
7616
7656
|
} catch (err) {
|
|
7617
|
-
console.error(`${
|
|
7657
|
+
console.error(`${TAG18} conversations error: ${String(err)}`);
|
|
7618
7658
|
return c.json({ error: String(err) }, 500);
|
|
7619
7659
|
}
|
|
7620
7660
|
});
|
|
@@ -7629,10 +7669,10 @@ app7.get("/messages", (c) => {
|
|
|
7629
7669
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
7630
7670
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
7631
7671
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
7632
|
-
console.error(`${
|
|
7672
|
+
console.error(`${TAG18} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
7633
7673
|
return c.json({ messages });
|
|
7634
7674
|
} catch (err) {
|
|
7635
|
-
console.error(`${
|
|
7675
|
+
console.error(`${TAG18} messages error: ${String(err)}`);
|
|
7636
7676
|
return c.json({ error: String(err) }, 500);
|
|
7637
7677
|
}
|
|
7638
7678
|
});
|
|
@@ -7644,12 +7684,12 @@ app7.get("/group-info", async (c) => {
|
|
|
7644
7684
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
7645
7685
|
}
|
|
7646
7686
|
if (!isGroupJid(jid)) {
|
|
7647
|
-
console.error(`${
|
|
7687
|
+
console.error(`${TAG18} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
7648
7688
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
7649
7689
|
}
|
|
7650
7690
|
const sock = getSocket(accountId);
|
|
7651
7691
|
if (!sock) {
|
|
7652
|
-
console.error(`${
|
|
7692
|
+
console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
|
|
7653
7693
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
7654
7694
|
}
|
|
7655
7695
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -7662,10 +7702,10 @@ app7.get("/group-info", async (c) => {
|
|
|
7662
7702
|
participantCount: meta.participants.length,
|
|
7663
7703
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
7664
7704
|
};
|
|
7665
|
-
console.error(`${
|
|
7705
|
+
console.error(`${TAG18} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
7666
7706
|
return c.json(result);
|
|
7667
7707
|
} catch (err) {
|
|
7668
|
-
console.error(`${
|
|
7708
|
+
console.error(`${TAG18} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
7669
7709
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
7670
7710
|
}
|
|
7671
7711
|
});
|
|
@@ -8546,7 +8586,7 @@ var app11 = new Hono();
|
|
|
8546
8586
|
app11.post("/cancel", requireAdminSession, async (c) => {
|
|
8547
8587
|
const session_key = c.var.sessionKey;
|
|
8548
8588
|
try {
|
|
8549
|
-
const { interruptClient: interruptClient2 } = await import("./client-pool-
|
|
8589
|
+
const { interruptClient: interruptClient2 } = await import("./client-pool-LXE7RIRT.js");
|
|
8550
8590
|
await interruptClient2(session_key);
|
|
8551
8591
|
return c.json({ ok: true });
|
|
8552
8592
|
} catch (err) {
|
|
@@ -9426,16 +9466,26 @@ app17.get("/", requireAdminSession, async (c) => {
|
|
|
9426
9466
|
sessionKey: null,
|
|
9427
9467
|
name: r.name,
|
|
9428
9468
|
updatedAt: r.updatedAt,
|
|
9429
|
-
phase: "flushed"
|
|
9469
|
+
phase: "flushed",
|
|
9470
|
+
channel: r.channel
|
|
9430
9471
|
}));
|
|
9431
9472
|
const inProgressRows = inProgressRaw.map((r) => ({
|
|
9432
9473
|
conversationId: null,
|
|
9433
9474
|
sessionKey: r.sessionKey,
|
|
9434
9475
|
name: null,
|
|
9435
9476
|
updatedAt: new Date(r.createdAt).toISOString(),
|
|
9436
|
-
phase: "pre-flush"
|
|
9477
|
+
phase: "pre-flush",
|
|
9478
|
+
channel: "webchat"
|
|
9437
9479
|
}));
|
|
9438
9480
|
const sessions = [...flushedRows, ...inProgressRows].sort((a, b) => a.updatedAt < b.updatedAt ? 1 : a.updatedAt > b.updatedAt ? -1 : 0).slice(0, 20);
|
|
9481
|
+
const channelCounts = sessions.reduce((acc, s) => {
|
|
9482
|
+
const k = s.channel ?? "unknown";
|
|
9483
|
+
acc[k] = (acc[k] ?? 0) + 1;
|
|
9484
|
+
return acc;
|
|
9485
|
+
}, {});
|
|
9486
|
+
console.error(
|
|
9487
|
+
`[conversations-list] render rows=${sessions.length} channels=${JSON.stringify(channelCounts)}`
|
|
9488
|
+
);
|
|
9439
9489
|
return c.json({ sessions });
|
|
9440
9490
|
} catch (err) {
|
|
9441
9491
|
console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9699,13 +9749,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
|
|
|
9699
9749
|
// server/routes/admin/device-browser.ts
|
|
9700
9750
|
var app19 = new Hono();
|
|
9701
9751
|
app19.post("/navigate", async (c) => {
|
|
9702
|
-
const
|
|
9752
|
+
const TAG19 = "[device-url:click]";
|
|
9703
9753
|
let body;
|
|
9704
9754
|
try {
|
|
9705
9755
|
body = await c.req.json();
|
|
9706
9756
|
} catch (err) {
|
|
9707
9757
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9708
|
-
console.error(`${
|
|
9758
|
+
console.error(`${TAG19} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
|
|
9709
9759
|
return c.json(
|
|
9710
9760
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
|
|
9711
9761
|
400
|
|
@@ -9715,7 +9765,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9715
9765
|
const intent = typeof body.intent === "string" ? body.intent : "";
|
|
9716
9766
|
const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
|
|
9717
9767
|
if (!url) {
|
|
9718
|
-
console.error(`${
|
|
9768
|
+
console.error(`${TAG19} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
|
|
9719
9769
|
return c.json(
|
|
9720
9770
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
|
|
9721
9771
|
400
|
|
@@ -9725,7 +9775,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9725
9775
|
try {
|
|
9726
9776
|
parsed = new URL(url);
|
|
9727
9777
|
} catch {
|
|
9728
|
-
console.error(`${
|
|
9778
|
+
console.error(`${TAG19} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
|
|
9729
9779
|
return c.json(
|
|
9730
9780
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
|
|
9731
9781
|
400
|
|
@@ -9733,7 +9783,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9733
9783
|
}
|
|
9734
9784
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
9735
9785
|
console.error(
|
|
9736
|
-
`${
|
|
9786
|
+
`${TAG19} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
|
|
9737
9787
|
);
|
|
9738
9788
|
return c.json(
|
|
9739
9789
|
{
|
|
@@ -9749,7 +9799,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9749
9799
|
const cdpOk = await ensureCdp(transport);
|
|
9750
9800
|
if (!cdpOk) {
|
|
9751
9801
|
console.error(
|
|
9752
|
-
`${
|
|
9802
|
+
`${TAG19} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
|
|
9753
9803
|
);
|
|
9754
9804
|
return c.json(
|
|
9755
9805
|
{
|
|
@@ -9765,7 +9815,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9765
9815
|
const browser = outcome.result === "ok" ? "vnc" : "fallback";
|
|
9766
9816
|
const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
|
|
9767
9817
|
console.error(
|
|
9768
|
-
`${
|
|
9818
|
+
`${TAG19} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
|
|
9769
9819
|
);
|
|
9770
9820
|
if (outcome.result !== "ok") {
|
|
9771
9821
|
return c.json(
|
|
@@ -9796,18 +9846,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9796
9846
|
]);
|
|
9797
9847
|
var app20 = new Hono();
|
|
9798
9848
|
app20.post("/", async (c) => {
|
|
9799
|
-
const
|
|
9849
|
+
const TAG19 = "[admin:events]";
|
|
9800
9850
|
let body;
|
|
9801
9851
|
try {
|
|
9802
9852
|
body = await c.req.json();
|
|
9803
9853
|
} catch (err) {
|
|
9804
9854
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9805
|
-
console.error(`${
|
|
9855
|
+
console.error(`${TAG19} reject reason=body-not-json detail=${detail}`);
|
|
9806
9856
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
9807
9857
|
}
|
|
9808
9858
|
const event = typeof body.event === "string" ? body.event : "";
|
|
9809
9859
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
9810
|
-
console.error(`${
|
|
9860
|
+
console.error(`${TAG19} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
9811
9861
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
9812
9862
|
}
|
|
9813
9863
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -11157,6 +11207,8 @@ function plainProperties(properties) {
|
|
|
11157
11207
|
// server/routes/admin/graph-search.ts
|
|
11158
11208
|
var DEFAULT_LIMIT = 20;
|
|
11159
11209
|
var MAX_LIMIT = 2e3;
|
|
11210
|
+
var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
|
|
11211
|
+
var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
|
|
11160
11212
|
var app23 = new Hono();
|
|
11161
11213
|
app23.get("/", requireAdminSession, async (c) => {
|
|
11162
11214
|
const sessionKey = c.var.sessionKey;
|
|
@@ -11175,13 +11227,15 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11175
11227
|
}
|
|
11176
11228
|
const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
|
|
11177
11229
|
const limit = Number.isFinite(parsedLimit) && parsedLimit > 0 ? Math.min(parsedLimit, MAX_LIMIT) : DEFAULT_LIMIT;
|
|
11230
|
+
const wantsBodyFulltext = labels.some((l) => CONVERSATION_PARENT_LABELS.has(l));
|
|
11231
|
+
const expandedLabels = wantsBodyFulltext ? Array.from(/* @__PURE__ */ new Set([...labels, ...MESSAGE_FAMILY_LABELS])) : labels;
|
|
11178
11232
|
const started = Date.now();
|
|
11179
11233
|
const session = getSession();
|
|
11180
11234
|
try {
|
|
11181
11235
|
const res = await hybrid(session, embed, {
|
|
11182
11236
|
query: q,
|
|
11183
11237
|
accountId,
|
|
11184
|
-
labels,
|
|
11238
|
+
labels: expandedLabels,
|
|
11185
11239
|
limit,
|
|
11186
11240
|
degradeOnEmbedFailure: true
|
|
11187
11241
|
});
|
|
@@ -11190,11 +11244,59 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11190
11244
|
console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
|
|
11191
11245
|
console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
|
|
11192
11246
|
}
|
|
11247
|
+
let labelMatchCount = 0;
|
|
11248
|
+
let bodyFulltextCount = 0;
|
|
11249
|
+
let resolvedResults = res.results;
|
|
11250
|
+
if (wantsBodyFulltext) {
|
|
11251
|
+
const userLabelSet = new Set(labels);
|
|
11252
|
+
const seen = /* @__PURE__ */ new Map();
|
|
11253
|
+
for (const r of res.results) {
|
|
11254
|
+
const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
|
|
11255
|
+
if (isMessage) continue;
|
|
11256
|
+
if (r.labels.some((l) => userLabelSet.has(l))) {
|
|
11257
|
+
const existing = seen.get(r.nodeId);
|
|
11258
|
+
if (!existing || existing.score < r.score) {
|
|
11259
|
+
seen.set(r.nodeId, r);
|
|
11260
|
+
}
|
|
11261
|
+
labelMatchCount++;
|
|
11262
|
+
}
|
|
11263
|
+
}
|
|
11264
|
+
for (const r of res.results) {
|
|
11265
|
+
const isMessage = r.labels.some((l) => MESSAGE_FAMILY_LABELS.includes(l));
|
|
11266
|
+
if (!isMessage) continue;
|
|
11267
|
+
const parent = r.related.find(
|
|
11268
|
+
(rel) => rel.relationship === "PART_OF" && rel.direction === "outgoing" && rel.labels.some((l) => userLabelSet.has(l))
|
|
11269
|
+
);
|
|
11270
|
+
if (!parent) continue;
|
|
11271
|
+
const existing = seen.get(parent.nodeId);
|
|
11272
|
+
if (existing) {
|
|
11273
|
+
if (existing.score < r.score) {
|
|
11274
|
+
seen.set(parent.nodeId, { ...existing, score: r.score });
|
|
11275
|
+
}
|
|
11276
|
+
} else {
|
|
11277
|
+
seen.set(parent.nodeId, {
|
|
11278
|
+
nodeId: parent.nodeId,
|
|
11279
|
+
labels: parent.labels,
|
|
11280
|
+
properties: parent.properties,
|
|
11281
|
+
score: r.score,
|
|
11282
|
+
related: []
|
|
11283
|
+
});
|
|
11284
|
+
}
|
|
11285
|
+
bodyFulltextCount++;
|
|
11286
|
+
}
|
|
11287
|
+
resolvedResults = Array.from(seen.values()).sort((a, b) => b.score - a.score);
|
|
11288
|
+
console.error(
|
|
11289
|
+
`[graph-search] label-match query="${q}" hits=${labelMatchCount}`
|
|
11290
|
+
);
|
|
11291
|
+
console.error(
|
|
11292
|
+
`[graph-search] body-fulltext query="${q}" hits=${bodyFulltextCount} ms=${total}`
|
|
11293
|
+
);
|
|
11294
|
+
}
|
|
11193
11295
|
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}`
|
|
11296
|
+
`[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
11297
|
);
|
|
11196
11298
|
return c.json({
|
|
11197
|
-
results:
|
|
11299
|
+
results: resolvedResults,
|
|
11198
11300
|
mode: res.mode,
|
|
11199
11301
|
embedError: res.embedError,
|
|
11200
11302
|
elapsedMs: total
|
|
@@ -11393,6 +11495,10 @@ async function handleDefault(c, accountId) {
|
|
|
11393
11495
|
const includeTrashed = c.req.query("includeTrashed") === "1";
|
|
11394
11496
|
const includeAgentActions = c.req.query("includeAgentActions") === "1";
|
|
11395
11497
|
const agentActionLabels = includeAgentActions ? [] : [...AGENT_ACTION_LABELS];
|
|
11498
|
+
const rawChannel = c.req.query("channel") ?? "";
|
|
11499
|
+
const channel = rawChannel.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11500
|
+
const rawMessageSublabel = c.req.query("messageSublabel") ?? "";
|
|
11501
|
+
const messageSublabel = rawMessageSublabel.split(",").map((s) => s.trim()).filter(Boolean);
|
|
11396
11502
|
if (labels.length === 0) {
|
|
11397
11503
|
console.error(
|
|
11398
11504
|
`[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
|
|
@@ -11424,7 +11530,7 @@ async function handleDefault(c, accountId) {
|
|
|
11424
11530
|
try {
|
|
11425
11531
|
const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
|
|
11426
11532
|
const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
|
|
11427
|
-
const cypherParams = { accountId, labels, agentActionLabels };
|
|
11533
|
+
const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
|
|
11428
11534
|
const result = await session.executeRead(async (tx) => {
|
|
11429
11535
|
const countResult = await tx.run(countCypher, cypherParams);
|
|
11430
11536
|
const matchedRaw = countResult.records[0]?.get("matched");
|
|
@@ -11456,8 +11562,10 @@ async function handleDefault(c, accountId) {
|
|
|
11456
11562
|
const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
|
|
11457
11563
|
const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
|
|
11458
11564
|
const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
|
|
11565
|
+
const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
|
|
11566
|
+
const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
|
|
11459
11567
|
console.error(
|
|
11460
|
-
`[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
11568
|
+
`[graph-page] load mode=default account=${accountId} agentActions=${includeAgentActions} labels=${labels.join(",")}${trashedSuffix}${channelSuffix}${sublabelSuffix} nodes=${nodes.length} edges=${edges.length} ms=${elapsed}`
|
|
11461
11569
|
);
|
|
11462
11570
|
return c.json({ nodes, edges });
|
|
11463
11571
|
} catch (err) {
|
|
@@ -11553,13 +11661,22 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
11553
11661
|
}
|
|
11554
11662
|
}
|
|
11555
11663
|
}
|
|
11664
|
+
var SUBFACET_PREDICATE = `
|
|
11665
|
+
AND (size($channel) = 0
|
|
11666
|
+
OR NOT any(lbl IN labels(n) WHERE lbl IN ['Conversation','AdminConversation','PublicConversation'])
|
|
11667
|
+
OR n.channel IS NULL
|
|
11668
|
+
OR n.channel IN $channel)
|
|
11669
|
+
AND (size($messageSublabel) = 0
|
|
11670
|
+
OR NOT any(lbl IN labels(n) WHERE lbl IN ['Message','UserMessage','AssistantMessage','WhatsAppMessage'])
|
|
11671
|
+
OR any(lbl IN labels(n) WHERE lbl IN $messageSublabel))
|
|
11672
|
+
`;
|
|
11556
11673
|
var DEFAULT_COUNT_CYPHER = `
|
|
11557
11674
|
MATCH (n)
|
|
11558
11675
|
WHERE n.accountId = $accountId
|
|
11559
11676
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11560
11677
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11561
11678
|
AND NOT n:Trashed
|
|
11562
|
-
AND n.deletedAt IS NULL
|
|
11679
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11563
11680
|
RETURN count(n) AS matched
|
|
11564
11681
|
`;
|
|
11565
11682
|
var CONVERSATION_PROPS_PROJECTION = `CASE
|
|
@@ -11573,7 +11690,7 @@ var DEFAULT_FETCH_CYPHER = `
|
|
|
11573
11690
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11574
11691
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11575
11692
|
AND NOT n:Trashed
|
|
11576
|
-
AND n.deletedAt IS NULL
|
|
11693
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11577
11694
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11578
11695
|
UNWIND nodes AS n
|
|
11579
11696
|
OPTIONAL MATCH (n)-[r]-(m)
|
|
@@ -11595,7 +11712,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
|
|
|
11595
11712
|
WHERE n.accountId = $accountId
|
|
11596
11713
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11597
11714
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11598
|
-
AND n.deletedAt IS NULL
|
|
11715
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11599
11716
|
RETURN count(n) AS matched
|
|
11600
11717
|
`;
|
|
11601
11718
|
var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
@@ -11603,7 +11720,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
|
11603
11720
|
WHERE n.accountId = $accountId
|
|
11604
11721
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11605
11722
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11606
|
-
AND n.deletedAt IS NULL
|
|
11723
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11607
11724
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11608
11725
|
UNWIND nodes AS n
|
|
11609
11726
|
OPTIONAL MATCH (n)-[r]-(m)
|