feat: multi-agent routing + multi-account providers
This commit is contained in:
@@ -2,7 +2,12 @@ import { randomUUID } from "node:crypto";
|
||||
|
||||
import { agentCommand } from "../../commands/agent.js";
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { type SessionEntry, saveSessionStore } from "../../config/sessions.js";
|
||||
import {
|
||||
resolveAgentIdFromSessionKey,
|
||||
resolveAgentMainSessionKey,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { registerAgentRunContext } from "../../infra/agent-events.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { resolveSendPolicy } from "../../sessions/send-policy.js";
|
||||
@@ -41,7 +46,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
sessionKey?: string;
|
||||
thinking?: string;
|
||||
deliver?: boolean;
|
||||
channel?: string;
|
||||
provider?: string;
|
||||
lane?: string;
|
||||
extraSystemPrompt?: string;
|
||||
idempotencyKey: string;
|
||||
@@ -72,7 +77,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
cfgForAgent = cfg;
|
||||
const now = Date.now();
|
||||
const sessionId = entry?.sessionId ?? randomUUID();
|
||||
sessionEntry = {
|
||||
const nextEntry: SessionEntry = {
|
||||
sessionId,
|
||||
updatedAt: now,
|
||||
thinkingLevel: entry?.thinkingLevel,
|
||||
@@ -80,14 +85,15 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
systemSent: entry?.systemSent,
|
||||
sendPolicy: entry?.sendPolicy,
|
||||
skillsSnapshot: entry?.skillsSnapshot,
|
||||
lastChannel: entry?.lastChannel,
|
||||
lastProvider: entry?.lastProvider,
|
||||
lastTo: entry?.lastTo,
|
||||
};
|
||||
sessionEntry = nextEntry;
|
||||
const sendPolicy = resolveSendPolicy({
|
||||
cfg,
|
||||
entry,
|
||||
sessionKey: requestedSessionKey,
|
||||
surface: entry?.surface,
|
||||
provider: entry?.provider,
|
||||
chatType: entry?.chatType,
|
||||
});
|
||||
if (sendPolicy === "deny") {
|
||||
@@ -102,14 +108,22 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
if (store) {
|
||||
store[requestedSessionKey] = sessionEntry;
|
||||
store[requestedSessionKey] = nextEntry;
|
||||
if (storePath) {
|
||||
await saveSessionStore(storePath, store);
|
||||
}
|
||||
}
|
||||
resolvedSessionId = sessionId;
|
||||
const mainKey = (cfg.session?.mainKey ?? "main").trim() || "main";
|
||||
if (requestedSessionKey === mainKey) {
|
||||
const agentId = resolveAgentIdFromSessionKey(requestedSessionKey);
|
||||
const mainSessionKey = resolveAgentMainSessionKey({
|
||||
cfg,
|
||||
agentId,
|
||||
});
|
||||
const rawMainKey = (cfg.session?.mainKey ?? "main").trim() || "main";
|
||||
if (
|
||||
requestedSessionKey === mainSessionKey ||
|
||||
requestedSessionKey === rawMainKey
|
||||
) {
|
||||
context.addChatRun(idem, {
|
||||
sessionKey: requestedSessionKey,
|
||||
clientRunId: idem,
|
||||
@@ -121,42 +135,42 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
|
||||
const runId = idem;
|
||||
|
||||
const requestedChannelRaw =
|
||||
typeof request.channel === "string" ? request.channel.trim() : "";
|
||||
const requestedChannelNormalized = requestedChannelRaw
|
||||
? requestedChannelRaw.toLowerCase()
|
||||
const requestedProviderRaw =
|
||||
typeof request.provider === "string" ? request.provider.trim() : "";
|
||||
const requestedProviderNormalized = requestedProviderRaw
|
||||
? requestedProviderRaw.toLowerCase()
|
||||
: "last";
|
||||
const requestedChannel =
|
||||
requestedChannelNormalized === "imsg"
|
||||
const requestedProvider =
|
||||
requestedProviderNormalized === "imsg"
|
||||
? "imessage"
|
||||
: requestedChannelNormalized;
|
||||
: requestedProviderNormalized;
|
||||
|
||||
const lastChannel = sessionEntry?.lastChannel;
|
||||
const lastProvider = sessionEntry?.lastProvider;
|
||||
const lastTo =
|
||||
typeof sessionEntry?.lastTo === "string"
|
||||
? sessionEntry.lastTo.trim()
|
||||
: "";
|
||||
|
||||
const resolvedChannel = (() => {
|
||||
if (requestedChannel === "last") {
|
||||
const resolvedProvider = (() => {
|
||||
if (requestedProvider === "last") {
|
||||
// WebChat is not a deliverable surface. Treat it as "unset" for routing,
|
||||
// so VoiceWake and CLI callers don't get stuck with deliver=false.
|
||||
return lastChannel && lastChannel !== "webchat"
|
||||
? lastChannel
|
||||
return lastProvider && lastProvider !== "webchat"
|
||||
? lastProvider
|
||||
: "whatsapp";
|
||||
}
|
||||
if (
|
||||
requestedChannel === "whatsapp" ||
|
||||
requestedChannel === "telegram" ||
|
||||
requestedChannel === "discord" ||
|
||||
requestedChannel === "signal" ||
|
||||
requestedChannel === "imessage" ||
|
||||
requestedChannel === "webchat"
|
||||
requestedProvider === "whatsapp" ||
|
||||
requestedProvider === "telegram" ||
|
||||
requestedProvider === "discord" ||
|
||||
requestedProvider === "signal" ||
|
||||
requestedProvider === "imessage" ||
|
||||
requestedProvider === "webchat"
|
||||
) {
|
||||
return requestedChannel;
|
||||
return requestedProvider;
|
||||
}
|
||||
return lastChannel && lastChannel !== "webchat"
|
||||
? lastChannel
|
||||
return lastProvider && lastProvider !== "webchat"
|
||||
? lastProvider
|
||||
: "whatsapp";
|
||||
})();
|
||||
|
||||
@@ -167,11 +181,11 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
: undefined;
|
||||
if (explicit) return explicit;
|
||||
if (
|
||||
resolvedChannel === "whatsapp" ||
|
||||
resolvedChannel === "telegram" ||
|
||||
resolvedChannel === "discord" ||
|
||||
resolvedChannel === "signal" ||
|
||||
resolvedChannel === "imessage"
|
||||
resolvedProvider === "whatsapp" ||
|
||||
resolvedProvider === "telegram" ||
|
||||
resolvedProvider === "discord" ||
|
||||
resolvedProvider === "signal" ||
|
||||
resolvedProvider === "imessage"
|
||||
) {
|
||||
return lastTo || undefined;
|
||||
}
|
||||
@@ -182,7 +196,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
// If we derived a WhatsApp recipient from session "lastTo", ensure it is still valid
|
||||
// for the configured allowlist. Otherwise, fall back to the first allowed number so
|
||||
// voice wake doesn't silently route to stale/test recipients.
|
||||
if (resolvedChannel !== "whatsapp") return resolvedTo;
|
||||
if (resolvedProvider !== "whatsapp") return resolvedTo;
|
||||
const explicit =
|
||||
typeof request.to === "string" && request.to.trim()
|
||||
? request.to.trim()
|
||||
@@ -207,7 +221,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
return allowFrom[0];
|
||||
})();
|
||||
|
||||
const deliver = request.deliver === true && resolvedChannel !== "webchat";
|
||||
const deliver = request.deliver === true && resolvedProvider !== "webchat";
|
||||
|
||||
const accepted = {
|
||||
runId,
|
||||
@@ -229,10 +243,10 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
sessionId: resolvedSessionId,
|
||||
thinking: request.thinking,
|
||||
deliver,
|
||||
provider: resolvedChannel,
|
||||
provider: resolvedProvider,
|
||||
timeout: request.timeout?.toString(),
|
||||
bestEffortDeliver,
|
||||
surface: "VoiceWake",
|
||||
messageProvider: "voicewake",
|
||||
runId,
|
||||
lane: request.lane,
|
||||
extraSystemPrompt: request.extraSystemPrompt,
|
||||
|
||||
@@ -202,7 +202,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
verboseLevel: entry?.verboseLevel,
|
||||
systemSent: entry?.systemSent,
|
||||
sendPolicy: entry?.sendPolicy,
|
||||
lastChannel: entry?.lastChannel,
|
||||
lastProvider: entry?.lastProvider,
|
||||
lastTo: entry?.lastTo,
|
||||
};
|
||||
const clientRunId = p.idempotencyKey;
|
||||
@@ -212,7 +212,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
cfg,
|
||||
entry,
|
||||
sessionKey: p.sessionKey,
|
||||
surface: entry?.surface,
|
||||
provider: entry?.provider,
|
||||
chatType: entry?.chatType,
|
||||
});
|
||||
if (sendPolicy === "deny") {
|
||||
@@ -262,7 +262,7 @@ export const chatHandlers: GatewayRequestHandlers = {
|
||||
thinking: p.thinking,
|
||||
deliver: p.deliver,
|
||||
timeout: Math.ceil(timeoutMs / 1000).toString(),
|
||||
surface: "WebChat",
|
||||
messageProvider: "webchat",
|
||||
abortSignal: abortController.signal,
|
||||
},
|
||||
defaultRuntime,
|
||||
|
||||
@@ -6,7 +6,6 @@ import {
|
||||
} from "../../config/config.js";
|
||||
import { type DiscordProbe, probeDiscord } from "../../discord/probe.js";
|
||||
import { type IMessageProbe, probeIMessage } from "../../imessage/probe.js";
|
||||
import { webAuthExists } from "../../providers/web/index.js";
|
||||
import { probeSignal, type SignalProbe } from "../../signal/probe.js";
|
||||
import { probeSlack, type SlackProbe } from "../../slack/probe.js";
|
||||
import {
|
||||
@@ -15,7 +14,15 @@ import {
|
||||
} from "../../slack/token.js";
|
||||
import { probeTelegram, type TelegramProbe } from "../../telegram/probe.js";
|
||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||
import { getWebAuthAgeMs, readWebSelfId } from "../../web/session.js";
|
||||
import {
|
||||
listEnabledWhatsAppAccounts,
|
||||
resolveDefaultWhatsAppAccountId,
|
||||
} from "../../web/accounts.js";
|
||||
import {
|
||||
getWebAuthAgeMs,
|
||||
readWebSelfId,
|
||||
webAuthExists,
|
||||
} from "../../web/session.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
errorShape,
|
||||
@@ -148,10 +155,55 @@ export const providersHandlers: GatewayRequestHandlers = {
|
||||
imessageLastProbeAt = Date.now();
|
||||
}
|
||||
|
||||
const linked = await webAuthExists();
|
||||
const authAgeMs = getWebAuthAgeMs();
|
||||
const self = readWebSelfId();
|
||||
const runtime = context.getRuntimeSnapshot();
|
||||
const defaultWhatsAppAccountId = resolveDefaultWhatsAppAccountId(cfg);
|
||||
const enabledWhatsAppAccounts = listEnabledWhatsAppAccounts(cfg);
|
||||
const defaultWhatsAppAccount =
|
||||
enabledWhatsAppAccounts.find(
|
||||
(account) => account.accountId === defaultWhatsAppAccountId,
|
||||
) ?? enabledWhatsAppAccounts[0];
|
||||
const linked = defaultWhatsAppAccount
|
||||
? await webAuthExists(defaultWhatsAppAccount.authDir)
|
||||
: false;
|
||||
const authAgeMs = defaultWhatsAppAccount
|
||||
? getWebAuthAgeMs(defaultWhatsAppAccount.authDir)
|
||||
: null;
|
||||
const self = defaultWhatsAppAccount
|
||||
? readWebSelfId(defaultWhatsAppAccount.authDir)
|
||||
: { e164: null, jid: null };
|
||||
|
||||
const defaultWhatsAppStatus = {
|
||||
running: false,
|
||||
connected: false,
|
||||
reconnectAttempts: 0,
|
||||
lastConnectedAt: null,
|
||||
lastDisconnect: null,
|
||||
lastMessageAt: null,
|
||||
lastEventAt: null,
|
||||
lastError: null,
|
||||
} as const;
|
||||
const whatsappAccounts = await Promise.all(
|
||||
enabledWhatsAppAccounts.map(async (account) => {
|
||||
const rt =
|
||||
runtime.whatsappAccounts?.[account.accountId] ??
|
||||
defaultWhatsAppStatus;
|
||||
return {
|
||||
accountId: account.accountId,
|
||||
enabled: account.enabled,
|
||||
linked: await webAuthExists(account.authDir),
|
||||
authAgeMs: getWebAuthAgeMs(account.authDir),
|
||||
self: readWebSelfId(account.authDir),
|
||||
running: rt.running,
|
||||
connected: rt.connected,
|
||||
lastConnectedAt: rt.lastConnectedAt ?? null,
|
||||
lastDisconnect: rt.lastDisconnect ?? null,
|
||||
reconnectAttempts: rt.reconnectAttempts,
|
||||
lastMessageAt: rt.lastMessageAt ?? null,
|
||||
lastEventAt: rt.lastEventAt ?? null,
|
||||
lastError: rt.lastError ?? null,
|
||||
};
|
||||
}),
|
||||
);
|
||||
|
||||
respond(
|
||||
true,
|
||||
@@ -171,6 +223,8 @@ export const providersHandlers: GatewayRequestHandlers = {
|
||||
lastEventAt: runtime.whatsapp.lastEventAt ?? null,
|
||||
lastError: runtime.whatsapp.lastError ?? null,
|
||||
},
|
||||
whatsappAccounts,
|
||||
whatsappDefaultAccountId: defaultWhatsAppAccountId,
|
||||
telegram: {
|
||||
configured: telegramEnabled && Boolean(telegramToken),
|
||||
tokenSource,
|
||||
|
||||
@@ -6,6 +6,7 @@ import { sendMessageSignal } from "../../signal/index.js";
|
||||
import { sendMessageSlack } from "../../slack/send.js";
|
||||
import { sendMessageTelegram } from "../../telegram/send.js";
|
||||
import { resolveTelegramToken } from "../../telegram/token.js";
|
||||
import { resolveDefaultWhatsAppAccountId } from "../../web/accounts.js";
|
||||
import { sendMessageWhatsApp, sendPollWhatsApp } from "../../web/outbound.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
@@ -37,6 +38,7 @@ export const sendHandlers: GatewayRequestHandlers = {
|
||||
mediaUrl?: string;
|
||||
gifPlayback?: boolean;
|
||||
provider?: string;
|
||||
accountId?: string;
|
||||
idempotencyKey: string;
|
||||
};
|
||||
const idem = request.idempotencyKey;
|
||||
@@ -148,10 +150,17 @@ export const sendHandlers: GatewayRequestHandlers = {
|
||||
});
|
||||
respond(true, payload, undefined, { provider });
|
||||
} else {
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
typeof request.accountId === "string" &&
|
||||
request.accountId.trim().length > 0
|
||||
? request.accountId.trim()
|
||||
: resolveDefaultWhatsAppAccountId(cfg);
|
||||
const result = await sendMessageWhatsApp(to, message, {
|
||||
mediaUrl: request.mediaUrl,
|
||||
verbose: shouldLogVerbose(),
|
||||
gifPlayback: request.gifPlayback,
|
||||
accountId,
|
||||
});
|
||||
const payload = {
|
||||
runId: idem,
|
||||
@@ -199,6 +208,7 @@ export const sendHandlers: GatewayRequestHandlers = {
|
||||
maxSelections?: number;
|
||||
durationHours?: number;
|
||||
provider?: string;
|
||||
accountId?: string;
|
||||
idempotencyKey: string;
|
||||
};
|
||||
const idem = request.idempotencyKey;
|
||||
@@ -245,8 +255,15 @@ export const sendHandlers: GatewayRequestHandlers = {
|
||||
});
|
||||
respond(true, payload, undefined, { provider });
|
||||
} else {
|
||||
const cfg = loadConfig();
|
||||
const accountId =
|
||||
typeof request.accountId === "string" &&
|
||||
request.accountId.trim().length > 0
|
||||
? request.accountId.trim()
|
||||
: resolveDefaultWhatsAppAccountId(cfg);
|
||||
const result = await sendPollWhatsApp(to, poll, {
|
||||
verbose: shouldLogVerbose(),
|
||||
accountId,
|
||||
});
|
||||
const payload = {
|
||||
runId: idem,
|
||||
|
||||
@@ -24,11 +24,11 @@ import { loadConfig } from "../../config/config.js";
|
||||
import {
|
||||
loadSessionStore,
|
||||
resolveMainSessionKey,
|
||||
resolveStorePath,
|
||||
type SessionEntry,
|
||||
saveSessionStore,
|
||||
} from "../../config/sessions.js";
|
||||
import { clearCommandLane } from "../../process/command-queue.js";
|
||||
import { isSubagentSessionKey } from "../../routing/session-key.js";
|
||||
import { normalizeSendPolicy } from "../../sessions/send-policy.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
@@ -43,7 +43,8 @@ import {
|
||||
import {
|
||||
archiveFileOnDisk,
|
||||
listSessionsFromStore,
|
||||
loadSessionEntry,
|
||||
loadCombinedSessionStoreForGateway,
|
||||
resolveGatewaySessionStoreTarget,
|
||||
resolveSessionTranscriptCandidates,
|
||||
type SessionsPatchResult,
|
||||
} from "../session-utils.js";
|
||||
@@ -64,8 +65,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
const p = params as import("../protocol/index.js").SessionsListParams;
|
||||
const cfg = loadConfig();
|
||||
const storePath = resolveStorePath(cfg.session?.store);
|
||||
const store = loadSessionStore(storePath);
|
||||
const { storePath, store } = loadCombinedSessionStoreForGateway(cfg);
|
||||
const result = listSessionsFromStore({
|
||||
cfg,
|
||||
storePath,
|
||||
@@ -98,11 +98,18 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
|
||||
const cfg = loadConfig();
|
||||
const storePath = resolveStorePath(cfg.session?.store);
|
||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||
const storePath = target.storePath;
|
||||
const store = loadSessionStore(storePath);
|
||||
const now = Date.now();
|
||||
|
||||
const existing = store[key];
|
||||
const primaryKey = target.storeKeys[0] ?? key;
|
||||
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
|
||||
store[primaryKey] = store[existingKey];
|
||||
delete store[existingKey];
|
||||
}
|
||||
const existing = store[primaryKey];
|
||||
const next: SessionEntry = existing
|
||||
? {
|
||||
...existing,
|
||||
@@ -134,7 +141,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
return;
|
||||
}
|
||||
if (!key.startsWith("subagent:")) {
|
||||
if (!isSubagentSessionKey(primaryKey)) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
@@ -311,12 +318,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
}
|
||||
|
||||
store[key] = next;
|
||||
store[primaryKey] = next;
|
||||
await saveSessionStore(storePath, store);
|
||||
const result: SessionsPatchResult = {
|
||||
ok: true,
|
||||
path: storePath,
|
||||
key,
|
||||
key: target.canonicalKey,
|
||||
entry: next,
|
||||
};
|
||||
respond(true, result, undefined);
|
||||
@@ -344,7 +351,17 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
const { storePath, store, entry } = loadSessionEntry(key);
|
||||
const cfg = loadConfig();
|
||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||
const storePath = target.storePath;
|
||||
const store = loadSessionStore(storePath);
|
||||
const primaryKey = target.storeKeys[0] ?? key;
|
||||
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
|
||||
store[primaryKey] = store[existingKey];
|
||||
delete store[existingKey];
|
||||
}
|
||||
const entry = store[primaryKey];
|
||||
const now = Date.now();
|
||||
const next: SessionEntry = {
|
||||
sessionId: randomUUID(),
|
||||
@@ -356,13 +373,17 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
model: entry?.model,
|
||||
contextTokens: entry?.contextTokens,
|
||||
sendPolicy: entry?.sendPolicy,
|
||||
lastChannel: entry?.lastChannel,
|
||||
lastProvider: entry?.lastProvider,
|
||||
lastTo: entry?.lastTo,
|
||||
skillsSnapshot: entry?.skillsSnapshot,
|
||||
};
|
||||
store[key] = next;
|
||||
store[primaryKey] = next;
|
||||
await saveSessionStore(storePath, store);
|
||||
respond(true, { ok: true, key, entry: next }, undefined);
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, key: target.canonicalKey, entry: next },
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
"sessions.delete": async ({ params, respond }) => {
|
||||
if (!validateSessionsDeleteParams(params)) {
|
||||
@@ -387,8 +408,10 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
|
||||
const mainKey = resolveMainSessionKey(loadConfig());
|
||||
if (key === mainKey) {
|
||||
const cfg = loadConfig();
|
||||
const mainKey = resolveMainSessionKey(cfg);
|
||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||
if (target.canonicalKey === mainKey) {
|
||||
respond(
|
||||
false,
|
||||
undefined,
|
||||
@@ -403,10 +426,18 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
const deleteTranscript =
|
||||
typeof p.deleteTranscript === "boolean" ? p.deleteTranscript : true;
|
||||
|
||||
const { storePath, store, entry } = loadSessionEntry(key);
|
||||
const storePath = target.storePath;
|
||||
const store = loadSessionStore(storePath);
|
||||
const primaryKey = target.storeKeys[0] ?? key;
|
||||
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
|
||||
store[primaryKey] = store[existingKey];
|
||||
delete store[existingKey];
|
||||
}
|
||||
const entry = store[primaryKey];
|
||||
const sessionId = entry?.sessionId;
|
||||
const existed = Boolean(store[key]);
|
||||
clearCommandLane(resolveEmbeddedSessionLane(key));
|
||||
const existed = Boolean(entry);
|
||||
clearCommandLane(resolveEmbeddedSessionLane(target.canonicalKey));
|
||||
if (sessionId && isEmbeddedPiRunActive(sessionId)) {
|
||||
abortEmbeddedPiRun(sessionId);
|
||||
const ended = await waitForEmbeddedPiRunEnd(sessionId, 15_000);
|
||||
@@ -422,7 +453,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (existed) delete store[key];
|
||||
if (existed) delete store[primaryKey];
|
||||
await saveSessionStore(storePath, store);
|
||||
|
||||
const archived: string[] = [];
|
||||
@@ -430,6 +461,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
for (const candidate of resolveSessionTranscriptCandidates(
|
||||
sessionId,
|
||||
storePath,
|
||||
target.agentId,
|
||||
)) {
|
||||
if (!fs.existsSync(candidate)) continue;
|
||||
try {
|
||||
@@ -440,7 +472,11 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
}
|
||||
|
||||
respond(true, { ok: true, key, deleted: existed, archived }, undefined);
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, key: target.canonicalKey, deleted: existed, archived },
|
||||
undefined,
|
||||
);
|
||||
},
|
||||
"sessions.compact": async ({ params, respond }) => {
|
||||
if (!validateSessionsCompactParams(params)) {
|
||||
@@ -470,12 +506,27 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
? Math.max(1, Math.floor(p.maxLines))
|
||||
: 400;
|
||||
|
||||
const { storePath, store, entry } = loadSessionEntry(key);
|
||||
const cfg = loadConfig();
|
||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||
const storePath = target.storePath;
|
||||
const store = loadSessionStore(storePath);
|
||||
const primaryKey = target.storeKeys[0] ?? key;
|
||||
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (existingKey && existingKey !== primaryKey && !store[primaryKey]) {
|
||||
store[primaryKey] = store[existingKey];
|
||||
delete store[existingKey];
|
||||
}
|
||||
const entry = store[primaryKey];
|
||||
const sessionId = entry?.sessionId;
|
||||
if (!sessionId) {
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, key, compacted: false, reason: "no sessionId" },
|
||||
{
|
||||
ok: true,
|
||||
key: target.canonicalKey,
|
||||
compacted: false,
|
||||
reason: "no sessionId",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
@@ -484,11 +535,17 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
const filePath = resolveSessionTranscriptCandidates(
|
||||
sessionId,
|
||||
storePath,
|
||||
target.agentId,
|
||||
).find((candidate) => fs.existsSync(candidate));
|
||||
if (!filePath) {
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, key, compacted: false, reason: "no transcript" },
|
||||
{
|
||||
ok: true,
|
||||
key: target.canonicalKey,
|
||||
compacted: false,
|
||||
reason: "no transcript",
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
@@ -499,7 +556,12 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
if (lines.length <= maxLines) {
|
||||
respond(
|
||||
true,
|
||||
{ ok: true, key, compacted: false, kept: lines.length },
|
||||
{
|
||||
ok: true,
|
||||
key: target.canonicalKey,
|
||||
compacted: false,
|
||||
kept: lines.length,
|
||||
},
|
||||
undefined,
|
||||
);
|
||||
return;
|
||||
@@ -509,11 +571,11 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
const keptLines = lines.slice(-maxLines);
|
||||
fs.writeFileSync(filePath, `${keptLines.join("\n")}\n`, "utf-8");
|
||||
|
||||
if (store[key]) {
|
||||
delete store[key].inputTokens;
|
||||
delete store[key].outputTokens;
|
||||
delete store[key].totalTokens;
|
||||
store[key].updatedAt = Date.now();
|
||||
if (store[primaryKey]) {
|
||||
delete store[primaryKey].inputTokens;
|
||||
delete store[primaryKey].outputTokens;
|
||||
delete store[primaryKey].totalTokens;
|
||||
store[primaryKey].updatedAt = Date.now();
|
||||
await saveSessionStore(storePath, store);
|
||||
}
|
||||
|
||||
@@ -521,7 +583,7 @@ export const sessionsHandlers: GatewayRequestHandlers = {
|
||||
true,
|
||||
{
|
||||
ok: true,
|
||||
key,
|
||||
key: target.canonicalKey,
|
||||
compacted: true,
|
||||
archived,
|
||||
kept: keptLines.length,
|
||||
|
||||
@@ -69,10 +69,10 @@ export type GatewayRequestContext = {
|
||||
findRunningWizard: () => string | null;
|
||||
purgeWizardSession: (id: string) => void;
|
||||
getRuntimeSnapshot: () => ProviderRuntimeSnapshot;
|
||||
startWhatsAppProvider: () => Promise<void>;
|
||||
stopWhatsAppProvider: () => Promise<void>;
|
||||
startWhatsAppProvider: (accountId?: string) => Promise<void>;
|
||||
stopWhatsAppProvider: (accountId?: string) => Promise<void>;
|
||||
stopTelegramProvider: () => Promise<void>;
|
||||
markWhatsAppLoggedOut: (cleared: boolean) => void;
|
||||
markWhatsAppLoggedOut: (cleared: boolean, accountId?: string) => void;
|
||||
wizardRunner: (
|
||||
opts: import("../../commands/onboard-types.js").OnboardOptions,
|
||||
runtime: import("../../runtime.js").RuntimeEnv,
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
import { loadConfig } from "../../config/config.js";
|
||||
import { defaultRuntime } from "../../runtime.js";
|
||||
import { resolveWhatsAppAccount } from "../../web/accounts.js";
|
||||
import { startWebLoginWithQr, waitForWebLogin } from "../../web/login-qr.js";
|
||||
import { logoutWeb } from "../../web/session.js";
|
||||
import {
|
||||
@@ -25,7 +27,11 @@ export const webHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await context.stopWhatsAppProvider();
|
||||
const accountId =
|
||||
typeof (params as { accountId?: unknown }).accountId === "string"
|
||||
? (params as { accountId?: string }).accountId
|
||||
: undefined;
|
||||
await context.stopWhatsAppProvider(accountId);
|
||||
const result = await startWebLoginWithQr({
|
||||
force: Boolean((params as { force?: boolean }).force),
|
||||
timeoutMs:
|
||||
@@ -33,6 +39,7 @@ export const webHandlers: GatewayRequestHandlers = {
|
||||
? (params as { timeoutMs?: number }).timeoutMs
|
||||
: undefined,
|
||||
verbose: Boolean((params as { verbose?: boolean }).verbose),
|
||||
accountId,
|
||||
});
|
||||
respond(true, result, undefined);
|
||||
} catch (err) {
|
||||
@@ -56,14 +63,19 @@ export const webHandlers: GatewayRequestHandlers = {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const accountId =
|
||||
typeof (params as { accountId?: unknown }).accountId === "string"
|
||||
? (params as { accountId?: string }).accountId
|
||||
: undefined;
|
||||
const result = await waitForWebLogin({
|
||||
timeoutMs:
|
||||
typeof (params as { timeoutMs?: unknown }).timeoutMs === "number"
|
||||
? (params as { timeoutMs?: number }).timeoutMs
|
||||
: undefined,
|
||||
accountId,
|
||||
});
|
||||
if (result.connected) {
|
||||
await context.startWhatsAppProvider();
|
||||
await context.startWhatsAppProvider(accountId);
|
||||
}
|
||||
respond(true, result, undefined);
|
||||
} catch (err) {
|
||||
@@ -74,11 +86,26 @@ export const webHandlers: GatewayRequestHandlers = {
|
||||
);
|
||||
}
|
||||
},
|
||||
"web.logout": async ({ respond, context }) => {
|
||||
"web.logout": async ({ params, respond, context }) => {
|
||||
try {
|
||||
await context.stopWhatsAppProvider();
|
||||
const cleared = await logoutWeb(defaultRuntime);
|
||||
context.markWhatsAppLoggedOut(cleared);
|
||||
const rawAccountId =
|
||||
params && typeof params === "object" && "accountId" in params
|
||||
? (params as { accountId?: unknown }).accountId
|
||||
: undefined;
|
||||
const accountId =
|
||||
typeof rawAccountId === "string" ? rawAccountId.trim() : "";
|
||||
const cfg = loadConfig();
|
||||
const account = resolveWhatsAppAccount({
|
||||
cfg,
|
||||
accountId: accountId || undefined,
|
||||
});
|
||||
await context.stopWhatsAppProvider(account.accountId);
|
||||
const cleared = await logoutWeb({
|
||||
authDir: account.authDir,
|
||||
isLegacyAuthDir: account.isLegacyAuthDir,
|
||||
runtime: defaultRuntime,
|
||||
});
|
||||
context.markWhatsAppLoggedOut(cleared, account.accountId);
|
||||
respond(true, { cleared }, undefined);
|
||||
} catch (err) {
|
||||
respond(
|
||||
|
||||
Reference in New Issue
Block a user