@rubytech/create-maxy 1.0.805 → 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/cloudflare.md +1 -1
- package/payload/platform/plugins/docs/references/graph.md +42 -0
- package/payload/platform/plugins/docs/references/internals.md +11 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/whatsapp-import/PLUGIN.md +18 -5
- package/payload/platform/plugins/whatsapp-import/skills/whatsapp-import-enrich/SKILL.md +314 -0
- package/payload/platform/templates/agents/admin/IDENTITY.md +3 -1
- package/payload/platform/templates/specialists/agents/database-operator.md +5 -2
- package/payload/server/chunk-LSUMH6OF.js +9993 -0
- package/payload/server/chunk-LTIWPCUF.js +3477 -0
- package/payload/server/chunk-SC3ZSD7N.js +9993 -0
- package/payload/server/chunk-YULDSPAC.js +3484 -0
- package/payload/server/client-pool-CD7WHZIK.js +31 -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 +348 -202
- 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
|
|
@@ -923,6 +924,27 @@ function defaultRules() {
|
|
|
923
924
|
// this rule it was written but never read (the review-detector had no
|
|
924
925
|
// rule coverage for it). A single ERR line is worth surfacing; the
|
|
925
926
|
// tee'd output is typically noise-free.
|
|
927
|
+
// Task 862: setup-tunnel.sh emits `[script:setup-tunnel]
|
|
928
|
+
// step=onboarding-persist result=skipped reason=no-account-dir` via
|
|
929
|
+
// phase_line when ACCOUNT_DIR is unset. Pre-Task-862, the form-driven
|
|
930
|
+
// action runner threaded STREAM_LOG_PATH but not ACCOUNT_DIR, so the
|
|
931
|
+
// line landed in the agent's stream log (system source) and the user
|
|
932
|
+
// looped on currentStep=6 indefinitely.
|
|
933
|
+
// The agent-via-Bash path also threads STREAM_LOG_PATH; operator-SSH
|
|
934
|
+
// does not — that's the disambiguator. If this pattern reappears in
|
|
935
|
+
// a system log, a future invocation surface forgot to declare
|
|
936
|
+
// ACCOUNT_DIR. Fix at action-runner.ts WHITELIST['cloudflare-setup'],
|
|
937
|
+
// not in the script.
|
|
938
|
+
id: "cloudflare-setup-account-dir-missing",
|
|
939
|
+
name: "cloudflare-setup ran without ACCOUNT_DIR \u2014 onboarding step-7 will not persist",
|
|
940
|
+
type: "silent-catch",
|
|
941
|
+
logSource: "system",
|
|
942
|
+
pattern: "\\[script:setup-tunnel\\] step=onboarding-persist result=skipped reason=no-account-dir",
|
|
943
|
+
thresholdCount: 0,
|
|
944
|
+
thresholdWindowMinutes: 0,
|
|
945
|
+
suggestedAction: "An invocation surface for setup-tunnel.sh failed to declare ACCOUNT_DIR. Without it the script's step-7 persist block (Task 562) skips, leaving OnboardingState.currentStep=6 forever. Inspect platform/ui/server/lib/action-runner.ts WHITELIST['cloudflare-setup'].build (Task 862 thread) and platform/ui/app/lib/claude-agent/spawn-env.ts buildSpawnEnv (Task 562 thread) \u2014 confirm ACCOUNT_DIR is in the env map for whichever surface the action-id prefix indicates. Do NOT change setup-tunnel.sh; the skipped branch is correct for operator-SSH reconfigure flows."
|
|
946
|
+
},
|
|
947
|
+
{
|
|
926
948
|
id: "cloudflared-edge-errors",
|
|
927
949
|
name: "cloudflared edge connectivity errors",
|
|
928
950
|
type: "silent-catch",
|
|
@@ -3718,6 +3740,37 @@ function sanitizeReason(err) {
|
|
|
3718
3740
|
return `${err.name}:${msg}`;
|
|
3719
3741
|
}
|
|
3720
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
|
+
|
|
3721
3774
|
// app/lib/whatsapp/inbound/media.ts
|
|
3722
3775
|
import { randomUUID as randomUUID3 } from "crypto";
|
|
3723
3776
|
import { writeFile, mkdir } from "fs/promises";
|
|
@@ -3727,7 +3780,7 @@ import {
|
|
|
3727
3780
|
downloadContentFromMessage,
|
|
3728
3781
|
normalizeMessageContent as normalizeMessageContent2
|
|
3729
3782
|
} from "@whiskeysockets/baileys";
|
|
3730
|
-
var
|
|
3783
|
+
var TAG9 = "[whatsapp:media]";
|
|
3731
3784
|
var MEDIA_DIR = "/tmp/maxy-media";
|
|
3732
3785
|
function mimeToExt(mimetype) {
|
|
3733
3786
|
const map = {
|
|
@@ -3783,25 +3836,25 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3783
3836
|
}
|
|
3784
3837
|
);
|
|
3785
3838
|
if (!buffer || buffer.length === 0) {
|
|
3786
|
-
console.error(`${
|
|
3839
|
+
console.error(`${TAG9} primary download returned empty, trying direct fallback`);
|
|
3787
3840
|
const downloadable = getDownloadableContent(content);
|
|
3788
3841
|
if (downloadable) {
|
|
3789
3842
|
try {
|
|
3790
3843
|
const stream = await downloadContentFromMessage(downloadable.downloadable, downloadable.mediaType);
|
|
3791
3844
|
buffer = await streamToBuffer(stream);
|
|
3792
3845
|
} catch (fallbackErr) {
|
|
3793
|
-
console.error(`${
|
|
3846
|
+
console.error(`${TAG9} direct download fallback failed: ${String(fallbackErr)}`);
|
|
3794
3847
|
}
|
|
3795
3848
|
}
|
|
3796
3849
|
}
|
|
3797
3850
|
if (!buffer || buffer.length === 0) {
|
|
3798
|
-
console.error(`${
|
|
3851
|
+
console.error(`${TAG9} download failed: empty buffer for ${mimetype ?? "unknown"}`);
|
|
3799
3852
|
return void 0;
|
|
3800
3853
|
}
|
|
3801
3854
|
if (buffer.length > maxBytes) {
|
|
3802
3855
|
const sizeMB = (buffer.length / (1024 * 1024)).toFixed(1);
|
|
3803
3856
|
const limitMB = (maxBytes / (1024 * 1024)).toFixed(0);
|
|
3804
|
-
console.error(`${
|
|
3857
|
+
console.error(`${TAG9} media too large type=${mimetype ?? "unknown"} size=${sizeMB}MB limit=${limitMB}MB`);
|
|
3805
3858
|
return void 0;
|
|
3806
3859
|
}
|
|
3807
3860
|
await mkdir(MEDIA_DIR, { recursive: true });
|
|
@@ -3810,20 +3863,20 @@ async function downloadInboundMedia(msg, sock, opts) {
|
|
|
3810
3863
|
const filePath = join4(MEDIA_DIR, filename);
|
|
3811
3864
|
await writeFile(filePath, buffer);
|
|
3812
3865
|
const sizeKB = (buffer.length / 1024).toFixed(0);
|
|
3813
|
-
console.error(`${
|
|
3866
|
+
console.error(`${TAG9} media downloaded type=${mimetype ?? "unknown"} size=${sizeKB}KB path=${filePath}`);
|
|
3814
3867
|
return {
|
|
3815
3868
|
path: filePath,
|
|
3816
3869
|
mimetype: mimetype ?? "application/octet-stream",
|
|
3817
3870
|
size: buffer.length
|
|
3818
3871
|
};
|
|
3819
3872
|
} catch (err) {
|
|
3820
|
-
console.error(`${
|
|
3873
|
+
console.error(`${TAG9} media download failed type=${mimetype ?? "unknown"} error=${String(err)}`);
|
|
3821
3874
|
return void 0;
|
|
3822
3875
|
}
|
|
3823
3876
|
}
|
|
3824
3877
|
|
|
3825
3878
|
// app/lib/whatsapp/inbound/debounce.ts
|
|
3826
|
-
var
|
|
3879
|
+
var TAG10 = "[whatsapp:debounce]";
|
|
3827
3880
|
var STT_TAG = "[whatsapp:stt-await]";
|
|
3828
3881
|
function createInboundDebouncer(opts) {
|
|
3829
3882
|
const { debounceMs, buildKey, onFlush, onError } = opts;
|
|
@@ -3854,7 +3907,7 @@ function createInboundDebouncer(opts) {
|
|
|
3854
3907
|
pending.delete(key);
|
|
3855
3908
|
const batchSize = batch.entries.length;
|
|
3856
3909
|
try {
|
|
3857
|
-
console.error(`${
|
|
3910
|
+
console.error(`${TAG10} debounce flush key=${key} batchSize=${batchSize}`);
|
|
3858
3911
|
const result = onFlush(batch.entries);
|
|
3859
3912
|
if (result && typeof result.catch === "function") {
|
|
3860
3913
|
result.catch(onError);
|
|
@@ -3926,7 +3979,7 @@ function createInboundDebouncer(opts) {
|
|
|
3926
3979
|
}
|
|
3927
3980
|
|
|
3928
3981
|
// app/lib/whatsapp/opening-hours.ts
|
|
3929
|
-
var
|
|
3982
|
+
var TAG11 = "[whatsapp:hours]";
|
|
3930
3983
|
async function isBusinessOpen(accountId) {
|
|
3931
3984
|
try {
|
|
3932
3985
|
const timezone = await resolveTimezone(accountId);
|
|
@@ -3944,7 +3997,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3944
3997
|
{ accountId, dayOfWeek, previousDayOfWeek }
|
|
3945
3998
|
);
|
|
3946
3999
|
if (result.records.length === 0) {
|
|
3947
|
-
console.error(`${
|
|
4000
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3948
4001
|
return { open: true, reason: "no opening hours configured" };
|
|
3949
4002
|
}
|
|
3950
4003
|
const specs = result.records.filter((r) => r.get("opens") != null && r.get("closes") != null).map((r) => ({
|
|
@@ -3953,7 +4006,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3953
4006
|
closes: String(r.get("closes")).trim()
|
|
3954
4007
|
}));
|
|
3955
4008
|
if (specs.length === 0) {
|
|
3956
|
-
console.error(`${
|
|
4009
|
+
console.error(`${TAG11} [${accountId}] business hours check: no opening hours configured, treating as open`);
|
|
3957
4010
|
return { open: true, reason: "no opening hours configured" };
|
|
3958
4011
|
}
|
|
3959
4012
|
for (const spec of specs) {
|
|
@@ -3963,7 +4016,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3963
4016
|
if (spec.opens > spec.closes && currentTime < spec.closes) {
|
|
3964
4017
|
const hoursStr = `${spec.opens}-${spec.closes} (${spec.day})`;
|
|
3965
4018
|
console.error(
|
|
3966
|
-
`${
|
|
4019
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3967
4020
|
);
|
|
3968
4021
|
return {
|
|
3969
4022
|
open: true,
|
|
@@ -3976,7 +4029,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3976
4029
|
} else if (isTimeInRange(currentTime, spec.opens, spec.closes)) {
|
|
3977
4030
|
const hoursStr = `${spec.opens}-${spec.closes}`;
|
|
3978
4031
|
console.error(
|
|
3979
|
-
`${
|
|
4032
|
+
`${TAG11} [${accountId}] business hours check: open (day=${dayOfWeek}, time=${currentTime}, hours=${hoursStr})`
|
|
3980
4033
|
);
|
|
3981
4034
|
return {
|
|
3982
4035
|
open: true,
|
|
@@ -3990,7 +4043,7 @@ async function isBusinessOpen(accountId) {
|
|
|
3990
4043
|
const todaySpecs = specs.filter((s) => s.day === dayOfWeek);
|
|
3991
4044
|
const allHours = todaySpecs.map((s) => `${s.opens}-${s.closes}`).join(", ") || "none today";
|
|
3992
4045
|
console.error(
|
|
3993
|
-
`${
|
|
4046
|
+
`${TAG11} [${accountId}] business hours check: closed (day=${dayOfWeek}, time=${currentTime}, hours=${allHours})`
|
|
3994
4047
|
);
|
|
3995
4048
|
return {
|
|
3996
4049
|
open: false,
|
|
@@ -4004,7 +4057,7 @@ async function isBusinessOpen(accountId) {
|
|
|
4004
4057
|
}
|
|
4005
4058
|
} catch (err) {
|
|
4006
4059
|
console.error(
|
|
4007
|
-
`${
|
|
4060
|
+
`${TAG11} [${accountId}] business hours check failed, treating as open: ${err instanceof Error ? err.message : String(err)}`
|
|
4008
4061
|
);
|
|
4009
4062
|
return { open: true, reason: "hours check failed (treating as open)" };
|
|
4010
4063
|
}
|
|
@@ -4050,7 +4103,7 @@ import { execFile } from "child_process";
|
|
|
4050
4103
|
import { unlink, stat } from "fs/promises";
|
|
4051
4104
|
import { promisify } from "util";
|
|
4052
4105
|
var execFileAsync = promisify(execFile);
|
|
4053
|
-
var
|
|
4106
|
+
var TAG12 = "[stt]";
|
|
4054
4107
|
var WHISPER_BINARY = process.env.WHISPER_BINARY ?? "/opt/whisper.cpp/build/bin/whisper-cli";
|
|
4055
4108
|
var WHISPER_MODEL = process.env.WHISPER_MODEL ?? "/opt/whisper.cpp/models/ggml-base.bin";
|
|
4056
4109
|
var WHISPER_TIMEOUT_MS = 20 * 60 * 1e3;
|
|
@@ -4061,11 +4114,11 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4061
4114
|
const s = await stat(audioPath);
|
|
4062
4115
|
audioBytes = s.size;
|
|
4063
4116
|
} catch {
|
|
4064
|
-
console.error(`${
|
|
4117
|
+
console.error(`${TAG12} failed: file not readable path=${audioPath}`);
|
|
4065
4118
|
return void 0;
|
|
4066
4119
|
}
|
|
4067
4120
|
console.error(
|
|
4068
|
-
`${
|
|
4121
|
+
`${TAG12} start provider=whisper-local audio_bytes=${audioBytes} mimetype=${mimetype} path=${audioPath}`
|
|
4069
4122
|
);
|
|
4070
4123
|
const wavPath = audioPath.replace(/\.[^.]+$/, "") + ".wav";
|
|
4071
4124
|
try {
|
|
@@ -4084,7 +4137,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4084
4137
|
], { timeout: 3e4 });
|
|
4085
4138
|
} catch (err) {
|
|
4086
4139
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4087
|
-
console.error(`${
|
|
4140
|
+
console.error(`${TAG12} failed: ffmpeg conversion error=${reason}`);
|
|
4088
4141
|
return void 0;
|
|
4089
4142
|
}
|
|
4090
4143
|
try {
|
|
@@ -4102,20 +4155,20 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4102
4155
|
const text = stdout.trim();
|
|
4103
4156
|
const durationMs = Date.now() - startMs;
|
|
4104
4157
|
if (!text) {
|
|
4105
|
-
console.error(`${
|
|
4158
|
+
console.error(`${TAG12} failed: whisper returned empty output duration_ms=${durationMs}`);
|
|
4106
4159
|
return void 0;
|
|
4107
4160
|
}
|
|
4108
4161
|
const langMatch = stderr.match(/auto-detected language:\s*(\w+)/);
|
|
4109
4162
|
const language = langMatch?.[1] ?? "unknown";
|
|
4110
4163
|
const words = text.split(/\s+/).filter(Boolean).length;
|
|
4111
4164
|
console.error(
|
|
4112
|
-
`${
|
|
4165
|
+
`${TAG12} done provider=whisper-local duration_ms=${durationMs} words=${words} lang=${language}`
|
|
4113
4166
|
);
|
|
4114
4167
|
return { text, language, durationMs };
|
|
4115
4168
|
} catch (err) {
|
|
4116
4169
|
const durationMs = Date.now() - startMs;
|
|
4117
4170
|
const reason = err instanceof Error ? err.message : String(err);
|
|
4118
|
-
console.error(`${
|
|
4171
|
+
console.error(`${TAG12} failed provider=whisper-local duration_ms=${durationMs} error=${reason}`);
|
|
4119
4172
|
return void 0;
|
|
4120
4173
|
} finally {
|
|
4121
4174
|
unlink(wavPath).catch(() => {
|
|
@@ -4124,7 +4177,7 @@ async function transcribe(audioPath, mimetype) {
|
|
|
4124
4177
|
}
|
|
4125
4178
|
|
|
4126
4179
|
// app/lib/whatsapp/manager.ts
|
|
4127
|
-
var
|
|
4180
|
+
var TAG13 = "[whatsapp:manager]";
|
|
4128
4181
|
var MAX_RECONNECT_ATTEMPTS = 10;
|
|
4129
4182
|
var MESSAGE_STORE_MAX = 500;
|
|
4130
4183
|
var GROUP_META_TTL = 5 * 60 * 1e3;
|
|
@@ -4144,7 +4197,7 @@ function storeMessage(storeKey, entry) {
|
|
|
4144
4197
|
if (entries.length > MESSAGE_STORE_MAX) {
|
|
4145
4198
|
const trimmed = entries.length - MESSAGE_STORE_MAX;
|
|
4146
4199
|
entries.splice(0, trimmed);
|
|
4147
|
-
console.error(`${
|
|
4200
|
+
console.error(`${TAG13} message store trimmed for ${storeKey}: ${trimmed} oldest messages removed`);
|
|
4148
4201
|
}
|
|
4149
4202
|
}
|
|
4150
4203
|
function deriveSessionKey(input) {
|
|
@@ -4163,7 +4216,7 @@ function deriveSessionKey(input) {
|
|
|
4163
4216
|
}
|
|
4164
4217
|
async function init(opts) {
|
|
4165
4218
|
if (initialized) {
|
|
4166
|
-
console.error(`${
|
|
4219
|
+
console.error(`${TAG13} already initialized`);
|
|
4167
4220
|
return;
|
|
4168
4221
|
}
|
|
4169
4222
|
configDir = opts.configDir;
|
|
@@ -4171,20 +4224,20 @@ async function init(opts) {
|
|
|
4171
4224
|
loadConfig(opts.accountConfig);
|
|
4172
4225
|
const accountIds = listCredentialAccountIds(configDir);
|
|
4173
4226
|
if (accountIds.length === 0) {
|
|
4174
|
-
console.error(`${
|
|
4227
|
+
console.error(`${TAG13} init: no stored WhatsApp credentials found`);
|
|
4175
4228
|
initialized = true;
|
|
4176
4229
|
return;
|
|
4177
4230
|
}
|
|
4178
|
-
console.error(`${
|
|
4231
|
+
console.error(`${TAG13} init: found ${accountIds.length} credentialed account(s): ${accountIds.join(", ")}`);
|
|
4179
4232
|
initialized = true;
|
|
4180
4233
|
for (const accountId of accountIds) {
|
|
4181
4234
|
const accountCfg = whatsAppConfig.accounts?.[accountId];
|
|
4182
4235
|
if (accountCfg?.enabled === false) {
|
|
4183
|
-
console.error(`${
|
|
4236
|
+
console.error(`${TAG13} skipping disabled account=${accountId}`);
|
|
4184
4237
|
continue;
|
|
4185
4238
|
}
|
|
4186
4239
|
startConnection(accountId).catch((err) => {
|
|
4187
|
-
console.error(`${
|
|
4240
|
+
console.error(`${TAG13} failed to auto-start account=${accountId}: ${formatError(err)}`);
|
|
4188
4241
|
});
|
|
4189
4242
|
}
|
|
4190
4243
|
}
|
|
@@ -4193,7 +4246,7 @@ async function startConnection(accountId) {
|
|
|
4193
4246
|
const authDir = resolveAuthDir(configDir, accountId);
|
|
4194
4247
|
const hasAuth = await authExists(authDir);
|
|
4195
4248
|
if (!hasAuth) {
|
|
4196
|
-
console.error(`${
|
|
4249
|
+
console.error(`${TAG13} no credentials for account=${accountId}`);
|
|
4197
4250
|
return;
|
|
4198
4251
|
}
|
|
4199
4252
|
await stopConnection(accountId);
|
|
@@ -4229,11 +4282,11 @@ async function stopConnection(accountId) {
|
|
|
4229
4282
|
conn.sock.ev.removeAllListeners("creds.update");
|
|
4230
4283
|
conn.sock.ws?.close?.();
|
|
4231
4284
|
} catch (err) {
|
|
4232
|
-
console.warn(`${
|
|
4285
|
+
console.warn(`${TAG13} socket cleanup error during stop account=${accountId}: ${String(err)}`);
|
|
4233
4286
|
}
|
|
4234
4287
|
}
|
|
4235
4288
|
connections.delete(accountId);
|
|
4236
|
-
console.error(`${
|
|
4289
|
+
console.error(`${TAG13} stopped account=${accountId}`);
|
|
4237
4290
|
}
|
|
4238
4291
|
function getStatus() {
|
|
4239
4292
|
return Array.from(connections.values()).map((conn) => ({
|
|
@@ -4274,9 +4327,9 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4274
4327
|
connections.set(accountId, conn);
|
|
4275
4328
|
try {
|
|
4276
4329
|
await sock.sendPresenceUpdate("available");
|
|
4277
|
-
console.error(`${
|
|
4330
|
+
console.error(`${TAG13} presence set to available account=${accountId}`);
|
|
4278
4331
|
} catch (err) {
|
|
4279
|
-
console.error(`${
|
|
4332
|
+
console.error(`${TAG13} presence update failed account=${accountId}: ${String(err)}`);
|
|
4280
4333
|
}
|
|
4281
4334
|
await runInitQueries(sock, {
|
|
4282
4335
|
accountId,
|
|
@@ -4287,7 +4340,7 @@ async function registerLoginSocket(accountId, sock, authDir) {
|
|
|
4287
4340
|
});
|
|
4288
4341
|
monitorInbound(conn);
|
|
4289
4342
|
watchForDisconnect(conn);
|
|
4290
|
-
console.error(`${
|
|
4343
|
+
console.error(`${TAG13} registered login socket for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4291
4344
|
}
|
|
4292
4345
|
function reloadConfig(accountConfig) {
|
|
4293
4346
|
loadConfig(accountConfig);
|
|
@@ -4311,7 +4364,7 @@ async function shutdown() {
|
|
|
4311
4364
|
const ids = Array.from(connections.keys());
|
|
4312
4365
|
await Promise.all(ids.map((id) => stopConnection(id)));
|
|
4313
4366
|
initialized = false;
|
|
4314
|
-
console.error(`${
|
|
4367
|
+
console.error(`${TAG13} shutdown complete`);
|
|
4315
4368
|
}
|
|
4316
4369
|
function loadConfig(accountConfig) {
|
|
4317
4370
|
try {
|
|
@@ -4323,12 +4376,12 @@ function loadConfig(accountConfig) {
|
|
|
4323
4376
|
if (parsed.success) {
|
|
4324
4377
|
whatsAppConfig = parsed.data;
|
|
4325
4378
|
} else {
|
|
4326
|
-
console.error(`${
|
|
4379
|
+
console.error(`${TAG13} config validation failed: ${parsed.error.message}`);
|
|
4327
4380
|
whatsAppConfig = {};
|
|
4328
4381
|
}
|
|
4329
4382
|
}
|
|
4330
4383
|
} catch (err) {
|
|
4331
|
-
console.error(`${
|
|
4384
|
+
console.error(`${TAG13} config load error: ${String(err)}`);
|
|
4332
4385
|
whatsAppConfig = {};
|
|
4333
4386
|
}
|
|
4334
4387
|
}
|
|
@@ -4339,13 +4392,13 @@ async function connectWithReconnect(conn) {
|
|
|
4339
4392
|
let cycleError = null;
|
|
4340
4393
|
let connectedAt;
|
|
4341
4394
|
try {
|
|
4342
|
-
console.error(`${
|
|
4395
|
+
console.error(`${TAG13} connecting account=${conn.accountId} attempt=${conn.reconnectAttempts}`);
|
|
4343
4396
|
const sock = await createWaSocket({
|
|
4344
4397
|
authDir: conn.authDir,
|
|
4345
4398
|
silent: true
|
|
4346
4399
|
});
|
|
4347
4400
|
conn.sock = sock;
|
|
4348
|
-
console.error(`${
|
|
4401
|
+
console.error(`${TAG13} socket created account=${conn.accountId} \u2014 waiting for connection`);
|
|
4349
4402
|
await waitForConnection(sock);
|
|
4350
4403
|
const selfId = readSelfId(conn.authDir);
|
|
4351
4404
|
connectedAt = Date.now();
|
|
@@ -4355,12 +4408,12 @@ async function connectWithReconnect(conn) {
|
|
|
4355
4408
|
conn.lastConnectedAt = connectedAt;
|
|
4356
4409
|
conn.lastError = void 0;
|
|
4357
4410
|
conn.lidMapping = sock.signalRepository?.lidMapping ?? null;
|
|
4358
|
-
console.error(`${
|
|
4411
|
+
console.error(`${TAG13} connected account=${conn.accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
4359
4412
|
try {
|
|
4360
4413
|
await sock.sendPresenceUpdate("available");
|
|
4361
|
-
console.error(`${
|
|
4414
|
+
console.error(`${TAG13} presence set to available account=${conn.accountId}`);
|
|
4362
4415
|
} catch (err) {
|
|
4363
|
-
console.error(`${
|
|
4416
|
+
console.error(`${TAG13} presence update failed account=${conn.accountId}: ${String(err)}`);
|
|
4364
4417
|
}
|
|
4365
4418
|
await runInitQueries(sock, {
|
|
4366
4419
|
accountId: conn.accountId,
|
|
@@ -4387,9 +4440,9 @@ async function connectWithReconnect(conn) {
|
|
|
4387
4440
|
}
|
|
4388
4441
|
const classification = classifyDisconnect(err);
|
|
4389
4442
|
conn.lastError = classification.message;
|
|
4390
|
-
console.error(`${
|
|
4443
|
+
console.error(`${TAG13} disconnect account=${conn.accountId}: ${classification.kind} \u2014 ${classification.message}`);
|
|
4391
4444
|
if (!classification.shouldRetry) {
|
|
4392
|
-
console.error(`${
|
|
4445
|
+
console.error(`${TAG13} terminal disconnect for account=${conn.accountId}, stopping reconnection`);
|
|
4393
4446
|
return;
|
|
4394
4447
|
}
|
|
4395
4448
|
}
|
|
@@ -4402,7 +4455,7 @@ async function connectWithReconnect(conn) {
|
|
|
4402
4455
|
if (decision.action === "abort") {
|
|
4403
4456
|
const stuckReason = `GIVING UP account=${conn.accountId} attempts=${decision.finalAttempts}/${maxAttempts} uptimeMsLast=${uptimeMs} stableThresholdMs=${STABLE_UPTIME_MS} lastError=${conn.lastError ?? "(none)"}`;
|
|
4404
4457
|
console.error(
|
|
4405
|
-
`${
|
|
4458
|
+
`${TAG13} ${stuckReason} \u2014 re-pair via QR or restart required; WhatsApp will not reconnect automatically`
|
|
4406
4459
|
);
|
|
4407
4460
|
conn.sessionStuckReason = stuckReason;
|
|
4408
4461
|
conn.lastError = stuckReason;
|
|
@@ -4411,17 +4464,17 @@ async function connectWithReconnect(conn) {
|
|
|
4411
4464
|
conn.reconnectAttempts = decision.nextAttempts;
|
|
4412
4465
|
if (decision.reason === "short-lived") {
|
|
4413
4466
|
console.error(
|
|
4414
|
-
`${
|
|
4467
|
+
`${TAG13} short-lived session account=${conn.accountId} uptimeMs=${uptimeMs} attempt=${decision.nextAttempts}/${maxAttempts} lastError=${conn.lastError ?? "(clean disconnect)"}`
|
|
4415
4468
|
);
|
|
4416
4469
|
} else {
|
|
4417
4470
|
console.error(
|
|
4418
|
-
`${
|
|
4471
|
+
`${TAG13} session stable account=${conn.accountId} uptimeMs=${uptimeMs} \u2014 reconnect counter reset`
|
|
4419
4472
|
);
|
|
4420
4473
|
}
|
|
4421
4474
|
if (decision.reason === "short-lived" || cycleError) {
|
|
4422
4475
|
const delay = computeBackoff(Math.max(1, decision.nextAttempts));
|
|
4423
4476
|
console.error(
|
|
4424
|
-
`${
|
|
4477
|
+
`${TAG13} reconnecting account=${conn.accountId} in ${delay}ms (attempt ${decision.nextAttempts}/${maxAttempts})`
|
|
4425
4478
|
);
|
|
4426
4479
|
await new Promise((resolve25) => {
|
|
4427
4480
|
const timer2 = setTimeout(resolve25, delay);
|
|
@@ -4455,11 +4508,11 @@ function watchForDisconnect(conn) {
|
|
|
4455
4508
|
conn.sock.ev.on("connection.update", (update) => {
|
|
4456
4509
|
if (update.connection === "close") {
|
|
4457
4510
|
if (connections.get(conn.accountId) !== conn) return;
|
|
4458
|
-
console.error(`${
|
|
4511
|
+
console.error(`${TAG13} socket disconnected for account=${conn.accountId}`);
|
|
4459
4512
|
conn.connected = false;
|
|
4460
4513
|
conn.sock = null;
|
|
4461
4514
|
connectWithReconnect(conn).catch((err) => {
|
|
4462
|
-
console.error(`${
|
|
4515
|
+
console.error(`${TAG13} reconnection failed for account=${conn.accountId}: ${formatError(err)}`);
|
|
4463
4516
|
});
|
|
4464
4517
|
}
|
|
4465
4518
|
});
|
|
@@ -4468,7 +4521,7 @@ async function getGroupMeta(conn, jid) {
|
|
|
4468
4521
|
const cached = conn.groupMetaCache.get(jid);
|
|
4469
4522
|
if (cached && cached.expires > Date.now()) return cached;
|
|
4470
4523
|
if (!conn.sock) return null;
|
|
4471
|
-
console.error(`${
|
|
4524
|
+
console.error(`${TAG13} group metadata cache miss for ${jid}, fetching from Baileys account=${conn.accountId}`);
|
|
4472
4525
|
try {
|
|
4473
4526
|
const meta = await conn.sock.groupMetadata(jid);
|
|
4474
4527
|
const participants = await Promise.all(
|
|
@@ -4483,12 +4536,12 @@ async function getGroupMeta(conn, jid) {
|
|
|
4483
4536
|
};
|
|
4484
4537
|
conn.groupMetaCache.set(jid, entry);
|
|
4485
4538
|
console.error(
|
|
4486
|
-
`${
|
|
4539
|
+
`${TAG13} group metadata cached for ${jid}: "${meta.subject}", ${participants.length} participants, expires in ${GROUP_META_TTL}ms account=${conn.accountId}`
|
|
4487
4540
|
);
|
|
4488
4541
|
return entry;
|
|
4489
4542
|
} catch (err) {
|
|
4490
4543
|
console.error(
|
|
4491
|
-
`${
|
|
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}`
|
|
4492
4545
|
);
|
|
4493
4546
|
const emptyEntry = { expires: Date.now() + GROUP_META_TTL };
|
|
4494
4547
|
conn.groupMetaCache.set(jid, emptyEntry);
|
|
@@ -4499,7 +4552,7 @@ function monitorInbound(conn) {
|
|
|
4499
4552
|
if (!conn.sock || !onInboundMessage) return;
|
|
4500
4553
|
const sock = conn.sock;
|
|
4501
4554
|
const debounceMs = whatsAppConfig.accounts?.[conn.accountId]?.debounceMs ?? whatsAppConfig.debounceMs ?? 0;
|
|
4502
|
-
console.error(`${
|
|
4555
|
+
console.error(`${TAG13} monitorInbound started account=${conn.accountId} debounceMs=${debounceMs}`);
|
|
4503
4556
|
conn.debouncer = createInboundDebouncer({
|
|
4504
4557
|
debounceMs,
|
|
4505
4558
|
buildKey: (payload) => {
|
|
@@ -4512,7 +4565,7 @@ function monitorInbound(conn) {
|
|
|
4512
4565
|
onInboundMessage(entries[0]);
|
|
4513
4566
|
return;
|
|
4514
4567
|
}
|
|
4515
|
-
console.error(`${
|
|
4568
|
+
console.error(`${TAG13} debounce: combining ${entries.length} messages account=${conn.accountId} from=${entries[0].senderPhone}`);
|
|
4516
4569
|
const last = entries[entries.length - 1];
|
|
4517
4570
|
const mediaEntry = entries.find((e) => e.mediaPath);
|
|
4518
4571
|
const combinedText = entries.map((e) => e.text).filter(Boolean).join("\n");
|
|
@@ -4525,7 +4578,7 @@ function monitorInbound(conn) {
|
|
|
4525
4578
|
});
|
|
4526
4579
|
},
|
|
4527
4580
|
onError: (err) => {
|
|
4528
|
-
console.error(`${
|
|
4581
|
+
console.error(`${TAG13} debounce flush error account=${conn.accountId}: ${String(err)}`);
|
|
4529
4582
|
}
|
|
4530
4583
|
});
|
|
4531
4584
|
sock.ev.on("messages.upsert", async (upsert) => {
|
|
@@ -4553,7 +4606,7 @@ function monitorInbound(conn) {
|
|
|
4553
4606
|
});
|
|
4554
4607
|
const entries = messageStore.get(storeKey);
|
|
4555
4608
|
console.error(
|
|
4556
|
-
`${
|
|
4609
|
+
`${TAG13} stored message ${msg.key.id ?? "?"} for ${remoteJid} (type: ${upsert.type}, store size: ${entries?.length ?? 0}/${MESSAGE_STORE_MAX}) account=${conn.accountId}`
|
|
4557
4610
|
);
|
|
4558
4611
|
recordActivity({
|
|
4559
4612
|
accountId: conn.accountId,
|
|
@@ -4576,23 +4629,31 @@ function monitorInbound(conn) {
|
|
|
4576
4629
|
isOwnerMirror: fromMe && !isGroup
|
|
4577
4630
|
});
|
|
4578
4631
|
if (msg.key.id) {
|
|
4579
|
-
await
|
|
4632
|
+
const merged = await ensureWhatsAppConversation({
|
|
4580
4633
|
accountId: conn.accountId,
|
|
4581
|
-
remoteJid,
|
|
4582
4634
|
sessionKey,
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
senderPhone,
|
|
4586
|
-
selfPhone: conn.selfPhone,
|
|
4587
|
-
body: extracted.text,
|
|
4588
|
-
timestamp: ts,
|
|
4589
|
-
pushName: msg.pushName ?? void 0,
|
|
4590
|
-
quoted: extracted.quotedMessage ? {
|
|
4591
|
-
id: extracted.quotedMessage.id,
|
|
4592
|
-
body: extracted.quotedMessage.text,
|
|
4593
|
-
sender: extracted.quotedMessage.sender
|
|
4594
|
-
} : void 0
|
|
4635
|
+
agentType: sessionKeyAgentType,
|
|
4636
|
+
groupJid: isGroup ? remoteJid : void 0
|
|
4595
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
|
+
}
|
|
4596
4657
|
}
|
|
4597
4658
|
}
|
|
4598
4659
|
if (upsert.type === "append") {
|
|
@@ -4606,13 +4667,13 @@ function monitorInbound(conn) {
|
|
|
4606
4667
|
);
|
|
4607
4668
|
}
|
|
4608
4669
|
console.error(
|
|
4609
|
-
`${
|
|
4670
|
+
`${TAG13} append-type message ${msg.key.id ?? "?"} stored, dispatch skipped account=${conn.accountId}`
|
|
4610
4671
|
);
|
|
4611
4672
|
continue;
|
|
4612
4673
|
}
|
|
4613
4674
|
await handleInboundMessage(conn, msg);
|
|
4614
4675
|
} catch (err) {
|
|
4615
|
-
console.error(`${
|
|
4676
|
+
console.error(`${TAG13} inbound handler error account=${conn.accountId}: ${String(err)}`);
|
|
4616
4677
|
}
|
|
4617
4678
|
}
|
|
4618
4679
|
});
|
|
@@ -4622,31 +4683,31 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4622
4683
|
const remoteJid = msg.key.remoteJid;
|
|
4623
4684
|
if (!remoteJid) return;
|
|
4624
4685
|
if (remoteJid === "status@broadcast") {
|
|
4625
|
-
console.error(`${
|
|
4686
|
+
console.error(`${TAG13} drop: status broadcast account=${conn.accountId}`);
|
|
4626
4687
|
return;
|
|
4627
4688
|
}
|
|
4628
4689
|
if (!msg.message) {
|
|
4629
|
-
console.error(`${
|
|
4690
|
+
console.error(`${TAG13} drop: empty message account=${conn.accountId} from=${remoteJid}`);
|
|
4630
4691
|
return;
|
|
4631
4692
|
}
|
|
4632
4693
|
const dedupKey = `${conn.accountId}:${remoteJid}:${msg.key.id}`;
|
|
4633
4694
|
if (isDuplicateInbound(dedupKey)) {
|
|
4634
|
-
console.error(`${
|
|
4695
|
+
console.error(`${TAG13} drop: duplicate account=${conn.accountId} key=${dedupKey}`);
|
|
4635
4696
|
return;
|
|
4636
4697
|
}
|
|
4637
4698
|
if (msg.key.fromMe) {
|
|
4638
4699
|
if (msg.key.id && isAgentSentMessage(msg.key.id)) {
|
|
4639
|
-
console.error(`${
|
|
4700
|
+
console.error(`${TAG13} drop: echo suppression account=${conn.accountId} msgId=${msg.key.id}`);
|
|
4640
4701
|
return;
|
|
4641
4702
|
}
|
|
4642
4703
|
const extracted2 = extractMessage(msg);
|
|
4643
4704
|
if (!extracted2.text) {
|
|
4644
|
-
console.error(`${
|
|
4705
|
+
console.error(`${TAG13} owner reply skipped \u2014 no text content account=${conn.accountId}`);
|
|
4645
4706
|
return;
|
|
4646
4707
|
}
|
|
4647
4708
|
const isGroup2 = isGroupJid(remoteJid);
|
|
4648
4709
|
const senderPhone2 = conn.selfPhone ?? "owner";
|
|
4649
|
-
console.error(`${
|
|
4710
|
+
console.error(`${TAG13} owner reply mirrored to session from=${senderPhone2} account=${conn.accountId}`);
|
|
4650
4711
|
const reply2 = async (text) => {
|
|
4651
4712
|
const currentSock = conn.sock;
|
|
4652
4713
|
if (!currentSock) throw new Error("WhatsApp disconnected \u2014 cannot reply");
|
|
@@ -4674,7 +4735,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4674
4735
|
}
|
|
4675
4736
|
const extracted = extractMessage(msg);
|
|
4676
4737
|
if (!extracted.text && !extracted.mediaType) {
|
|
4677
|
-
console.error(`${
|
|
4738
|
+
console.error(`${TAG13} drop: no text or media account=${conn.accountId} from=${remoteJid}`);
|
|
4678
4739
|
return;
|
|
4679
4740
|
}
|
|
4680
4741
|
let mediaResult;
|
|
@@ -4684,7 +4745,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4684
4745
|
maxBytes: maxMb * 1024 * 1024
|
|
4685
4746
|
});
|
|
4686
4747
|
if (!mediaResult) {
|
|
4687
|
-
console.error(`${
|
|
4748
|
+
console.error(`${TAG13} media download returned undefined account=${conn.accountId} type=${extracted.mediaType} from=${remoteJid}`);
|
|
4688
4749
|
}
|
|
4689
4750
|
}
|
|
4690
4751
|
const isGroup = isGroupJid(remoteJid);
|
|
@@ -4729,7 +4790,7 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4729
4790
|
});
|
|
4730
4791
|
}
|
|
4731
4792
|
console.error(
|
|
4732
|
-
`${
|
|
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}` : "")
|
|
4733
4794
|
);
|
|
4734
4795
|
if (!accessResult.allowed) return;
|
|
4735
4796
|
let groupSubject;
|
|
@@ -4772,15 +4833,15 @@ async function handleInboundMessage(conn, msg) {
|
|
|
4772
4833
|
if (accessResult.agentType === "public") {
|
|
4773
4834
|
const hoursResult = await isBusinessOpen(conn.accountId);
|
|
4774
4835
|
if (!hoursResult.open) {
|
|
4775
|
-
console.error(`${
|
|
4836
|
+
console.error(`${TAG13} [${conn.accountId}] dispatch skipped: business closed`);
|
|
4776
4837
|
const afterHoursMessage = whatsAppConfig.accounts?.[conn.accountId]?.afterHoursMessage ?? whatsAppConfig.afterHoursMessage;
|
|
4777
4838
|
if (afterHoursMessage) {
|
|
4778
4839
|
try {
|
|
4779
4840
|
await reply(afterHoursMessage);
|
|
4780
|
-
console.error(`${
|
|
4841
|
+
console.error(`${TAG13} [${conn.accountId}] after-hours auto-reply sent to ${remoteJid}`);
|
|
4781
4842
|
} catch (err) {
|
|
4782
4843
|
console.error(
|
|
4783
|
-
`${
|
|
4844
|
+
`${TAG13} [${conn.accountId}] after-hours auto-reply failed: ${err instanceof Error ? err.message : String(err)}`
|
|
4784
4845
|
);
|
|
4785
4846
|
}
|
|
4786
4847
|
}
|
|
@@ -5366,7 +5427,7 @@ async function storeGeneratedFile(accountId, filePath) {
|
|
|
5366
5427
|
import { writeFile as writeFile3, mkdtemp, rm } from "fs/promises";
|
|
5367
5428
|
import { tmpdir } from "os";
|
|
5368
5429
|
import { join as join5 } from "path";
|
|
5369
|
-
var
|
|
5430
|
+
var TAG14 = "[voice]";
|
|
5370
5431
|
var AUDIO_MIME_TYPES = /* @__PURE__ */ new Set([
|
|
5371
5432
|
"audio/ogg",
|
|
5372
5433
|
"audio/webm",
|
|
@@ -5398,7 +5459,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5398
5459
|
const bytes = file.size;
|
|
5399
5460
|
const mimeType = file.type;
|
|
5400
5461
|
console.error(
|
|
5401
|
-
`${
|
|
5462
|
+
`${TAG14} recording send source=${source} duration_ms=unknown bytes=${bytes} format=${mimeType}`
|
|
5402
5463
|
);
|
|
5403
5464
|
let tempDir;
|
|
5404
5465
|
let tempPath;
|
|
@@ -5410,7 +5471,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5410
5471
|
await writeFile3(tempPath, buffer);
|
|
5411
5472
|
} catch (err) {
|
|
5412
5473
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5413
|
-
console.error(`${
|
|
5474
|
+
console.error(`${TAG14} failed source=${source} error=temp-file-write: ${reason}`);
|
|
5414
5475
|
return { ok: false, error: "Could not process voice note" };
|
|
5415
5476
|
}
|
|
5416
5477
|
try {
|
|
@@ -5418,7 +5479,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5418
5479
|
if (!sttResult) {
|
|
5419
5480
|
const elapsed2 = Date.now() - startMs;
|
|
5420
5481
|
console.error(
|
|
5421
|
-
`${
|
|
5482
|
+
`${TAG14} failed source=${source} error=transcription-failed duration_ms=${elapsed2}`
|
|
5422
5483
|
);
|
|
5423
5484
|
return { ok: false, error: "Could not transcribe voice note. Please try again or type your message." };
|
|
5424
5485
|
}
|
|
@@ -5426,7 +5487,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5426
5487
|
const elapsed = Date.now() - startMs;
|
|
5427
5488
|
const words = rawText.split(/\s+/).filter(Boolean).length;
|
|
5428
5489
|
console.error(
|
|
5429
|
-
`${
|
|
5490
|
+
`${TAG14} transcribed source=${source} duration_ms=${elapsed} stt_ms=${sttResult.durationMs} words=${words}`
|
|
5430
5491
|
);
|
|
5431
5492
|
return {
|
|
5432
5493
|
ok: true,
|
|
@@ -5436,7 +5497,7 @@ async function transcribeVoiceNote(file, source) {
|
|
|
5436
5497
|
const elapsed = Date.now() - startMs;
|
|
5437
5498
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5438
5499
|
console.error(
|
|
5439
|
-
`${
|
|
5500
|
+
`${TAG14} failed source=${source} error=${reason} duration_ms=${elapsed}`
|
|
5440
5501
|
);
|
|
5441
5502
|
return { ok: false, error: "Voice note transcription failed. Please try again or type your message." };
|
|
5442
5503
|
} finally {
|
|
@@ -5471,7 +5532,7 @@ var VERDICT_DEFINITIONS = {
|
|
|
5471
5532
|
};
|
|
5472
5533
|
|
|
5473
5534
|
// app/lib/inbound-gateway.ts
|
|
5474
|
-
var
|
|
5535
|
+
var TAG15 = "[inbound-gateway]";
|
|
5475
5536
|
var GATEWAY_TIMEOUT_MS = 1e4;
|
|
5476
5537
|
var MIN_WORDS_FOR_PROCESSING = 5;
|
|
5477
5538
|
function defaultResult(rawText, latencyMs) {
|
|
@@ -5594,11 +5655,11 @@ async function processInbound(rawText, channel) {
|
|
|
5594
5655
|
};
|
|
5595
5656
|
result.fallthrough = false;
|
|
5596
5657
|
console.warn(
|
|
5597
|
-
`${
|
|
5658
|
+
`${TAG15} short-message-injection channel=${channel} words=${words.length} latency_ms=${result.latencyMs}`
|
|
5598
5659
|
);
|
|
5599
5660
|
} else {
|
|
5600
5661
|
console.log(
|
|
5601
|
-
`${
|
|
5662
|
+
`${TAG15} passthrough channel=${channel} reason=short-message words=${words.length} latency_ms=${result.latencyMs}`
|
|
5602
5663
|
);
|
|
5603
5664
|
}
|
|
5604
5665
|
return result;
|
|
@@ -5615,13 +5676,13 @@ async function processInbound(rawText, channel) {
|
|
|
5615
5676
|
const latencyMs = Date.now() - startMs;
|
|
5616
5677
|
if (llmResult.kind === "fallback") {
|
|
5617
5678
|
console.warn(
|
|
5618
|
-
`${
|
|
5679
|
+
`${TAG15} fallthrough channel=${channel} reason=${llmResult.cause} detail=${llmResult.reason} latency_ms=${latencyMs}`
|
|
5619
5680
|
);
|
|
5620
5681
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5621
5682
|
}
|
|
5622
5683
|
if (llmResult.kind !== "ok-tool") {
|
|
5623
5684
|
console.warn(
|
|
5624
|
-
`${
|
|
5685
|
+
`${TAG15} fallthrough channel=${channel} reason=no-tool-response latency_ms=${latencyMs}`
|
|
5625
5686
|
);
|
|
5626
5687
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5627
5688
|
}
|
|
@@ -5631,7 +5692,7 @@ async function processInbound(rawText, channel) {
|
|
|
5631
5692
|
const verdict = input.verdict;
|
|
5632
5693
|
if (!processedText || !verdict || !["clean", "suspicious", "discard"].includes(verdict)) {
|
|
5633
5694
|
console.warn(
|
|
5634
|
-
`${
|
|
5695
|
+
`${TAG15} fallthrough channel=${channel} reason=mandatory-fields-missing latency_ms=${latencyMs} hasProcessedText=${!!processedText} verdict=${String(verdict)}`
|
|
5635
5696
|
);
|
|
5636
5697
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5637
5698
|
}
|
|
@@ -5659,18 +5720,18 @@ async function processInbound(rawText, channel) {
|
|
|
5659
5720
|
fallthrough: false
|
|
5660
5721
|
};
|
|
5661
5722
|
console.log(
|
|
5662
|
-
`${
|
|
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}`
|
|
5663
5724
|
);
|
|
5664
5725
|
if (verdict !== "clean") {
|
|
5665
5726
|
console.warn(
|
|
5666
|
-
`${
|
|
5727
|
+
`${TAG15} ${verdict.toUpperCase()} channel=${channel} reason=${reason} promptInjection=${promptInjectionRisk}`
|
|
5667
5728
|
);
|
|
5668
5729
|
}
|
|
5669
5730
|
return result;
|
|
5670
5731
|
} catch (err) {
|
|
5671
5732
|
const reason = err instanceof Error ? err.message : String(err);
|
|
5672
5733
|
console.warn(
|
|
5673
|
-
`${
|
|
5734
|
+
`${TAG15} fallthrough channel=${channel} reason=parse-error: ${reason} latency_ms=${latencyMs}`
|
|
5674
5735
|
);
|
|
5675
5736
|
return defaultResult(rawText.trim(), latencyMs);
|
|
5676
5737
|
}
|
|
@@ -6763,7 +6824,7 @@ function checkTelegramAccess(params) {
|
|
|
6763
6824
|
}
|
|
6764
6825
|
|
|
6765
6826
|
// server/routes/telegram.ts
|
|
6766
|
-
var
|
|
6827
|
+
var TAG16 = "[telegram-webhook]";
|
|
6767
6828
|
var TELEGRAM_API = "https://api.telegram.org";
|
|
6768
6829
|
function getWebhookSecret(botType) {
|
|
6769
6830
|
const filePath = botType === "admin" ? TELEGRAM_ADMIN_WEBHOOK_SECRET_FILE : TELEGRAM_WEBHOOK_SECRET_FILE;
|
|
@@ -6787,12 +6848,12 @@ async function handleInbound(params) {
|
|
|
6787
6848
|
const gatewayChannel = agentType === "admin" ? "telegram-admin" : "telegram-dm";
|
|
6788
6849
|
registerSession(sessionKey, agentType, accountId);
|
|
6789
6850
|
console.error(
|
|
6790
|
-
`${
|
|
6851
|
+
`${TAG16} session registered: sessionKey=${sessionKey} agentType=${agentType} botType=${botType} senderId=${senderId} accountId=${accountId.slice(0, 8)}\u2026`
|
|
6791
6852
|
);
|
|
6792
6853
|
const gatewayResult = await processInbound(text, gatewayChannel);
|
|
6793
6854
|
if (gatewayResult.screening.verdict === "discard") {
|
|
6794
6855
|
console.error(
|
|
6795
|
-
`${
|
|
6856
|
+
`${TAG16} discarded: senderId=${senderId} chatId=${chatId} reason=${gatewayResult.screening.reason}`
|
|
6796
6857
|
);
|
|
6797
6858
|
return;
|
|
6798
6859
|
}
|
|
@@ -6815,7 +6876,7 @@ async function handleInbound(params) {
|
|
|
6815
6876
|
}
|
|
6816
6877
|
} catch (err) {
|
|
6817
6878
|
console.error(
|
|
6818
|
-
`${
|
|
6879
|
+
`${TAG16} agent-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6819
6880
|
);
|
|
6820
6881
|
responseText = "I'm having trouble right now. Please try again in a moment.";
|
|
6821
6882
|
}
|
|
@@ -6833,12 +6894,12 @@ async function handleInbound(params) {
|
|
|
6833
6894
|
const data = await res.json();
|
|
6834
6895
|
if (!data.ok) {
|
|
6835
6896
|
console.error(
|
|
6836
|
-
`${
|
|
6897
|
+
`${TAG16} send-error: chatId=${chatId} error=${data.description ?? "unknown"}`
|
|
6837
6898
|
);
|
|
6838
6899
|
}
|
|
6839
6900
|
} catch (err) {
|
|
6840
6901
|
console.error(
|
|
6841
|
-
`${
|
|
6902
|
+
`${TAG16} send-error: chatId=${chatId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6842
6903
|
);
|
|
6843
6904
|
}
|
|
6844
6905
|
}
|
|
@@ -6846,28 +6907,28 @@ var app6 = new Hono();
|
|
|
6846
6907
|
app6.post("/webhook", async (c) => {
|
|
6847
6908
|
const botType = c.req.query("bot");
|
|
6848
6909
|
if (!botType || botType !== "public" && botType !== "admin") {
|
|
6849
|
-
console.error(`${
|
|
6910
|
+
console.error(`${TAG16} invalid-bot-type: received=${botType ?? "missing"}`);
|
|
6850
6911
|
return c.json({ error: "Missing or invalid bot type" }, 400);
|
|
6851
6912
|
}
|
|
6852
6913
|
const storedSecret = getWebhookSecret(botType);
|
|
6853
6914
|
if (!storedSecret) {
|
|
6854
|
-
console.error(`${
|
|
6915
|
+
console.error(`${TAG16} secret=missing botType=${botType}`);
|
|
6855
6916
|
return c.json({ error: "Webhook not configured" }, 401);
|
|
6856
6917
|
}
|
|
6857
6918
|
const headerSecret = c.req.header("x-telegram-bot-api-secret-token");
|
|
6858
6919
|
if (!headerSecret || !verifyWebhookSecret(headerSecret, storedSecret)) {
|
|
6859
|
-
console.error(`${
|
|
6920
|
+
console.error(`${TAG16} secret=invalid botType=${botType}`);
|
|
6860
6921
|
return c.json({ error: "Unauthorized" }, 401);
|
|
6861
6922
|
}
|
|
6862
6923
|
const account = resolveAccount();
|
|
6863
6924
|
if (!account) {
|
|
6864
|
-
console.error(`${
|
|
6925
|
+
console.error(`${TAG16} config=no-account`);
|
|
6865
6926
|
return c.json({ error: "No account configured" }, 500);
|
|
6866
6927
|
}
|
|
6867
6928
|
const tgConfig = account.config.telegram ?? {};
|
|
6868
6929
|
const botToken = botType === "admin" ? tgConfig.adminBotToken : tgConfig.publicBotToken;
|
|
6869
6930
|
if (!botToken) {
|
|
6870
|
-
console.error(`${
|
|
6931
|
+
console.error(`${TAG16} config=no-token botType=${botType}`);
|
|
6871
6932
|
return c.json({ error: `No ${botType} bot token configured` }, 500);
|
|
6872
6933
|
}
|
|
6873
6934
|
let update;
|
|
@@ -6895,7 +6956,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6895
6956
|
}
|
|
6896
6957
|
const accessResult = checkTelegramAccess({ senderId, botType, config: tgConfig });
|
|
6897
6958
|
console.error(
|
|
6898
|
-
`${
|
|
6959
|
+
`${TAG16} access: botType=${botType} senderId=${senderId} chatId=${chatId} allowed=${accessResult.allowed} reason=${accessResult.reason} agentType=${accessResult.agentType}`
|
|
6899
6960
|
);
|
|
6900
6961
|
if (!accessResult.allowed) {
|
|
6901
6962
|
return c.json({ ok: true });
|
|
@@ -6906,7 +6967,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6906
6967
|
headers: { "Content-Type": "application/json" },
|
|
6907
6968
|
body: JSON.stringify({ callback_query_id: callbackId })
|
|
6908
6969
|
}).catch((err) => {
|
|
6909
|
-
console.error(`${
|
|
6970
|
+
console.error(`${TAG16} callback-ack-error: ${err instanceof Error ? err.message : String(err)}`);
|
|
6910
6971
|
});
|
|
6911
6972
|
}
|
|
6912
6973
|
handleInbound({
|
|
@@ -6919,7 +6980,7 @@ app6.post("/webhook", async (c) => {
|
|
|
6919
6980
|
agentType: accessResult.agentType
|
|
6920
6981
|
}).catch((err) => {
|
|
6921
6982
|
console.error(
|
|
6922
|
-
`${
|
|
6983
|
+
`${TAG16} unhandled-error: chatId=${chatId} senderId=${senderId} error=${err instanceof Error ? err.message : String(err)}`
|
|
6923
6984
|
);
|
|
6924
6985
|
});
|
|
6925
6986
|
return c.json({ ok: true });
|
|
@@ -6933,14 +6994,14 @@ import { realpathSync as realpathSync2, readdirSync as readdirSync2, readFileSyn
|
|
|
6933
6994
|
|
|
6934
6995
|
// app/lib/whatsapp/login.ts
|
|
6935
6996
|
import { randomUUID as randomUUID6 } from "crypto";
|
|
6936
|
-
var
|
|
6997
|
+
var TAG17 = "[whatsapp:login]";
|
|
6937
6998
|
var ACTIVE_LOGIN_TTL_MS = 3 * 6e4;
|
|
6938
6999
|
var activeLogins = /* @__PURE__ */ new Map();
|
|
6939
7000
|
function closeSocket(sock) {
|
|
6940
7001
|
try {
|
|
6941
7002
|
sock.ws?.close?.();
|
|
6942
7003
|
} catch (err) {
|
|
6943
|
-
console.warn(`${
|
|
7004
|
+
console.warn(`${TAG17} socket close error during cleanup: ${String(err)}`);
|
|
6944
7005
|
}
|
|
6945
7006
|
}
|
|
6946
7007
|
function resetActiveLogin(accountId) {
|
|
@@ -6963,7 +7024,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6963
7024
|
const current = activeLogins.get(accountId);
|
|
6964
7025
|
if (current?.id === login.id) {
|
|
6965
7026
|
current.connected = true;
|
|
6966
|
-
console.error(`${
|
|
7027
|
+
console.error(`${TAG17} loginConnectionLoop: connected account=${accountId} attempt=${attempt}`);
|
|
6967
7028
|
}
|
|
6968
7029
|
return;
|
|
6969
7030
|
} catch (err) {
|
|
@@ -6973,7 +7034,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6973
7034
|
if (!classification.shouldRetry || attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6974
7035
|
if (attempt >= LOGIN_MAX_RECONNECTS) {
|
|
6975
7036
|
console.error(
|
|
6976
|
-
`${
|
|
7037
|
+
`${TAG17} login reconnect attempts exhausted (${attempt}/${LOGIN_MAX_RECONNECTS}) \u2014 surfacing error to agent`
|
|
6977
7038
|
);
|
|
6978
7039
|
current.error = `Login failed after ${attempt} reconnect attempts: ${formatError(err)}`;
|
|
6979
7040
|
} else {
|
|
@@ -6985,7 +7046,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6985
7046
|
attempt++;
|
|
6986
7047
|
const delay = LOGIN_RECONNECT_DELAYS[attempt - 1] ?? 8e3;
|
|
6987
7048
|
console.error(
|
|
6988
|
-
`${
|
|
7049
|
+
`${TAG17} status=${classification.statusCode ?? "unknown"} restart required \u2014 reconnecting with saved creds (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}) delay=${delay}ms`
|
|
6989
7050
|
);
|
|
6990
7051
|
closeSocket(current.sock);
|
|
6991
7052
|
await new Promise((r) => setTimeout(r, delay));
|
|
@@ -6996,7 +7057,7 @@ async function loginConnectionLoop(accountId, login) {
|
|
|
6996
7057
|
current.sock = newSock;
|
|
6997
7058
|
} catch (sockErr) {
|
|
6998
7059
|
console.error(
|
|
6999
|
-
`${
|
|
7060
|
+
`${TAG17} reconnect socket creation failed (attempt ${attempt}/${LOGIN_MAX_RECONNECTS}): ${String(sockErr)}`
|
|
7000
7061
|
);
|
|
7001
7062
|
current.error = `Reconnection failed: ${String(sockErr)}`;
|
|
7002
7063
|
return;
|
|
@@ -7010,7 +7071,7 @@ async function startLogin(opts) {
|
|
|
7010
7071
|
const hasAuth = await authExists(authDir);
|
|
7011
7072
|
const selfId = readSelfId(authDir);
|
|
7012
7073
|
console.error(
|
|
7013
|
-
`${
|
|
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")
|
|
7014
7075
|
);
|
|
7015
7076
|
if (hasAuth && !force) {
|
|
7016
7077
|
const who = selfId.e164 ?? selfId.jid ?? "unknown";
|
|
@@ -7022,7 +7083,7 @@ async function startLogin(opts) {
|
|
|
7022
7083
|
await clearAuth(authDir);
|
|
7023
7084
|
const existing = activeLogins.get(accountId);
|
|
7024
7085
|
if (existing && isLoginFresh(existing) && existing.qrDataUrl && !force) {
|
|
7025
|
-
console.error(`${
|
|
7086
|
+
console.error(`${TAG17} startLogin account=${accountId} guard: returning existing QR (age=${Math.round((Date.now() - existing.startedAt) / 1e3)}s)`);
|
|
7026
7087
|
return {
|
|
7027
7088
|
qrDataUrl: existing.qrDataUrl,
|
|
7028
7089
|
qrRaw: existing.qr,
|
|
@@ -7030,7 +7091,7 @@ async function startLogin(opts) {
|
|
|
7030
7091
|
};
|
|
7031
7092
|
}
|
|
7032
7093
|
if (existing) {
|
|
7033
|
-
console.error(`${
|
|
7094
|
+
console.error(`${TAG17} startLogin account=${accountId} ${force ? "force override" : "stale/no-QR"}, resetting active login`);
|
|
7034
7095
|
}
|
|
7035
7096
|
resetActiveLogin(accountId);
|
|
7036
7097
|
let resolveQr = null;
|
|
@@ -7052,14 +7113,14 @@ async function startLogin(opts) {
|
|
|
7052
7113
|
onQr: (qr2) => {
|
|
7053
7114
|
loginQrCount++;
|
|
7054
7115
|
if (pendingQr) {
|
|
7055
|
-
console.error(`${
|
|
7116
|
+
console.error(`${TAG17} QR rotation #${loginQrCount} received for account=${accountId} \u2014 not forwarded (initial QR already captured)`);
|
|
7056
7117
|
return;
|
|
7057
7118
|
}
|
|
7058
7119
|
pendingQr = qr2;
|
|
7059
7120
|
const current = activeLogins.get(accountId);
|
|
7060
7121
|
if (current && !current.qr) current.qr = qr2;
|
|
7061
7122
|
clearTimeout(qrTimer);
|
|
7062
|
-
console.error(`${
|
|
7123
|
+
console.error(`${TAG17} QR #${loginQrCount} received for account=${accountId} \u2014 forwarding to caller`);
|
|
7063
7124
|
resolveQr?.(qr2);
|
|
7064
7125
|
}
|
|
7065
7126
|
});
|
|
@@ -7079,7 +7140,7 @@ async function startLogin(opts) {
|
|
|
7079
7140
|
activeLogins.set(accountId, login);
|
|
7080
7141
|
if (pendingQr && !login.qr) login.qr = pendingQr;
|
|
7081
7142
|
loginConnectionLoop(accountId, login).catch((err) => {
|
|
7082
|
-
console.error(`${
|
|
7143
|
+
console.error(`${TAG17} loginConnectionLoop unexpected error: ${String(err)}`);
|
|
7083
7144
|
const current = activeLogins.get(accountId);
|
|
7084
7145
|
if (current?.id === login.id) {
|
|
7085
7146
|
current.error = `Unexpected login error: ${String(err)}`;
|
|
@@ -7104,7 +7165,7 @@ async function waitForLogin(opts) {
|
|
|
7104
7165
|
const { accountId, timeoutMs = 6e4 } = opts;
|
|
7105
7166
|
const login = activeLogins.get(accountId);
|
|
7106
7167
|
console.error(
|
|
7107
|
-
`${
|
|
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")
|
|
7108
7169
|
);
|
|
7109
7170
|
if (!login) {
|
|
7110
7171
|
return { connected: false, message: "No active WhatsApp login in progress." };
|
|
@@ -7117,7 +7178,7 @@ async function waitForLogin(opts) {
|
|
|
7117
7178
|
while (Date.now() < deadline) {
|
|
7118
7179
|
if (login.connected) {
|
|
7119
7180
|
const selfId = readSelfId(login.authDir);
|
|
7120
|
-
console.error(`${
|
|
7181
|
+
console.error(`${TAG17} login complete for account=${accountId} phone=${selfId.e164 ?? "unknown"}`);
|
|
7121
7182
|
const sock = login.sock;
|
|
7122
7183
|
const authDir = login.authDir;
|
|
7123
7184
|
activeLogins.delete(accountId);
|
|
@@ -7137,7 +7198,7 @@ async function waitForLogin(opts) {
|
|
|
7137
7198
|
await new Promise((r) => setTimeout(r, 1e3));
|
|
7138
7199
|
}
|
|
7139
7200
|
const elapsed = Math.round((Date.now() - (deadline - timeoutMs)) / 1e3);
|
|
7140
|
-
console.error(`${
|
|
7201
|
+
console.error(`${TAG17} waitForLogin timeout account=${accountId} elapsed=${elapsed}s \u2014 cleaning up active login`);
|
|
7141
7202
|
resetActiveLogin(accountId);
|
|
7142
7203
|
return { connected: false, message: "Login timed out. Try generating a new QR." };
|
|
7143
7204
|
}
|
|
@@ -7251,17 +7312,17 @@ function serializeWhatsAppSchema() {
|
|
|
7251
7312
|
}
|
|
7252
7313
|
|
|
7253
7314
|
// server/routes/whatsapp.ts
|
|
7254
|
-
var
|
|
7315
|
+
var TAG18 = "[whatsapp:api]";
|
|
7255
7316
|
var PLATFORM_ROOT4 = process.env.MAXY_PLATFORM_ROOT || "";
|
|
7256
7317
|
var app7 = new Hono();
|
|
7257
7318
|
app7.get("/status", (c) => {
|
|
7258
7319
|
try {
|
|
7259
7320
|
const status = getStatus();
|
|
7260
7321
|
const summary = status.map((a) => `${a.accountId}:${a.connected ? "up" : "down"}`).join(", ");
|
|
7261
|
-
console.error(`${
|
|
7322
|
+
console.error(`${TAG18} status accounts=${status.length} [${summary}]`);
|
|
7262
7323
|
return c.json({ accounts: status });
|
|
7263
7324
|
} catch (err) {
|
|
7264
|
-
console.error(`${
|
|
7325
|
+
console.error(`${TAG18} status error: ${String(err)}`);
|
|
7265
7326
|
return c.json({ error: String(err) }, 500);
|
|
7266
7327
|
}
|
|
7267
7328
|
});
|
|
@@ -7272,10 +7333,10 @@ app7.post("/login/start", async (c) => {
|
|
|
7272
7333
|
const force = body.force ?? false;
|
|
7273
7334
|
const authDir = join6(MAXY_DIR, "credentials", "whatsapp", accountId);
|
|
7274
7335
|
const result = await startLogin({ accountId, authDir, force });
|
|
7275
|
-
console.error(`${
|
|
7336
|
+
console.error(`${TAG18} login/start result account=${accountId} hasQr=${!!result.qrRaw}${result.selfPhone ? ` phone=${result.selfPhone}` : ""}`);
|
|
7276
7337
|
return c.json(result);
|
|
7277
7338
|
} catch (err) {
|
|
7278
|
-
console.error(`${
|
|
7339
|
+
console.error(`${TAG18} login/start error: ${String(err)}`);
|
|
7279
7340
|
return c.json({ error: String(err) }, 500);
|
|
7280
7341
|
}
|
|
7281
7342
|
});
|
|
@@ -7290,7 +7351,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7290
7351
|
try {
|
|
7291
7352
|
await registerLoginSocket(accountId, result.sock, result.authDir);
|
|
7292
7353
|
} catch (regErr) {
|
|
7293
|
-
console.error(`${
|
|
7354
|
+
console.error(`${TAG18} registerLoginSocket failed account=${accountId}: ${String(regErr)}`);
|
|
7294
7355
|
}
|
|
7295
7356
|
try {
|
|
7296
7357
|
const account = resolveAccount();
|
|
@@ -7298,16 +7359,16 @@ app7.post("/login/wait", async (c) => {
|
|
|
7298
7359
|
const persistResult = persistAfterPairing(account.accountDir, accountId, result.selfPhone ?? null);
|
|
7299
7360
|
configPersisted = persistResult.ok;
|
|
7300
7361
|
if (!persistResult.ok) {
|
|
7301
|
-
console.error(`${
|
|
7362
|
+
console.error(`${TAG18} config persist failed account=${accountId}: ${persistResult.error}`);
|
|
7302
7363
|
}
|
|
7303
7364
|
} else {
|
|
7304
|
-
console.error(`${
|
|
7365
|
+
console.error(`${TAG18} config persist skipped \u2014 no account resolved`);
|
|
7305
7366
|
}
|
|
7306
7367
|
} catch (persistErr) {
|
|
7307
|
-
console.error(`${
|
|
7368
|
+
console.error(`${TAG18} config persist error account=${accountId}: ${String(persistErr)}`);
|
|
7308
7369
|
}
|
|
7309
7370
|
}
|
|
7310
|
-
console.error(`${
|
|
7371
|
+
console.error(`${TAG18} login/wait result account=${accountId} connected=${result.connected}${result.selfPhone ? ` phone=${result.selfPhone}` : ""} configPersisted=${configPersisted}`);
|
|
7311
7372
|
return c.json({
|
|
7312
7373
|
connected: result.connected,
|
|
7313
7374
|
message: result.message,
|
|
@@ -7315,7 +7376,7 @@ app7.post("/login/wait", async (c) => {
|
|
|
7315
7376
|
configPersisted
|
|
7316
7377
|
});
|
|
7317
7378
|
} catch (err) {
|
|
7318
|
-
console.error(`${
|
|
7379
|
+
console.error(`${TAG18} login/wait error: ${String(err)}`);
|
|
7319
7380
|
return c.json({ error: String(err) }, 500);
|
|
7320
7381
|
}
|
|
7321
7382
|
});
|
|
@@ -7326,7 +7387,7 @@ app7.post("/disconnect", async (c) => {
|
|
|
7326
7387
|
await stopConnection(accountId);
|
|
7327
7388
|
return c.json({ disconnected: true, accountId });
|
|
7328
7389
|
} catch (err) {
|
|
7329
|
-
console.error(`${
|
|
7390
|
+
console.error(`${TAG18} disconnect error: ${String(err)}`);
|
|
7330
7391
|
return c.json({ error: String(err) }, 500);
|
|
7331
7392
|
}
|
|
7332
7393
|
});
|
|
@@ -7337,7 +7398,7 @@ app7.post("/reconnect", async (c) => {
|
|
|
7337
7398
|
await startConnection(accountId);
|
|
7338
7399
|
return c.json({ reconnecting: true, accountId });
|
|
7339
7400
|
} catch (err) {
|
|
7340
|
-
console.error(`${
|
|
7401
|
+
console.error(`${TAG18} reconnect error: ${String(err)}`);
|
|
7341
7402
|
return c.json({ error: String(err) }, 500);
|
|
7342
7403
|
}
|
|
7343
7404
|
});
|
|
@@ -7356,7 +7417,7 @@ app7.post("/send", async (c) => {
|
|
|
7356
7417
|
const result = await sendTextMessage(sock, to, text, { accountId });
|
|
7357
7418
|
return c.json(result);
|
|
7358
7419
|
} catch (err) {
|
|
7359
|
-
console.error(`${
|
|
7420
|
+
console.error(`${TAG18} send error: ${String(err)}`);
|
|
7360
7421
|
return c.json({ error: String(err) }, 500);
|
|
7361
7422
|
}
|
|
7362
7423
|
});
|
|
@@ -7377,7 +7438,7 @@ app7.post("/config", async (c) => {
|
|
|
7377
7438
|
return c.json({ ok: false, error: 'Missing required field "phone" (E.164 format, e.g. +441234567890).' }, 400);
|
|
7378
7439
|
}
|
|
7379
7440
|
const result = addAdminPhone(account.accountDir, phone);
|
|
7380
|
-
console.error(`${
|
|
7441
|
+
console.error(`${TAG18} config action=add-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7381
7442
|
return c.json(result, result.ok ? 200 : 400);
|
|
7382
7443
|
}
|
|
7383
7444
|
case "remove-admin-phone": {
|
|
@@ -7385,12 +7446,12 @@ app7.post("/config", async (c) => {
|
|
|
7385
7446
|
return c.json({ ok: false, error: 'Missing required field "phone".' }, 400);
|
|
7386
7447
|
}
|
|
7387
7448
|
const result = removeAdminPhone(account.accountDir, phone);
|
|
7388
|
-
console.error(`${
|
|
7449
|
+
console.error(`${TAG18} config action=remove-admin-phone phone=${phone} ok=${result.ok}`);
|
|
7389
7450
|
return c.json(result, result.ok ? 200 : 400);
|
|
7390
7451
|
}
|
|
7391
7452
|
case "list-admin-phones": {
|
|
7392
7453
|
const phones = readAdminPhones(account.accountDir);
|
|
7393
|
-
console.error(`${
|
|
7454
|
+
console.error(`${TAG18} config action=list-admin-phones count=${phones.length}`);
|
|
7394
7455
|
return c.json({ ok: true, phones });
|
|
7395
7456
|
}
|
|
7396
7457
|
case "set-public-agent": {
|
|
@@ -7398,14 +7459,14 @@ app7.post("/config", async (c) => {
|
|
|
7398
7459
|
return c.json({ ok: false, error: 'Missing required field "slug" (the agent directory name, e.g. "my-agent").' }, 400);
|
|
7399
7460
|
}
|
|
7400
7461
|
const result = setPublicAgent(account.accountDir, slug);
|
|
7401
|
-
console.error(`${
|
|
7462
|
+
console.error(`${TAG18} config action=set-public-agent slug=${slug} ok=${result.ok}`);
|
|
7402
7463
|
return c.json(result, result.ok ? 200 : 400);
|
|
7403
7464
|
}
|
|
7404
7465
|
case "get-public-agent": {
|
|
7405
7466
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7406
7467
|
const targetGroup = typeof groupJid === "string" && groupJid.trim() ? groupJid.trim() : void 0;
|
|
7407
7468
|
const resolved = resolvePublicAgent(account.accountDir, { accountId: targetAccount, groupJid: targetGroup });
|
|
7408
|
-
console.error(`${
|
|
7469
|
+
console.error(`${TAG18} config action=get-public-agent accountId=${targetAccount} groupJid=${targetGroup ?? "none"} slug=${resolved?.slug ?? "none"} source=${resolved?.source ?? "none"}`);
|
|
7409
7470
|
return c.json({ ok: true, slug: resolved?.slug ?? null, source: resolved?.source ?? null });
|
|
7410
7471
|
}
|
|
7411
7472
|
case "set-group-public-agent": {
|
|
@@ -7417,7 +7478,7 @@ app7.post("/config", async (c) => {
|
|
|
7417
7478
|
}
|
|
7418
7479
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7419
7480
|
const result = setGroupPublicAgent(account.accountDir, targetAccount, groupJid, slug);
|
|
7420
|
-
console.error(`${
|
|
7481
|
+
console.error(`${TAG18} config action=set-group-public-agent accountId=${targetAccount} groupJid=${groupJid} slug=${slug} ok=${result.ok}`);
|
|
7421
7482
|
return c.json(result, result.ok ? 200 : 400);
|
|
7422
7483
|
}
|
|
7423
7484
|
case "unset-group-public-agent": {
|
|
@@ -7426,7 +7487,7 @@ app7.post("/config", async (c) => {
|
|
|
7426
7487
|
}
|
|
7427
7488
|
const targetAccount = typeof accountId === "string" && accountId.trim() ? accountId.trim() : "default";
|
|
7428
7489
|
const result = unsetGroupPublicAgent(account.accountDir, targetAccount, groupJid);
|
|
7429
|
-
console.error(`${
|
|
7490
|
+
console.error(`${TAG18} config action=unset-group-public-agent accountId=${targetAccount} groupJid=${groupJid} ok=${result.ok}`);
|
|
7430
7491
|
return c.json(result, result.ok ? 200 : 400);
|
|
7431
7492
|
}
|
|
7432
7493
|
case "list-public-agents": {
|
|
@@ -7443,26 +7504,26 @@ app7.post("/config", async (c) => {
|
|
|
7443
7504
|
const config = JSON.parse(readFileSync9(configPath2, "utf-8"));
|
|
7444
7505
|
agents.push({ slug: entry.name, displayName: config.displayName ?? entry.name });
|
|
7445
7506
|
} catch {
|
|
7446
|
-
console.error(`${
|
|
7507
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to parse config.json for agent ${entry.name}" \u2014 skipping`);
|
|
7447
7508
|
}
|
|
7448
7509
|
}
|
|
7449
7510
|
} catch (err) {
|
|
7450
|
-
console.error(`${
|
|
7511
|
+
console.error(`${TAG18} config action=list-public-agents error="failed to scan agents directory: ${String(err)}"`);
|
|
7451
7512
|
}
|
|
7452
7513
|
}
|
|
7453
|
-
console.error(`${
|
|
7514
|
+
console.error(`${TAG18} config action=list-public-agents count=${agents.length}`);
|
|
7454
7515
|
return c.json({ ok: true, agents });
|
|
7455
7516
|
}
|
|
7456
7517
|
case "schema": {
|
|
7457
7518
|
const text = serializeWhatsAppSchema();
|
|
7458
|
-
console.error(`${
|
|
7519
|
+
console.error(`${TAG18} config action=schema`);
|
|
7459
7520
|
return c.json({ ok: true, text });
|
|
7460
7521
|
}
|
|
7461
7522
|
case "list-groups": {
|
|
7462
7523
|
const groupAccountId = accountId ?? "default";
|
|
7463
7524
|
const sock = getSocket(groupAccountId);
|
|
7464
7525
|
if (!sock) {
|
|
7465
|
-
console.error(`${
|
|
7526
|
+
console.error(`${TAG18} config action=list-groups error="not connected" accountId=${groupAccountId}`);
|
|
7466
7527
|
return c.json({ ok: false, error: `WhatsApp account "${groupAccountId}" is not connected. Connect first, then retry.` });
|
|
7467
7528
|
}
|
|
7468
7529
|
try {
|
|
@@ -7472,10 +7533,10 @@ app7.post("/config", async (c) => {
|
|
|
7472
7533
|
name: g.subject ?? g.id,
|
|
7473
7534
|
participantCount: Array.isArray(g.participants) ? g.participants.length : 0
|
|
7474
7535
|
}));
|
|
7475
|
-
console.error(`${
|
|
7536
|
+
console.error(`${TAG18} config action=list-groups count=${groups.length} accountId=${groupAccountId}`);
|
|
7476
7537
|
return c.json({ ok: true, groups });
|
|
7477
7538
|
} catch (err) {
|
|
7478
|
-
console.error(`${
|
|
7539
|
+
console.error(`${TAG18} config action=list-groups error="${String(err)}" accountId=${groupAccountId}`);
|
|
7479
7540
|
return c.json({ ok: false, error: `Failed to fetch groups: ${String(err)}` });
|
|
7480
7541
|
}
|
|
7481
7542
|
}
|
|
@@ -7485,12 +7546,12 @@ app7.post("/config", async (c) => {
|
|
|
7485
7546
|
}
|
|
7486
7547
|
const result = updateConfig(account.accountDir, fields);
|
|
7487
7548
|
const fieldNames = Object.keys(fields);
|
|
7488
|
-
console.error(`${
|
|
7549
|
+
console.error(`${TAG18} config action=update-config fields=[${fieldNames.join(",")}] ok=${result.ok}`);
|
|
7489
7550
|
return c.json(result, result.ok ? 200 : 400);
|
|
7490
7551
|
}
|
|
7491
7552
|
case "get-config": {
|
|
7492
7553
|
const waConfig = getConfig(account.accountDir);
|
|
7493
|
-
console.error(`${
|
|
7554
|
+
console.error(`${TAG18} config action=get-config`);
|
|
7494
7555
|
return c.json({ ok: true, config: waConfig });
|
|
7495
7556
|
}
|
|
7496
7557
|
default:
|
|
@@ -7500,7 +7561,7 @@ app7.post("/config", async (c) => {
|
|
|
7500
7561
|
);
|
|
7501
7562
|
}
|
|
7502
7563
|
} catch (err) {
|
|
7503
|
-
console.error(`${
|
|
7564
|
+
console.error(`${TAG18} config error: ${String(err)}`);
|
|
7504
7565
|
return c.json({ ok: false, error: String(err) }, 500);
|
|
7505
7566
|
}
|
|
7506
7567
|
});
|
|
@@ -7522,16 +7583,16 @@ app7.post("/send-document", async (c) => {
|
|
|
7522
7583
|
const accountResolved = realpathSync2(accountDir);
|
|
7523
7584
|
if (!resolvedPath.startsWith(accountResolved + "/")) {
|
|
7524
7585
|
const sanitised = filePath.replace(accountDir, "<account>/");
|
|
7525
|
-
console.error(`${
|
|
7586
|
+
console.error(`${TAG18} send-document REJECTED path=${sanitised} reason=outside_account_directory`);
|
|
7526
7587
|
return c.json({ error: "Access denied: file is outside the account directory" }, 403);
|
|
7527
7588
|
}
|
|
7528
7589
|
} catch (err) {
|
|
7529
7590
|
const code = err.code;
|
|
7530
7591
|
if (code === "ENOENT") {
|
|
7531
|
-
console.error(`${
|
|
7592
|
+
console.error(`${TAG18} send-document ENOENT path=${filePath}`);
|
|
7532
7593
|
return c.json({ error: `File not found: ${filePath}` }, 404);
|
|
7533
7594
|
}
|
|
7534
|
-
console.error(`${
|
|
7595
|
+
console.error(`${TAG18} send-document path error: ${String(err)}`);
|
|
7535
7596
|
return c.json({ error: String(err) }, 500);
|
|
7536
7597
|
}
|
|
7537
7598
|
const fileStat = await stat3(resolvedPath);
|
|
@@ -7553,11 +7614,11 @@ app7.post("/send-document", async (c) => {
|
|
|
7553
7614
|
caption
|
|
7554
7615
|
}, { accountId });
|
|
7555
7616
|
console.error(
|
|
7556
|
-
`${
|
|
7617
|
+
`${TAG18} send-document to=${to} size=${fileStat.size} mime=${mimetype} ok=${result.success}` + (result.messageId ? ` id=${result.messageId}` : "")
|
|
7557
7618
|
);
|
|
7558
7619
|
return c.json(result);
|
|
7559
7620
|
} catch (err) {
|
|
7560
|
-
console.error(`${
|
|
7621
|
+
console.error(`${TAG18} send-document error: ${String(err)}`);
|
|
7561
7622
|
return c.json({ error: String(err) }, 500);
|
|
7562
7623
|
}
|
|
7563
7624
|
});
|
|
@@ -7567,11 +7628,11 @@ app7.get("/activity", (c) => {
|
|
|
7567
7628
|
const result = getChannelActivity(accountId);
|
|
7568
7629
|
const total = result.accounts.reduce((sum, a) => sum + a.total, 0);
|
|
7569
7630
|
console.error(
|
|
7570
|
-
`${
|
|
7631
|
+
`${TAG18} activity accounts=${result.accounts.length} total=${total} recentEvents=${result.recentEvents.length}` + (accountId ? ` filter=${accountId}` : "")
|
|
7571
7632
|
);
|
|
7572
7633
|
return c.json(result);
|
|
7573
7634
|
} catch (err) {
|
|
7574
|
-
console.error(`${
|
|
7635
|
+
console.error(`${TAG18} activity error: ${String(err)}`);
|
|
7575
7636
|
return c.json({ error: String(err) }, 500);
|
|
7576
7637
|
}
|
|
7577
7638
|
});
|
|
@@ -7590,10 +7651,10 @@ app7.get("/conversations", (c) => {
|
|
|
7590
7651
|
};
|
|
7591
7652
|
});
|
|
7592
7653
|
conversations.sort((a, b) => (b.lastMessageTimestamp ?? 0) - (a.lastMessageTimestamp ?? 0));
|
|
7593
|
-
console.error(`${
|
|
7654
|
+
console.error(`${TAG18} conversations account=${accountId} count=${conversations.length}`);
|
|
7594
7655
|
return c.json({ conversations });
|
|
7595
7656
|
} catch (err) {
|
|
7596
|
-
console.error(`${
|
|
7657
|
+
console.error(`${TAG18} conversations error: ${String(err)}`);
|
|
7597
7658
|
return c.json({ error: String(err) }, 500);
|
|
7598
7659
|
}
|
|
7599
7660
|
});
|
|
@@ -7608,10 +7669,10 @@ app7.get("/messages", (c) => {
|
|
|
7608
7669
|
const limit = limitParam ? parseInt(limitParam, 10) : void 0;
|
|
7609
7670
|
const effectiveLimit = limit && !Number.isNaN(limit) && limit > 0 ? limit : void 0;
|
|
7610
7671
|
const messages = getMessages(accountId, jid, effectiveLimit);
|
|
7611
|
-
console.error(`${
|
|
7672
|
+
console.error(`${TAG18} messages account=${accountId} jid=${jid} limit=${effectiveLimit ?? "all"} returned=${messages.length}`);
|
|
7612
7673
|
return c.json({ messages });
|
|
7613
7674
|
} catch (err) {
|
|
7614
|
-
console.error(`${
|
|
7675
|
+
console.error(`${TAG18} messages error: ${String(err)}`);
|
|
7615
7676
|
return c.json({ error: String(err) }, 500);
|
|
7616
7677
|
}
|
|
7617
7678
|
});
|
|
@@ -7623,12 +7684,12 @@ app7.get("/group-info", async (c) => {
|
|
|
7623
7684
|
return c.json({ error: "Missing required parameter: jid" }, 400);
|
|
7624
7685
|
}
|
|
7625
7686
|
if (!isGroupJid(jid)) {
|
|
7626
|
-
console.error(`${
|
|
7687
|
+
console.error(`${TAG18} group-info error="not a group JID" jid=${jid} account=${accountId}`);
|
|
7627
7688
|
return c.json({ error: `"${jid}" is not a group JID. Group JIDs end with @g.us.` }, 400);
|
|
7628
7689
|
}
|
|
7629
7690
|
const sock = getSocket(accountId);
|
|
7630
7691
|
if (!sock) {
|
|
7631
|
-
console.error(`${
|
|
7692
|
+
console.error(`${TAG18} group-info error="not connected" account=${accountId}`);
|
|
7632
7693
|
return c.json({ error: `WhatsApp account "${accountId}" is not connected. Connect first, then retry.` }, 400);
|
|
7633
7694
|
}
|
|
7634
7695
|
const meta = await sock.groupMetadata(jid);
|
|
@@ -7641,10 +7702,10 @@ app7.get("/group-info", async (c) => {
|
|
|
7641
7702
|
participantCount: meta.participants.length,
|
|
7642
7703
|
participants: meta.participants.map((p) => ({ jid: p.id, admin: p.admin ?? null }))
|
|
7643
7704
|
};
|
|
7644
|
-
console.error(`${
|
|
7705
|
+
console.error(`${TAG18} group-info jid=${jid} subject="${meta.subject}" participants=${meta.participants.length} account=${accountId}`);
|
|
7645
7706
|
return c.json(result);
|
|
7646
7707
|
} catch (err) {
|
|
7647
|
-
console.error(`${
|
|
7708
|
+
console.error(`${TAG18} group-info error="${String(err)}" jid=${jid} account=${accountId}`);
|
|
7648
7709
|
return c.json({ error: `Failed to fetch group info: ${String(err)}` }, 500);
|
|
7649
7710
|
}
|
|
7650
7711
|
});
|
|
@@ -8525,7 +8586,7 @@ var app11 = new Hono();
|
|
|
8525
8586
|
app11.post("/cancel", requireAdminSession, async (c) => {
|
|
8526
8587
|
const session_key = c.var.sessionKey;
|
|
8527
8588
|
try {
|
|
8528
|
-
const { interruptClient: interruptClient2 } = await import("./client-pool-
|
|
8589
|
+
const { interruptClient: interruptClient2 } = await import("./client-pool-LXE7RIRT.js");
|
|
8529
8590
|
await interruptClient2(session_key);
|
|
8530
8591
|
return c.json({ ok: true });
|
|
8531
8592
|
} catch (err) {
|
|
@@ -9405,16 +9466,26 @@ app17.get("/", requireAdminSession, async (c) => {
|
|
|
9405
9466
|
sessionKey: null,
|
|
9406
9467
|
name: r.name,
|
|
9407
9468
|
updatedAt: r.updatedAt,
|
|
9408
|
-
phase: "flushed"
|
|
9469
|
+
phase: "flushed",
|
|
9470
|
+
channel: r.channel
|
|
9409
9471
|
}));
|
|
9410
9472
|
const inProgressRows = inProgressRaw.map((r) => ({
|
|
9411
9473
|
conversationId: null,
|
|
9412
9474
|
sessionKey: r.sessionKey,
|
|
9413
9475
|
name: null,
|
|
9414
9476
|
updatedAt: new Date(r.createdAt).toISOString(),
|
|
9415
|
-
phase: "pre-flush"
|
|
9477
|
+
phase: "pre-flush",
|
|
9478
|
+
channel: "webchat"
|
|
9416
9479
|
}));
|
|
9417
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
|
+
);
|
|
9418
9489
|
return c.json({ sessions });
|
|
9419
9490
|
} catch (err) {
|
|
9420
9491
|
console.error(`[sessions-list] Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -9678,13 +9749,13 @@ async function cdpNavigateNewTab(url, opts = {}) {
|
|
|
9678
9749
|
// server/routes/admin/device-browser.ts
|
|
9679
9750
|
var app19 = new Hono();
|
|
9680
9751
|
app19.post("/navigate", async (c) => {
|
|
9681
|
-
const
|
|
9752
|
+
const TAG19 = "[device-url:click]";
|
|
9682
9753
|
let body;
|
|
9683
9754
|
try {
|
|
9684
9755
|
body = await c.req.json();
|
|
9685
9756
|
} catch (err) {
|
|
9686
9757
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9687
|
-
console.error(`${
|
|
9758
|
+
console.error(`${TAG19} reject reason=body-not-json detail=${detail} browser=fallback navigateResult=error`);
|
|
9688
9759
|
return c.json(
|
|
9689
9760
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "Request body was not valid JSON" },
|
|
9690
9761
|
400
|
|
@@ -9694,7 +9765,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9694
9765
|
const intent = typeof body.intent === "string" ? body.intent : "";
|
|
9695
9766
|
const hostname2 = typeof body.hostname === "string" ? body.hostname : "";
|
|
9696
9767
|
if (!url) {
|
|
9697
|
-
console.error(`${
|
|
9768
|
+
console.error(`${TAG19} reject reason=missing-url intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`);
|
|
9698
9769
|
return c.json(
|
|
9699
9770
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url field is required" },
|
|
9700
9771
|
400
|
|
@@ -9704,7 +9775,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9704
9775
|
try {
|
|
9705
9776
|
parsed = new URL(url);
|
|
9706
9777
|
} catch {
|
|
9707
|
-
console.error(`${
|
|
9778
|
+
console.error(`${TAG19} reject reason=url-malformed intent=${JSON.stringify(intent)} url=${url} browser=fallback navigateResult=error`);
|
|
9708
9779
|
return c.json(
|
|
9709
9780
|
{ ok: false, navigateResult: "error", browser: "fallback", detail: "url is not a valid URL" },
|
|
9710
9781
|
400
|
|
@@ -9712,7 +9783,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9712
9783
|
}
|
|
9713
9784
|
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
9714
9785
|
console.error(
|
|
9715
|
-
`${
|
|
9786
|
+
`${TAG19} reject reason=scheme-not-allowed scheme=${parsed.protocol} intent=${JSON.stringify(intent)} browser=fallback navigateResult=error`
|
|
9716
9787
|
);
|
|
9717
9788
|
return c.json(
|
|
9718
9789
|
{
|
|
@@ -9728,7 +9799,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9728
9799
|
const cdpOk = await ensureCdp(transport);
|
|
9729
9800
|
if (!cdpOk) {
|
|
9730
9801
|
console.error(
|
|
9731
|
-
`${
|
|
9802
|
+
`${TAG19} intent=${JSON.stringify(intent)} browser=fallback navigateResult=cdp-unreachable hostname=${JSON.stringify(hostname2)}`
|
|
9732
9803
|
);
|
|
9733
9804
|
return c.json(
|
|
9734
9805
|
{
|
|
@@ -9744,7 +9815,7 @@ app19.post("/navigate", async (c) => {
|
|
|
9744
9815
|
const browser = outcome.result === "ok" ? "vnc" : "fallback";
|
|
9745
9816
|
const detailStr = outcome.detail ? ` detail=${JSON.stringify(outcome.detail.length > 230 ? outcome.detail.slice(0, 227) + "..." : outcome.detail)}` : "";
|
|
9746
9817
|
console.error(
|
|
9747
|
-
`${
|
|
9818
|
+
`${TAG19} intent=${JSON.stringify(intent)} browser=${browser} navigateResult=${outcome.result} hostname=${JSON.stringify(hostname2)} targetId=${outcome.targetId ?? "none"}${detailStr}`
|
|
9748
9819
|
);
|
|
9749
9820
|
if (outcome.result !== "ok") {
|
|
9750
9821
|
return c.json(
|
|
@@ -9775,18 +9846,18 @@ var ALLOWED_EVENTS = /* @__PURE__ */ new Set([
|
|
|
9775
9846
|
]);
|
|
9776
9847
|
var app20 = new Hono();
|
|
9777
9848
|
app20.post("/", async (c) => {
|
|
9778
|
-
const
|
|
9849
|
+
const TAG19 = "[admin:events]";
|
|
9779
9850
|
let body;
|
|
9780
9851
|
try {
|
|
9781
9852
|
body = await c.req.json();
|
|
9782
9853
|
} catch (err) {
|
|
9783
9854
|
const detail = err instanceof Error ? err.message : String(err);
|
|
9784
|
-
console.error(`${
|
|
9855
|
+
console.error(`${TAG19} reject reason=body-not-json detail=${detail}`);
|
|
9785
9856
|
return c.json({ ok: false, detail: "Request body was not valid JSON" }, 400);
|
|
9786
9857
|
}
|
|
9787
9858
|
const event = typeof body.event === "string" ? body.event : "";
|
|
9788
9859
|
if (!ALLOWED_EVENTS.has(event)) {
|
|
9789
|
-
console.error(`${
|
|
9860
|
+
console.error(`${TAG19} reject reason=event-not-allowed event=${JSON.stringify(event)}`);
|
|
9790
9861
|
return c.json({ ok: false, detail: `Event "${event}" is not allowed` }, 400);
|
|
9791
9862
|
}
|
|
9792
9863
|
const rawFields = body.fields && typeof body.fields === "object" ? body.fields : {};
|
|
@@ -10067,6 +10138,14 @@ app21.post("/setup", requireAdminSession, async (c) => {
|
|
|
10067
10138
|
const publicFqdn = body.publicLabel ? `${body.publicLabel}.${body.publicDomain}` : void 0;
|
|
10068
10139
|
const apex = body.apex && body.apex.length > 0 ? body.apex : void 0;
|
|
10069
10140
|
log(`phase=submit-received admin=${adminFqdn} public=${publicFqdn ?? "absent"} apex=${apex ?? "absent"}`);
|
|
10141
|
+
const account = resolveAccount();
|
|
10142
|
+
if (!account) {
|
|
10143
|
+
return err("script", "No account on disk for this device \u2014 re-run installer.");
|
|
10144
|
+
}
|
|
10145
|
+
if (account.accountId !== accountId) {
|
|
10146
|
+
return err("script", `Session/device account drift: session=${accountId.slice(0, 8)} device=${account.accountId.slice(0, 8)}`);
|
|
10147
|
+
}
|
|
10148
|
+
const accountDir = account.accountDir;
|
|
10070
10149
|
try {
|
|
10071
10150
|
await setRemotePassword(body.password);
|
|
10072
10151
|
log(`phase=password-set`);
|
|
@@ -10092,7 +10171,7 @@ app21.post("/setup", requireAdminSession, async (c) => {
|
|
|
10092
10171
|
let actionId;
|
|
10093
10172
|
let unit;
|
|
10094
10173
|
try {
|
|
10095
|
-
const launched = await launchAction("cloudflare-setup", { positional: args, streamLogPath });
|
|
10174
|
+
const launched = await launchAction("cloudflare-setup", { positional: args, streamLogPath, accountDir });
|
|
10096
10175
|
actionId = launched.actionId;
|
|
10097
10176
|
unit = launched.unit;
|
|
10098
10177
|
} catch (e) {
|
|
@@ -11128,6 +11207,8 @@ function plainProperties(properties) {
|
|
|
11128
11207
|
// server/routes/admin/graph-search.ts
|
|
11129
11208
|
var DEFAULT_LIMIT = 20;
|
|
11130
11209
|
var MAX_LIMIT = 2e3;
|
|
11210
|
+
var MESSAGE_FAMILY_LABELS = ["Message", "UserMessage", "AssistantMessage", "WhatsAppMessage"];
|
|
11211
|
+
var CONVERSATION_PARENT_LABELS = /* @__PURE__ */ new Set(["AdminConversation", "PublicConversation"]);
|
|
11131
11212
|
var app23 = new Hono();
|
|
11132
11213
|
app23.get("/", requireAdminSession, async (c) => {
|
|
11133
11214
|
const sessionKey = c.var.sessionKey;
|
|
@@ -11146,13 +11227,15 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11146
11227
|
}
|
|
11147
11228
|
const parsedLimit = rawLimit ? parseInt(rawLimit, 10) : DEFAULT_LIMIT;
|
|
11148
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;
|
|
11149
11232
|
const started = Date.now();
|
|
11150
11233
|
const session = getSession();
|
|
11151
11234
|
try {
|
|
11152
11235
|
const res = await hybrid(session, embed, {
|
|
11153
11236
|
query: q,
|
|
11154
11237
|
accountId,
|
|
11155
|
-
labels,
|
|
11238
|
+
labels: expandedLabels,
|
|
11156
11239
|
limit,
|
|
11157
11240
|
degradeOnEmbedFailure: true
|
|
11158
11241
|
});
|
|
@@ -11161,11 +11244,59 @@ app23.get("/", requireAdminSession, async (c) => {
|
|
|
11161
11244
|
console.error(`[graph-search] embed-unavailable err="${res.embedError}" \u2014 bm25-only`);
|
|
11162
11245
|
console.error(`[graph-search] embed-degraded query="${q}" results=${res.results.length}`);
|
|
11163
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
|
+
}
|
|
11164
11295
|
console.error(
|
|
11165
|
-
`[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}`
|
|
11166
11297
|
);
|
|
11167
11298
|
return c.json({
|
|
11168
|
-
results:
|
|
11299
|
+
results: resolvedResults,
|
|
11169
11300
|
mode: res.mode,
|
|
11170
11301
|
embedError: res.embedError,
|
|
11171
11302
|
elapsedMs: total
|
|
@@ -11364,6 +11495,10 @@ async function handleDefault(c, accountId) {
|
|
|
11364
11495
|
const includeTrashed = c.req.query("includeTrashed") === "1";
|
|
11365
11496
|
const includeAgentActions = c.req.query("includeAgentActions") === "1";
|
|
11366
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);
|
|
11367
11502
|
if (labels.length === 0) {
|
|
11368
11503
|
console.error(
|
|
11369
11504
|
`[graph-page] load rejected reason=missing-filter account=${accountId} labels=`
|
|
@@ -11395,7 +11530,7 @@ async function handleDefault(c, accountId) {
|
|
|
11395
11530
|
try {
|
|
11396
11531
|
const countCypher = includeTrashed ? DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED : DEFAULT_COUNT_CYPHER;
|
|
11397
11532
|
const fetchCypher = includeTrashed ? DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED : DEFAULT_FETCH_CYPHER;
|
|
11398
|
-
const cypherParams = { accountId, labels, agentActionLabels };
|
|
11533
|
+
const cypherParams = { accountId, labels, agentActionLabels, channel, messageSublabel };
|
|
11399
11534
|
const result = await session.executeRead(async (tx) => {
|
|
11400
11535
|
const countResult = await tx.run(countCypher, cypherParams);
|
|
11401
11536
|
const matchedRaw = countResult.records[0]?.get("matched");
|
|
@@ -11427,8 +11562,10 @@ async function handleDefault(c, accountId) {
|
|
|
11427
11562
|
const nodes = result.rawNodes.map((n) => pruneNode(n, warnedClasses, conversationWarnings));
|
|
11428
11563
|
const edges = result.rawEdges.filter((e) => e && e.id != null).map((e) => pruneEdge(e, warnedClasses));
|
|
11429
11564
|
const trashedSuffix = includeTrashed ? " includeTrashed=1" : "";
|
|
11565
|
+
const channelSuffix = channel.length > 0 ? ` channel=${channel.join(",")}` : "";
|
|
11566
|
+
const sublabelSuffix = messageSublabel.length > 0 ? ` messageSublabel=${messageSublabel.join(",")}` : "";
|
|
11430
11567
|
console.error(
|
|
11431
|
-
`[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}`
|
|
11432
11569
|
);
|
|
11433
11570
|
return c.json({ nodes, edges });
|
|
11434
11571
|
} catch (err) {
|
|
@@ -11524,13 +11661,22 @@ async function handleNeighbourhood(c, accountId) {
|
|
|
11524
11661
|
}
|
|
11525
11662
|
}
|
|
11526
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
|
+
`;
|
|
11527
11673
|
var DEFAULT_COUNT_CYPHER = `
|
|
11528
11674
|
MATCH (n)
|
|
11529
11675
|
WHERE n.accountId = $accountId
|
|
11530
11676
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11531
11677
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11532
11678
|
AND NOT n:Trashed
|
|
11533
|
-
AND n.deletedAt IS NULL
|
|
11679
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11534
11680
|
RETURN count(n) AS matched
|
|
11535
11681
|
`;
|
|
11536
11682
|
var CONVERSATION_PROPS_PROJECTION = `CASE
|
|
@@ -11544,7 +11690,7 @@ var DEFAULT_FETCH_CYPHER = `
|
|
|
11544
11690
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11545
11691
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11546
11692
|
AND NOT n:Trashed
|
|
11547
|
-
AND n.deletedAt IS NULL
|
|
11693
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11548
11694
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11549
11695
|
UNWIND nodes AS n
|
|
11550
11696
|
OPTIONAL MATCH (n)-[r]-(m)
|
|
@@ -11566,7 +11712,7 @@ var DEFAULT_COUNT_CYPHER_INCLUDE_TRASHED = `
|
|
|
11566
11712
|
WHERE n.accountId = $accountId
|
|
11567
11713
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11568
11714
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11569
|
-
AND n.deletedAt IS NULL
|
|
11715
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11570
11716
|
RETURN count(n) AS matched
|
|
11571
11717
|
`;
|
|
11572
11718
|
var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
@@ -11574,7 +11720,7 @@ var DEFAULT_FETCH_CYPHER_INCLUDE_TRASHED = `
|
|
|
11574
11720
|
WHERE n.accountId = $accountId
|
|
11575
11721
|
AND any(lbl IN labels(n) WHERE lbl IN $labels)
|
|
11576
11722
|
AND NOT any(lbl IN labels(n) WHERE lbl IN $agentActionLabels)
|
|
11577
|
-
AND n.deletedAt IS NULL
|
|
11723
|
+
AND n.deletedAt IS NULL${SUBFACET_PREDICATE}
|
|
11578
11724
|
WITH collect(n) AS nodes, collect(elementId(n)) AS nodeIds
|
|
11579
11725
|
UNWIND nodes AS n
|
|
11580
11726
|
OPTIONAL MATCH (n)-[r]-(m)
|