@mindot/will 0.4.0 → 0.5.0
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/README.md +32 -12
- package/dist/channels/discord.d.ts +2 -11
- package/dist/channels/whatsapp.d.ts +72 -0
- package/dist/channels/whatsapp.js +252 -0
- package/dist/channels/whatsapp.js.map +1 -0
- package/dist/cli.js +270 -40
- package/dist/cli.js.map +1 -1
- package/dist/index.d.ts +2 -2
- package/dist/index.js +53 -28
- package/dist/index.js.map +1 -1
- package/dist/mcp/effectors.d.ts +1 -1
- package/dist/types-E9-HV-SW.d.ts +11 -0
- package/dist/{will-D-slky1N.d.ts → will-BDq-TMQr.d.ts} +8 -6
- package/package.json +12 -3
- package/src/channels/whatsapp.ts +318 -0
- package/src/cli.ts +32 -9
- package/src/cognition/faculties/executive.engine/engine.ts +3 -2
- package/src/cognition/utilities/token.tracker.ts +6 -0
- package/src/host/boot.ts +32 -10
- package/src/llm/index.ts +75 -25
- package/src/sdk/will.ts +16 -7
- package/src/stem/guards/identity.coherence.ts +4 -3
package/dist/cli.js
CHANGED
|
@@ -3862,6 +3862,11 @@ var MODEL_PRICING = {
|
|
|
3862
3862
|
// Legacy aliases kept for backward compat
|
|
3863
3863
|
"anthropic/claude-haiku-4": { input: 1, output: 5 },
|
|
3864
3864
|
"anthropic/claude-opus-4": { input: 5, output: 25 },
|
|
3865
|
+
// Z.ai (GLM-5 family). `glm-5.2[1m]` is the same model asking for its 1M
|
|
3866
|
+
// context window — same rate, so it gets its own row rather than relying on
|
|
3867
|
+
// the normalizer (a future long-context tier would price differently).
|
|
3868
|
+
"glm/glm-5.2": { input: 1.4, output: 4.4 },
|
|
3869
|
+
"glm/glm-5.2[1m]": { input: 1.4, output: 4.4 },
|
|
3865
3870
|
// Google
|
|
3866
3871
|
"google/gemini-2.0-flash": { input: 0.1, output: 0.4 },
|
|
3867
3872
|
"google/gemini-2.0-pro": { input: 1.25, output: 5 },
|
|
@@ -13153,6 +13158,38 @@ async function withGate(fn, label, gate = llmGate) {
|
|
|
13153
13158
|
}
|
|
13154
13159
|
|
|
13155
13160
|
// src/llm/index.ts
|
|
13161
|
+
var ANTHROPIC_WIRE = /* @__PURE__ */ new Set(["anthropic", "glm"]);
|
|
13162
|
+
function speaksAnthropicWire(provider) {
|
|
13163
|
+
return ANTHROPIC_WIRE.has(provider);
|
|
13164
|
+
}
|
|
13165
|
+
function defaultBaseFor(provider) {
|
|
13166
|
+
switch (provider) {
|
|
13167
|
+
case "anthropic":
|
|
13168
|
+
return "https://api.anthropic.com/v1";
|
|
13169
|
+
// Z.ai documents the base as `…/api/anthropic` because the Anthropic SDK
|
|
13170
|
+
// appends `/v1/messages`; this client appends `/messages`, so the version
|
|
13171
|
+
// segment belongs here — verified against the live endpoint.
|
|
13172
|
+
case "glm":
|
|
13173
|
+
return "https://api.z.ai/api/anthropic/v1";
|
|
13174
|
+
case "openai":
|
|
13175
|
+
return "https://api.openai.com/v1";
|
|
13176
|
+
case "deepseek":
|
|
13177
|
+
return "https://api.deepseek.com/v1";
|
|
13178
|
+
case "google":
|
|
13179
|
+
return "https://generativelanguage.googleapis.com/v1beta";
|
|
13180
|
+
}
|
|
13181
|
+
}
|
|
13182
|
+
function defaultModelFor(provider) {
|
|
13183
|
+
return provider === "glm" ? "glm-5.2" : "claude-sonnet-4-5-20250929";
|
|
13184
|
+
}
|
|
13185
|
+
function anthropicWireHeaders(provider, apiKey) {
|
|
13186
|
+
return {
|
|
13187
|
+
"Content-Type": "application/json",
|
|
13188
|
+
"anthropic-version": "2023-06-01",
|
|
13189
|
+
"x-api-key": apiKey,
|
|
13190
|
+
...provider === "glm" ? { Authorization: `Bearer ${apiKey}` } : {}
|
|
13191
|
+
};
|
|
13192
|
+
}
|
|
13156
13193
|
var DEFAULT_CALL_META = { category: "executive", attribute: "master", function: "decision" };
|
|
13157
13194
|
var LLMDirector = class {
|
|
13158
13195
|
_willId;
|
|
@@ -13255,7 +13292,7 @@ var LLMDirector = class {
|
|
|
13255
13292
|
this._recordCompletion(systemPrompt, userMessage, tick, result2, Date.now() - start, true);
|
|
13256
13293
|
return result2;
|
|
13257
13294
|
}
|
|
13258
|
-
const result = this._provider
|
|
13295
|
+
const result = speaksAnthropicWire(this._provider) ? await this._callAnthropicStream(systemPrompt, userMessage, onChunk, temperature) : await (async () => {
|
|
13259
13296
|
const r = await this._callProvider(systemPrompt, userMessage, temperature);
|
|
13260
13297
|
onChunk(r.text);
|
|
13261
13298
|
return r;
|
|
@@ -13335,11 +13372,7 @@ var LLMDirector = class {
|
|
|
13335
13372
|
try {
|
|
13336
13373
|
res = await fetch(`${this._resolvedBase()}/messages`, {
|
|
13337
13374
|
method: "POST",
|
|
13338
|
-
headers:
|
|
13339
|
-
"Content-Type": "application/json",
|
|
13340
|
-
"anthropic-version": "2023-06-01",
|
|
13341
|
-
"x-api-key": this._apiKey
|
|
13342
|
-
},
|
|
13375
|
+
headers: anthropicWireHeaders(this._provider, this._apiKey),
|
|
13343
13376
|
body: JSON.stringify({
|
|
13344
13377
|
model: this._model,
|
|
13345
13378
|
max_tokens: this._maxOutputTokens,
|
|
@@ -13414,7 +13447,7 @@ var LLMDirector = class {
|
|
|
13414
13447
|
return result2;
|
|
13415
13448
|
}
|
|
13416
13449
|
const result = await withGate(
|
|
13417
|
-
() => this._provider
|
|
13450
|
+
() => speaksAnthropicWire(this._provider) ? this._callAnthropicStream(systemPrompt, userMessage, () => {
|
|
13418
13451
|
}, temperature) : this._callProvider(systemPrompt, userMessage, temperature),
|
|
13419
13452
|
"executive/direct"
|
|
13420
13453
|
);
|
|
@@ -13426,6 +13459,8 @@ var LLMDirector = class {
|
|
|
13426
13459
|
switch (this._provider) {
|
|
13427
13460
|
case "anthropic":
|
|
13428
13461
|
return this._callAnthropic(systemPrompt, userMessage, temperature);
|
|
13462
|
+
case "glm":
|
|
13463
|
+
return this._callAnthropic(systemPrompt, userMessage, temperature);
|
|
13429
13464
|
case "deepseek":
|
|
13430
13465
|
return this._callOpenAI(systemPrompt, userMessage, temperature);
|
|
13431
13466
|
case "openai":
|
|
@@ -13438,16 +13473,7 @@ var LLMDirector = class {
|
|
|
13438
13473
|
}
|
|
13439
13474
|
/** Default API base URL (including version segment) for a provider. */
|
|
13440
13475
|
_baseFor(provider) {
|
|
13441
|
-
|
|
13442
|
-
case "anthropic":
|
|
13443
|
-
return "https://api.anthropic.com/v1";
|
|
13444
|
-
case "openai":
|
|
13445
|
-
return "https://api.openai.com/v1";
|
|
13446
|
-
case "deepseek":
|
|
13447
|
-
return "https://api.deepseek.com/v1";
|
|
13448
|
-
case "google":
|
|
13449
|
-
return "https://generativelanguage.googleapis.com/v1beta";
|
|
13450
|
-
}
|
|
13476
|
+
return defaultBaseFor(provider);
|
|
13451
13477
|
}
|
|
13452
13478
|
/** Resolved API base: explicit override wins, else the provider default. */
|
|
13453
13479
|
_resolvedBase() {
|
|
@@ -13487,15 +13513,11 @@ var LLMDirector = class {
|
|
|
13487
13513
|
};
|
|
13488
13514
|
const res = await this._fetchWithTimeout(`${this._resolvedBase()}/messages`, {
|
|
13489
13515
|
method: "POST",
|
|
13490
|
-
headers:
|
|
13491
|
-
"Content-Type": "application/json",
|
|
13492
|
-
"anthropic-version": "2023-06-01",
|
|
13493
|
-
"x-api-key": this._apiKey
|
|
13494
|
-
},
|
|
13516
|
+
headers: anthropicWireHeaders(this._provider, this._apiKey),
|
|
13495
13517
|
body: JSON.stringify(body)
|
|
13496
13518
|
});
|
|
13497
13519
|
if (!res.ok)
|
|
13498
|
-
throw new Error(
|
|
13520
|
+
throw new Error(`${this._provider} API ${res.status}: ${(await res.text()).slice(0, 300)}`);
|
|
13499
13521
|
const data = await res.json(), text = data.content.find((b) => b.type === "text")?.text ?? "";
|
|
13500
13522
|
return {
|
|
13501
13523
|
text,
|
|
@@ -14275,7 +14297,7 @@ var ExecutiveEngine = class extends AsyncEngine {
|
|
|
14275
14297
|
this._gatingState.executiveInterval = rtConfig.executiveInterval;
|
|
14276
14298
|
this._gatingState.cooldownTicks = rtConfig.cooldownTicks;
|
|
14277
14299
|
if (!this._llmDirector && this._willId) {
|
|
14278
|
-
const execModel = this._models.executive ?? process.env.WILL_LLM_MODEL ?? "
|
|
14300
|
+
const execModel = this._models.executive ?? process.env.WILL_LLM_MODEL ?? defaultModelFor(this._llm?.provider ?? process.env.WILL_LLM_PROVIDER ?? "anthropic");
|
|
14279
14301
|
this._llmDirector = this._directorFor(execModel);
|
|
14280
14302
|
this._summarizer?.attachLLMDirector(this._directorFor(this._models.summarizer ?? execModel));
|
|
14281
14303
|
}
|
|
@@ -22198,12 +22220,13 @@ async function checkIdentityCoherence(input, reviewer) {
|
|
|
22198
22220
|
return { ok: !issues.some((i) => i.severity === "error"), ran: true, issues, raw: text };
|
|
22199
22221
|
}
|
|
22200
22222
|
async function reviewIdentityCoherence(input, opts = {}) {
|
|
22223
|
+
const provider = process.env.WILL_LLM_PROVIDER ?? "anthropic";
|
|
22201
22224
|
const director = new LLMDirector({
|
|
22202
22225
|
willId: opts.willId ?? "identity-coherence",
|
|
22203
|
-
model: process.env.WILL_LLM_MODEL ??
|
|
22226
|
+
model: process.env.WILL_LLM_MODEL ?? defaultModelFor(provider),
|
|
22204
22227
|
maxOutputTokens: 512,
|
|
22205
22228
|
apiKey: process.env.WILL_LLM_API_KEY ?? process.env.ANTHROPIC_API_KEY ?? "",
|
|
22206
|
-
provider
|
|
22229
|
+
provider,
|
|
22207
22230
|
sessionLogger: null,
|
|
22208
22231
|
baseUrl: process.env.WILL_LLM_BASE_URL ?? process.env.OPENAI_BASE_URL,
|
|
22209
22232
|
// When a per-Will tracker is supplied, the creation-time review records under
|
|
@@ -25760,7 +25783,9 @@ var Will = class _Will {
|
|
|
25760
25783
|
}
|
|
25761
25784
|
// ── Internals ──────────────────────────────────────────────
|
|
25762
25785
|
_buildConfig(id, opts) {
|
|
25763
|
-
const
|
|
25786
|
+
const mode = opts.llm ?? (process.env.ANTHROPIC_API_KEY ? "anthropic" : process.env.ZAI_API_KEY ? "glm" : "mock");
|
|
25787
|
+
const useMock = mode === "mock";
|
|
25788
|
+
const llmConfig = mode === "glm" ? { provider: "glm", ...opts.llmConfig } : opts.llmConfig;
|
|
25764
25789
|
return {
|
|
25765
25790
|
id,
|
|
25766
25791
|
name: opts.name,
|
|
@@ -25772,7 +25797,7 @@ var Will = class _Will {
|
|
|
25772
25797
|
},
|
|
25773
25798
|
anatomy: opts.anatomy ?? "mind",
|
|
25774
25799
|
model: opts.model,
|
|
25775
|
-
llm:
|
|
25800
|
+
llm: llmConfig,
|
|
25776
25801
|
testMode: useMock,
|
|
25777
25802
|
persistentMemory: opts.persist ?? false,
|
|
25778
25803
|
snapshotInterval: 100,
|
|
@@ -25955,21 +25980,32 @@ function routeLogsToStderr() {
|
|
|
25955
25980
|
function slug2(s) {
|
|
25956
25981
|
return s.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "will";
|
|
25957
25982
|
}
|
|
25983
|
+
function resolveLlmMode() {
|
|
25984
|
+
const explicit = process.env.WILL_LLM;
|
|
25985
|
+
if (explicit) return explicit;
|
|
25986
|
+
if (process.env.ANTHROPIC_API_KEY) return "anthropic";
|
|
25987
|
+
if (process.env.ZAI_API_KEY) return "glm";
|
|
25988
|
+
return "mock";
|
|
25989
|
+
}
|
|
25990
|
+
function resolveLlmKey(mode) {
|
|
25991
|
+
return process.env.WILL_LLM_API_KEY ?? (mode === "glm" ? process.env.ZAI_API_KEY : process.env.ANTHROPIC_API_KEY);
|
|
25992
|
+
}
|
|
25958
25993
|
async function preflightLLM(anatomy) {
|
|
25959
|
-
const mode =
|
|
25994
|
+
const mode = resolveLlmMode();
|
|
25960
25995
|
if (mode === "mock" || anatomy === "reflex") return;
|
|
25961
|
-
const key =
|
|
25996
|
+
const key = resolveLlmKey(mode);
|
|
25962
25997
|
if (!key) {
|
|
25963
|
-
|
|
25998
|
+
const expected = mode === "glm" ? "ZAI_API_KEY" : "ANTHROPIC_API_KEY";
|
|
25999
|
+
console.error(`[will] WILL_LLM=${mode} but no ${expected} / WILL_LLM_API_KEY is set.`);
|
|
25964
26000
|
console.error("[will] The Will would boot, perceive, and never speak. Set a key, or run keyless with WILL_LLM=mock.");
|
|
25965
26001
|
process.exit(2);
|
|
25966
26002
|
}
|
|
25967
|
-
const base = process.env.WILL_LLM_BASE_URL ??
|
|
25968
|
-
const model = process.env.WILL_LLM_MODEL ?? "claude-haiku-4-5-20251001";
|
|
26003
|
+
const base = process.env.WILL_LLM_BASE_URL ?? defaultBaseFor(mode);
|
|
26004
|
+
const model = process.env.WILL_LLM_MODEL ?? (mode === "glm" ? defaultModelFor("glm") : "claude-haiku-4-5-20251001");
|
|
25969
26005
|
try {
|
|
25970
26006
|
const res = await fetch(`${base}/messages`, {
|
|
25971
26007
|
method: "POST",
|
|
25972
|
-
headers:
|
|
26008
|
+
headers: anthropicWireHeaders(mode, key),
|
|
25973
26009
|
body: JSON.stringify({ model, max_tokens: 1, messages: [{ role: "user", content: "ping" }] }),
|
|
25974
26010
|
signal: AbortSignal.timeout(2e4)
|
|
25975
26011
|
});
|
|
@@ -26474,21 +26510,200 @@ async function createDiscordClient() {
|
|
|
26474
26510
|
});
|
|
26475
26511
|
}
|
|
26476
26512
|
|
|
26513
|
+
// src/channels/whatsapp.ts
|
|
26514
|
+
var WHATSAPP_MESSAGE_LIMIT = 65536;
|
|
26515
|
+
var isGroupJid = (jid) => jid.endsWith("@g.us");
|
|
26516
|
+
var bareId = (jid) => jid.split("@")[0].split(":")[0];
|
|
26517
|
+
var dmJidFor = (userId) => `${userId}@s.whatsapp.net`;
|
|
26518
|
+
function textOf(m) {
|
|
26519
|
+
const msg = m.message;
|
|
26520
|
+
return msg?.conversation ?? msg?.extendedTextMessage?.text ?? msg?.imageMessage?.caption ?? msg?.videoMessage?.caption ?? "";
|
|
26521
|
+
}
|
|
26522
|
+
async function connectWhatsApp(will, opts = {}) {
|
|
26523
|
+
const log = opts.log ?? ((m) => console.error(`[will:whatsapp] ${m}`));
|
|
26524
|
+
const roster = new ChannelRoster(opts.rosterPath ?? `.will/${will.id}.whatsapp.json`);
|
|
26525
|
+
const allowed = opts.chats?.length ? new Set(opts.chats) : null;
|
|
26526
|
+
let closed = false;
|
|
26527
|
+
const socket = opts.socket ?? await createWhatsAppSocket({
|
|
26528
|
+
authPath: opts.authPath ?? `.will/${will.id}.wa-auth`,
|
|
26529
|
+
log,
|
|
26530
|
+
stillOpen: () => !closed
|
|
26531
|
+
});
|
|
26532
|
+
let lastActiveChatId = opts.homeChatId ?? null;
|
|
26533
|
+
socket.ev.on("messages.upsert", ({ messages, type }) => {
|
|
26534
|
+
if (type && type !== "notify") return;
|
|
26535
|
+
for (const m of messages) void onMessage(m);
|
|
26536
|
+
});
|
|
26537
|
+
async function onMessage(m) {
|
|
26538
|
+
const jid = m.key.remoteJid;
|
|
26539
|
+
if (!jid || m.key.fromMe || !m.message || m.messageStubType) return;
|
|
26540
|
+
if (jid.endsWith("@broadcast") || jid.endsWith("@newsletter")) return;
|
|
26541
|
+
if (allowed && !allowed.has(jid)) return;
|
|
26542
|
+
const isGroup = isGroupJid(jid);
|
|
26543
|
+
const senderJid = isGroup ? m.key.participant : jid;
|
|
26544
|
+
if (!senderJid) return;
|
|
26545
|
+
const selfId = socket.user ? bareId(socket.user.id) : null;
|
|
26546
|
+
const mentioned = m.message.extendedTextMessage?.contextInfo?.mentionedJid ?? [];
|
|
26547
|
+
const addressed = !isGroup || selfId != null && mentioned.some((j) => bareId(j) === selfId);
|
|
26548
|
+
if (opts.mentionOnly && !addressed) return;
|
|
26549
|
+
const userId = bareId(senderJid);
|
|
26550
|
+
const entityId = `whatsapp:${userId}`;
|
|
26551
|
+
const speaker = m.pushName ?? void 0;
|
|
26552
|
+
roster.record({
|
|
26553
|
+
entityId,
|
|
26554
|
+
userId,
|
|
26555
|
+
...speaker ? { displayName: speaker } : {},
|
|
26556
|
+
...isGroup ? { lastChannelId: jid } : { dmChannelId: jid }
|
|
26557
|
+
});
|
|
26558
|
+
if (isGroup) lastActiveChatId = jid;
|
|
26559
|
+
if (addressed) await socket.sendPresenceUpdate?.("composing", jid).catch(() => {
|
|
26560
|
+
});
|
|
26561
|
+
const text = textOf(m);
|
|
26562
|
+
if (!text.trim()) return;
|
|
26563
|
+
await will.perceive({
|
|
26564
|
+
text,
|
|
26565
|
+
from: entityId,
|
|
26566
|
+
thread: `whatsapp:${jid}`,
|
|
26567
|
+
...speaker ? { speaker } : {}
|
|
26568
|
+
});
|
|
26569
|
+
}
|
|
26570
|
+
will.on("message", (m) => {
|
|
26571
|
+
if (!closed) void deliver(m);
|
|
26572
|
+
});
|
|
26573
|
+
async function deliver(m) {
|
|
26574
|
+
const peer = m.to ? roster.resolve(m.to) : void 0;
|
|
26575
|
+
const chunks = chunkText(m.content, WHATSAPP_MESSAGE_LIMIT);
|
|
26576
|
+
const derivedDm = m.to?.startsWith("whatsapp:") ? dmJidFor(m.to.slice("whatsapp:".length)) : void 0;
|
|
26577
|
+
const targets = [peer?.lastChannelId, peer?.dmChannelId, derivedDm, opts.homeChatId ?? void 0, lastActiveChatId ?? void 0];
|
|
26578
|
+
for (const jid of targets) {
|
|
26579
|
+
if (!jid) continue;
|
|
26580
|
+
try {
|
|
26581
|
+
for (const chunk of chunks) await socket.sendMessage(jid, { text: chunk });
|
|
26582
|
+
return;
|
|
26583
|
+
} catch {
|
|
26584
|
+
}
|
|
26585
|
+
}
|
|
26586
|
+
log(`no route for utterance to '${m.to}' \u2014 dropped (${m.content.length} chars)`);
|
|
26587
|
+
}
|
|
26588
|
+
return {
|
|
26589
|
+
kind: "whatsapp",
|
|
26590
|
+
async start() {
|
|
26591
|
+
log(`${will.name} is present on WhatsApp${socket.user ? ` as ${bareId(socket.user.id)}` : ""}`);
|
|
26592
|
+
},
|
|
26593
|
+
async close() {
|
|
26594
|
+
if (closed) return;
|
|
26595
|
+
closed = true;
|
|
26596
|
+
roster.flush();
|
|
26597
|
+
try {
|
|
26598
|
+
socket.end?.();
|
|
26599
|
+
} catch {
|
|
26600
|
+
}
|
|
26601
|
+
}
|
|
26602
|
+
};
|
|
26603
|
+
}
|
|
26604
|
+
async function createWhatsAppSocket(o) {
|
|
26605
|
+
let baileys;
|
|
26606
|
+
try {
|
|
26607
|
+
baileys = await import('baileys');
|
|
26608
|
+
} catch {
|
|
26609
|
+
throw new Error("baileys is not installed (it is an optionalDependency) \u2014 run `bun add baileys` / `npm i baileys` and retry.");
|
|
26610
|
+
}
|
|
26611
|
+
const { default: makeWASocket, useMultiFileAuthState, DisconnectReason } = baileys;
|
|
26612
|
+
const noop = () => {
|
|
26613
|
+
};
|
|
26614
|
+
const logger2 = {
|
|
26615
|
+
level: "silent",
|
|
26616
|
+
child() {
|
|
26617
|
+
return logger2;
|
|
26618
|
+
},
|
|
26619
|
+
trace: noop,
|
|
26620
|
+
debug: noop,
|
|
26621
|
+
info: noop,
|
|
26622
|
+
warn: noop,
|
|
26623
|
+
error: noop,
|
|
26624
|
+
fatal: noop
|
|
26625
|
+
};
|
|
26626
|
+
const subscribers = [];
|
|
26627
|
+
let inner = null;
|
|
26628
|
+
const facade = {
|
|
26629
|
+
get user() {
|
|
26630
|
+
return inner?.user ? { id: inner.user.id, name: inner.user.name ?? void 0 } : null;
|
|
26631
|
+
},
|
|
26632
|
+
ev: { on: (_e, fn) => {
|
|
26633
|
+
subscribers.push(fn);
|
|
26634
|
+
} },
|
|
26635
|
+
sendMessage: (jid, content) => {
|
|
26636
|
+
if (!inner) return Promise.reject(new Error("whatsapp socket not connected"));
|
|
26637
|
+
return inner.sendMessage(jid, content);
|
|
26638
|
+
},
|
|
26639
|
+
sendPresenceUpdate: (state2, jid) => inner?.sendPresenceUpdate(state2, jid) ?? Promise.resolve(),
|
|
26640
|
+
end: () => inner?.end(void 0)
|
|
26641
|
+
};
|
|
26642
|
+
const { state, saveCreds } = await useMultiFileAuthState(o.authPath);
|
|
26643
|
+
await new Promise((resolveOpen, rejectOpen) => {
|
|
26644
|
+
let opened = false;
|
|
26645
|
+
function connect2() {
|
|
26646
|
+
const sock = makeWASocket({ auth: state, logger: logger2 });
|
|
26647
|
+
inner = sock;
|
|
26648
|
+
sock.ev.on("creds.update", saveCreds);
|
|
26649
|
+
sock.ev.on("messages.upsert", (u) => {
|
|
26650
|
+
for (const fn of subscribers) fn(u);
|
|
26651
|
+
});
|
|
26652
|
+
sock.ev.on("connection.update", (update) => {
|
|
26653
|
+
const { connection, lastDisconnect, qr } = update;
|
|
26654
|
+
if (qr) void printQr(qr, o.log);
|
|
26655
|
+
if (connection === "open" && !opened) {
|
|
26656
|
+
opened = true;
|
|
26657
|
+
resolveOpen();
|
|
26658
|
+
}
|
|
26659
|
+
if (connection === "close") {
|
|
26660
|
+
const code = lastDisconnect?.error?.output?.statusCode;
|
|
26661
|
+
if (code === DisconnectReason.loggedOut) {
|
|
26662
|
+
const err = new Error("WhatsApp unlinked this device (logged out) \u2014 delete the auth dir and pair again.");
|
|
26663
|
+
o.log(err.message);
|
|
26664
|
+
if (!opened) rejectOpen(err);
|
|
26665
|
+
return;
|
|
26666
|
+
}
|
|
26667
|
+
if (o.stillOpen()) {
|
|
26668
|
+
o.log(`connection closed (status ${code ?? "?"}) \u2014 reconnecting\u2026`);
|
|
26669
|
+
setTimeout(connect2, 3e3);
|
|
26670
|
+
}
|
|
26671
|
+
}
|
|
26672
|
+
});
|
|
26673
|
+
}
|
|
26674
|
+
connect2();
|
|
26675
|
+
});
|
|
26676
|
+
return facade;
|
|
26677
|
+
}
|
|
26678
|
+
async function printQr(qr, log) {
|
|
26679
|
+
log("pair this device: WhatsApp \u2192 Settings \u2192 Linked devices \u2192 Link a device");
|
|
26680
|
+
try {
|
|
26681
|
+
const qrt = await import('qrcode-terminal');
|
|
26682
|
+
(qrt.default ?? qrt).generate(qr, { small: true });
|
|
26683
|
+
} catch {
|
|
26684
|
+
log(`qrcode-terminal not installed \u2014 raw pairing code:
|
|
26685
|
+
${qr}`);
|
|
26686
|
+
}
|
|
26687
|
+
}
|
|
26688
|
+
|
|
26477
26689
|
// src/cli.ts
|
|
26478
26690
|
routeLogsToStderr();
|
|
26479
|
-
var USAGE = `usage: will <mcp | serve | discord>
|
|
26691
|
+
var USAGE = `usage: will <mcp | serve | discord | whatsapp>
|
|
26480
26692
|
|
|
26481
|
-
mcp
|
|
26482
|
-
serve
|
|
26483
|
-
discord
|
|
26484
|
-
|
|
26693
|
+
mcp host a persistent mind over MCP stdio (Claude Desktop / Claude Code)
|
|
26694
|
+
serve host a persistent mind over HTTP (any language; WILL_PORT, default 7777)
|
|
26695
|
+
discord put a persistent mind in a Discord server (DISCORD_BOT_TOKEN; optional
|
|
26696
|
+
WILL_DISCORD_CHANNELS, WILL_DISCORD_MENTION_ONLY, WILL_DISCORD_HOME_CHANNEL)
|
|
26697
|
+
whatsapp put a persistent mind on WhatsApp \u2014 QR-pairs as a linked device; no token.
|
|
26698
|
+
UNOFFICIAL protocol (ban risk; use a spare number \u2014 docs/channels/whatsapp.md).
|
|
26699
|
+
Optional WILL_WHATSAPP_CHATS, WILL_WHATSAPP_MENTION_ONLY, WILL_WHATSAPP_HOME_CHAT
|
|
26485
26700
|
|
|
26486
26701
|
Shared env: WILL_NAME, WILL_IDENTITY, WILL_TIER, WILL_LLM, WILL_TICK_MS,
|
|
26487
26702
|
WILL_SEED, WILL_PMA_PATH, WILL_MCP_SERVERS. The mind persists across runs via
|
|
26488
26703
|
its PMA artifact.`;
|
|
26489
26704
|
async function main() {
|
|
26490
26705
|
const sub = process.argv[2];
|
|
26491
|
-
if (sub !== "mcp" && sub !== "serve" && sub !== "discord") {
|
|
26706
|
+
if (sub !== "mcp" && sub !== "serve" && sub !== "discord" && sub !== "whatsapp") {
|
|
26492
26707
|
console.error(sub ? `unknown subcommand: ${sub}
|
|
26493
26708
|
|
|
26494
26709
|
${USAGE}` : USAGE);
|
|
@@ -26520,6 +26735,21 @@ ${USAGE}` : USAGE);
|
|
|
26520
26735
|
console.error(`[will] ${name} is present on Discord (tick ${tickMs}ms, anatomy ${anatomy}) \u2014 it speaks when it decides to.`);
|
|
26521
26736
|
return;
|
|
26522
26737
|
}
|
|
26738
|
+
if (sub === "whatsapp") {
|
|
26739
|
+
const csv = (v) => v?.split(",").map((s) => s.trim()).filter(Boolean);
|
|
26740
|
+
const stem = pmaPath.replace(/(\.pma)?\.json$/, "");
|
|
26741
|
+
const bridge = await connectWhatsApp(will, {
|
|
26742
|
+
chats: csv(process.env.WILL_WHATSAPP_CHATS),
|
|
26743
|
+
mentionOnly: /^(1|true|yes)$/i.test(process.env.WILL_WHATSAPP_MENTION_ONLY ?? ""),
|
|
26744
|
+
homeChatId: process.env.WILL_WHATSAPP_HOME_CHAT,
|
|
26745
|
+
authPath: stem + ".wa-auth",
|
|
26746
|
+
rosterPath: stem + ".whatsapp.json"
|
|
26747
|
+
});
|
|
26748
|
+
onCleanup(() => bridge.close());
|
|
26749
|
+
await bridge.start();
|
|
26750
|
+
console.error(`[will] ${name} is present on WhatsApp (tick ${tickMs}ms, anatomy ${anatomy}) \u2014 it speaks when it decides to.`);
|
|
26751
|
+
return;
|
|
26752
|
+
}
|
|
26523
26753
|
const port = parseInt(process.env.WILL_PORT ?? "7777");
|
|
26524
26754
|
const host = process.env.WILL_HOST ?? "127.0.0.1";
|
|
26525
26755
|
const server = buildWillHttpServer(will, { pmaPath });
|