agiagent-dev 2026.1.32 → 2026.1.33
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/dist/agents/bash-tools.exec.js +17 -8
- package/dist/agents/system-prompt.js +17 -0
- package/dist/build-info.json +3 -3
- package/dist/canvas-host/a2ui/.bundle.hash +1 -1
- package/dist/cli/connect-cli/register.js +5 -1
- package/dist/config/plugin-auto-enable.js +14 -6
- package/dist/gateway/auth.d.ts +1 -0
- package/dist/gateway/auth.js +1 -1
- package/dist/gateway/hosted-agent.d.ts +21 -0
- package/dist/gateway/hosted-agent.js +96 -0
- package/dist/gateway/hosted-db.d.ts +2 -0
- package/dist/gateway/hosted-db.js +3 -0
- package/dist/gateway/hosted-telegram.d.ts +27 -0
- package/dist/gateway/hosted-telegram.js +163 -0
- package/dist/gateway/hosted-whatsapp.d.ts +44 -0
- package/dist/gateway/hosted-whatsapp.js +247 -0
- package/dist/gateway/server/ws-connection/message-handler.js +32 -1
- package/dist/gateway/server.impl.js +1 -1
- package/dist/node-host/runner.js +3 -1
- package/package.json +1 -1
|
@@ -768,12 +768,16 @@ export function createExecTool(defaults) {
|
|
|
768
768
|
// Fall back to requiring approval if node approvals cannot be fetched.
|
|
769
769
|
}
|
|
770
770
|
}
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
771
|
+
// In hosted mode, skip approval entirely - commands are pre-authorized by the gateway
|
|
772
|
+
const hostedAutoApprove = process.env.AGIAGENT_HOSTED_MODE === "1";
|
|
773
|
+
const requiresAsk = hostedAutoApprove
|
|
774
|
+
? false
|
|
775
|
+
: requiresExecApproval({
|
|
776
|
+
ask: hostAsk,
|
|
777
|
+
security: hostSecurity,
|
|
778
|
+
analysisOk,
|
|
779
|
+
allowlistSatisfied,
|
|
780
|
+
});
|
|
777
781
|
const commandText = params.command;
|
|
778
782
|
const invokeTimeoutMs = Math.max(10_000, (typeof params.timeout === "number" ? params.timeout : defaultTimeoutSec) * 1000 + 5_000);
|
|
779
783
|
const buildInvokeParams = (approvedByAsk, approvalDecision, runId) => ({
|
|
@@ -782,7 +786,9 @@ export function createExecTool(defaults) {
|
|
|
782
786
|
params: {
|
|
783
787
|
command: argv,
|
|
784
788
|
rawCommand: params.command,
|
|
785
|
-
cwd
|
|
789
|
+
// Don't pass gateway's cwd to remote nodes - use null to let node use its own cwd
|
|
790
|
+
// The gateway's cwd (e.g., /app or /data on Fly.io) doesn't exist on user's machine
|
|
791
|
+
cwd: params.workdir?.trim() || null,
|
|
786
792
|
env: nodeEnv,
|
|
787
793
|
timeoutMs: typeof params.timeout === "number" ? params.timeout * 1000 : undefined,
|
|
788
794
|
agentId,
|
|
@@ -790,6 +796,8 @@ export function createExecTool(defaults) {
|
|
|
790
796
|
approved: approvedByAsk,
|
|
791
797
|
approvalDecision: approvalDecision ?? undefined,
|
|
792
798
|
runId: runId ?? undefined,
|
|
799
|
+
// Tell node to skip macOS app exec host and use direct spawn in hosted mode
|
|
800
|
+
hostedMode: process.env.AGIAGENT_HOSTED_MODE === "1" ? true : undefined,
|
|
793
801
|
},
|
|
794
802
|
idempotencyKey: crypto.randomUUID(),
|
|
795
803
|
});
|
|
@@ -893,7 +901,8 @@ export function createExecTool(defaults) {
|
|
|
893
901
|
};
|
|
894
902
|
}
|
|
895
903
|
const startedAt = Date.now();
|
|
896
|
-
|
|
904
|
+
// In hosted mode, hostedAutoApprove is true, so we send approved=true to the node
|
|
905
|
+
const raw = await callGatewayTool("node.invoke", { timeoutMs: invokeTimeoutMs }, buildInvokeParams(hostedAutoApprove, hostedAutoApprove ? "allow-once" : null));
|
|
897
906
|
const payload = raw && typeof raw === "object" ? raw.payload : undefined;
|
|
898
907
|
const payloadObj = payload && typeof payload === "object" ? payload : {};
|
|
899
908
|
const stdout = typeof payloadObj.stdout === "string" ? payloadObj.stdout : "";
|
|
@@ -314,6 +314,23 @@ export function buildAgentSystemPrompt(params) {
|
|
|
314
314
|
"",
|
|
315
315
|
...skillsSection,
|
|
316
316
|
...memorySection,
|
|
317
|
+
// File type guidance for document handling
|
|
318
|
+
!isMinimal ? "## File Type Guidance" : "",
|
|
319
|
+
!isMinimal
|
|
320
|
+
? [
|
|
321
|
+
"When working with specific file types, ALWAYS read the corresponding skill BEFORE any editing:",
|
|
322
|
+
"",
|
|
323
|
+
"### Word Documents (.docx)",
|
|
324
|
+
"1. Find the `docx` skill in <available_skills> above",
|
|
325
|
+
`2. Use \`${readToolName}\` to read the skill's SKILL.md at its <location>`,
|
|
326
|
+
"3. Follow ALL instructions in that skill - it contains CRITICAL rules",
|
|
327
|
+
"4. Use python-docx (NOT raw XML/zipfile manipulation)",
|
|
328
|
+
"5. Count paragraphs/bullets before AND after editing - counts must match unless user requested reduction",
|
|
329
|
+
"",
|
|
330
|
+
"NEVER skip reading the skill. NEVER use zipfile+ElementTree for .docx files.",
|
|
331
|
+
].join("\n")
|
|
332
|
+
: "",
|
|
333
|
+
!isMinimal ? "" : "",
|
|
317
334
|
// Skip self-update for subagent/none modes
|
|
318
335
|
hasGateway && !isMinimal ? "## AGIAgent Self-Update" : "",
|
|
319
336
|
hasGateway && !isMinimal
|
package/dist/build-info.json
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
|
|
1
|
+
48ae5a7be920c7ca3c20f3800f4b9c0a40fa52956e24f6fbf8ec668ac4b14679
|
|
@@ -44,7 +44,11 @@ ${theme.muted("Your WhatsApp messages will trigger AI that runs commands on this
|
|
|
44
44
|
if (gatewayUrl.includes("://")) {
|
|
45
45
|
const url = new URL(gatewayUrl);
|
|
46
46
|
host = url.hostname;
|
|
47
|
-
port = url.port
|
|
47
|
+
port = url.port
|
|
48
|
+
? parseInt(url.port, 10)
|
|
49
|
+
: url.protocol === "wss:" || url.protocol === "https:"
|
|
50
|
+
? 443
|
|
51
|
+
: 80;
|
|
48
52
|
useTls = url.protocol === "wss:" || url.protocol === "https:";
|
|
49
53
|
}
|
|
50
54
|
else if (gatewayUrl.includes(":")) {
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import { normalizeProviderId } from "../agents/model-selection.js";
|
|
2
2
|
import { getChannelPluginCatalogEntry, listChannelPluginCatalogEntries, } from "../channels/plugins/catalog.js";
|
|
3
|
-
import {
|
|
3
|
+
import { CHAT_CHANNEL_ORDER, getChatChannelMeta, normalizeChatChannelId, } from "../channels/registry.js";
|
|
4
4
|
import { hasAnyWhatsAppAuth } from "../web/accounts.js";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
// Core/built-in channels are part of the main codebase and don't need plugin entries.
|
|
6
|
+
// Only extension channels (actual plugins) should be auto-enabled via plugins.entries.
|
|
7
|
+
const CORE_CHANNEL_IDS = new Set(CHAT_CHANNEL_ORDER);
|
|
8
|
+
// Extension channel plugins that can be auto-enabled
|
|
9
|
+
const CHANNEL_PLUGIN_IDS = Array.from(new Set([...listChannelPluginCatalogEntries().map((entry) => entry.id)]));
|
|
9
10
|
const PROVIDER_PLUGIN_IDS = [
|
|
10
11
|
{ pluginId: "google-antigravity-auth", providerId: "google-antigravity" },
|
|
11
12
|
{ pluginId: "google-gemini-cli-auth", providerId: "google-gemini-cli" },
|
|
@@ -245,13 +246,20 @@ function resolveConfiguredPlugins(cfg, env) {
|
|
|
245
246
|
if (key === "defaults") {
|
|
246
247
|
continue;
|
|
247
248
|
}
|
|
248
|
-
|
|
249
|
+
// Only add extension channels, not core channels
|
|
250
|
+
if (!CORE_CHANNEL_IDS.has(key)) {
|
|
251
|
+
channelIds.add(key);
|
|
252
|
+
}
|
|
249
253
|
}
|
|
250
254
|
}
|
|
251
255
|
for (const channelId of channelIds) {
|
|
252
256
|
if (!channelId) {
|
|
253
257
|
continue;
|
|
254
258
|
}
|
|
259
|
+
// Skip core channels - they're built-in and don't need plugin entries
|
|
260
|
+
if (CORE_CHANNEL_IDS.has(channelId)) {
|
|
261
|
+
continue;
|
|
262
|
+
}
|
|
255
263
|
if (isChannelConfigured(cfg, channelId, env)) {
|
|
256
264
|
changes.push({
|
|
257
265
|
pluginId: channelId,
|
package/dist/gateway/auth.d.ts
CHANGED
package/dist/gateway/auth.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { timingSafeEqual } from "node:crypto";
|
|
2
2
|
import { readTailscaleWhoisIdentity } from "../infra/tailscale.js";
|
|
3
|
-
import { isTrustedProxyAddress, parseForwardedForClientIp, resolveGatewayClientIp } from "./net.js";
|
|
4
3
|
import { isHostedMode, validateHostedToken } from "./hosted-db.js";
|
|
4
|
+
import { isTrustedProxyAddress, parseForwardedForClientIp, resolveGatewayClientIp } from "./net.js";
|
|
5
5
|
function safeEqual(a, b) {
|
|
6
6
|
if (a.length !== b.length) {
|
|
7
7
|
return false;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode AI agent processing.
|
|
3
|
+
*
|
|
4
|
+
* Handles running the AI agent for hosted users using the existing
|
|
5
|
+
* auto-reply infrastructure with full tool support.
|
|
6
|
+
*/
|
|
7
|
+
export type HostedAgentReplyParams = {
|
|
8
|
+
userId: string;
|
|
9
|
+
userName: string;
|
|
10
|
+
nodeId: string;
|
|
11
|
+
messageText: string;
|
|
12
|
+
senderJid: string;
|
|
13
|
+
chatJid: string;
|
|
14
|
+
isGroup: boolean;
|
|
15
|
+
sendNodeEvent: (event: string, payload: unknown) => void;
|
|
16
|
+
};
|
|
17
|
+
/**
|
|
18
|
+
* Process a message through the AI agent for a hosted user.
|
|
19
|
+
* Uses the full agent infrastructure with tool support.
|
|
20
|
+
*/
|
|
21
|
+
export declare function runHostedAgentReply(params: HostedAgentReplyParams): Promise<string>;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode AI agent processing.
|
|
3
|
+
*
|
|
4
|
+
* Handles running the AI agent for hosted users using the existing
|
|
5
|
+
* auto-reply infrastructure with full tool support.
|
|
6
|
+
*/
|
|
7
|
+
import { getReplyFromConfig } from "../auto-reply/reply.js";
|
|
8
|
+
import { finalizeInboundContext } from "../auto-reply/reply/inbound-context.js";
|
|
9
|
+
import { loadConfig } from "../config/config.js";
|
|
10
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
11
|
+
import { jidToE164 } from "../utils.js";
|
|
12
|
+
const log = createSubsystemLogger("hosted-agent");
|
|
13
|
+
/**
|
|
14
|
+
* Process a message through the AI agent for a hosted user.
|
|
15
|
+
* Uses the full agent infrastructure with tool support.
|
|
16
|
+
*/
|
|
17
|
+
export async function runHostedAgentReply(params) {
|
|
18
|
+
const { userId, userName, nodeId, messageText, senderJid, chatJid, isGroup, sendNodeEvent } = params;
|
|
19
|
+
log.info(`Processing message for user ${userName}: "${messageText.slice(0, 50)}..."`);
|
|
20
|
+
try {
|
|
21
|
+
const cfg = loadConfig();
|
|
22
|
+
// Create a per-user session key to isolate conversations between hosted users
|
|
23
|
+
// Format: hosted:<userId>:<chatJid> for full isolation
|
|
24
|
+
const userSessionKey = `hosted:${userId}:${chatJid}`;
|
|
25
|
+
const senderE164 = jidToE164(senderJid) ?? senderJid;
|
|
26
|
+
log.info(`Routing to session ${userSessionKey} for user ${userName}`);
|
|
27
|
+
// Build the message context using the existing infrastructure
|
|
28
|
+
const ctx = finalizeInboundContext({
|
|
29
|
+
Body: messageText,
|
|
30
|
+
RawBody: messageText,
|
|
31
|
+
CommandBody: messageText,
|
|
32
|
+
BodyForAgent: messageText,
|
|
33
|
+
BodyForCommands: messageText,
|
|
34
|
+
From: senderJid,
|
|
35
|
+
To: userId,
|
|
36
|
+
SessionKey: userSessionKey,
|
|
37
|
+
AccountId: userId,
|
|
38
|
+
MessageSid: `hosted-${Date.now()}`,
|
|
39
|
+
ChatType: isGroup ? "group" : "dm",
|
|
40
|
+
ConversationLabel: chatJid,
|
|
41
|
+
SenderName: userName,
|
|
42
|
+
SenderId: senderJid,
|
|
43
|
+
SenderE164: senderE164,
|
|
44
|
+
// Allow commands for hosted users
|
|
45
|
+
CommandAuthorized: true,
|
|
46
|
+
Provider: "telegram",
|
|
47
|
+
Surface: "telegram",
|
|
48
|
+
OriginatingChannel: "telegram",
|
|
49
|
+
OriginatingTo: chatJid,
|
|
50
|
+
});
|
|
51
|
+
// Create config override to bind exec to the user's connected node
|
|
52
|
+
// This ensures commands run on the user's device, not on the gateway
|
|
53
|
+
const configOverride = {
|
|
54
|
+
...cfg,
|
|
55
|
+
tools: {
|
|
56
|
+
...cfg.tools,
|
|
57
|
+
exec: {
|
|
58
|
+
...cfg.tools?.exec,
|
|
59
|
+
host: "node",
|
|
60
|
+
node: nodeId,
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
// Get the AI reply using the full agent infrastructure
|
|
65
|
+
// Pass the config override so exec is bound to the user's node
|
|
66
|
+
const result = await getReplyFromConfig(ctx, undefined, configOverride);
|
|
67
|
+
if (!result) {
|
|
68
|
+
log.warn(`No reply generated for user ${userName}`);
|
|
69
|
+
return "";
|
|
70
|
+
}
|
|
71
|
+
// Extract text from reply payload(s)
|
|
72
|
+
const replyText = extractReplyText(result);
|
|
73
|
+
log.info(`Generated reply for user ${userName}: "${replyText.slice(0, 50)}..."`);
|
|
74
|
+
return replyText;
|
|
75
|
+
}
|
|
76
|
+
catch (err) {
|
|
77
|
+
log.error(`Failed to generate reply for user ${userName}`, { error: String(err) });
|
|
78
|
+
sendNodeEvent("agent.error", {
|
|
79
|
+
userId,
|
|
80
|
+
error: String(err),
|
|
81
|
+
});
|
|
82
|
+
return `Sorry, I encountered an error: ${String(err).slice(0, 100)}`;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* Extract text from reply payload(s).
|
|
87
|
+
*/
|
|
88
|
+
function extractReplyText(result) {
|
|
89
|
+
if (Array.isArray(result)) {
|
|
90
|
+
return result
|
|
91
|
+
.map((r) => r.text ?? "")
|
|
92
|
+
.filter(Boolean)
|
|
93
|
+
.join("\n");
|
|
94
|
+
}
|
|
95
|
+
return result.text ?? "";
|
|
96
|
+
}
|
|
@@ -12,6 +12,7 @@ export type ConnectionToken = {
|
|
|
12
12
|
userId: string;
|
|
13
13
|
token: string;
|
|
14
14
|
whatsappAccountId: string | null;
|
|
15
|
+
telegramBotToken: string | null;
|
|
15
16
|
createdAt: Date;
|
|
16
17
|
lastConnectedAt: Date | null;
|
|
17
18
|
};
|
|
@@ -52,6 +53,7 @@ export declare function validateHostedToken(token: string): Promise<{
|
|
|
52
53
|
userName: string;
|
|
53
54
|
tokenId: string;
|
|
54
55
|
whatsappAccountId: string | null;
|
|
56
|
+
telegramBotToken: string | null;
|
|
55
57
|
} | null>;
|
|
56
58
|
/**
|
|
57
59
|
* Register a node for a hosted user.
|
|
@@ -44,6 +44,7 @@ export async function initHostedDb() {
|
|
|
44
44
|
t.user_id,
|
|
45
45
|
t.token,
|
|
46
46
|
t.whatsapp_account_id,
|
|
47
|
+
t.telegram_bot_token,
|
|
47
48
|
t.created_at as token_created_at,
|
|
48
49
|
t.last_connected_at,
|
|
49
50
|
u.id as user_id,
|
|
@@ -69,6 +70,7 @@ export async function initHostedDb() {
|
|
|
69
70
|
userId: row.user_id,
|
|
70
71
|
token: row.token,
|
|
71
72
|
whatsappAccountId: row.whatsapp_account_id,
|
|
73
|
+
telegramBotToken: row.telegram_bot_token,
|
|
72
74
|
createdAt: row.token_created_at,
|
|
73
75
|
lastConnectedAt: row.last_connected_at,
|
|
74
76
|
},
|
|
@@ -128,6 +130,7 @@ export async function validateHostedToken(token) {
|
|
|
128
130
|
userName: result.user.name,
|
|
129
131
|
tokenId: result.tokenRecord.id,
|
|
130
132
|
whatsappAccountId: result.tokenRecord.whatsappAccountId,
|
|
133
|
+
telegramBotToken: result.tokenRecord.telegramBotToken,
|
|
131
134
|
};
|
|
132
135
|
}
|
|
133
136
|
// In-memory mapping of userId -> nodeId for WhatsApp event routing
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode Telegram handling.
|
|
3
|
+
*
|
|
4
|
+
* Manages Telegram bots for hosted users:
|
|
5
|
+
* 1. Starts Telegram bot with user's token
|
|
6
|
+
* 2. Handles incoming messages
|
|
7
|
+
* 3. Routes to AI agent
|
|
8
|
+
* 4. Sends responses
|
|
9
|
+
*/
|
|
10
|
+
/**
|
|
11
|
+
* Start a Telegram bot for a hosted user.
|
|
12
|
+
*/
|
|
13
|
+
export declare function startHostedTelegramBot(params: {
|
|
14
|
+
userId: string;
|
|
15
|
+
userName: string;
|
|
16
|
+
nodeId: string;
|
|
17
|
+
botToken: string;
|
|
18
|
+
sendEvent: (event: string, payload: unknown) => void;
|
|
19
|
+
}): Promise<void>;
|
|
20
|
+
/**
|
|
21
|
+
* Stop a Telegram bot for a hosted user.
|
|
22
|
+
*/
|
|
23
|
+
export declare function stopHostedTelegramBot(userId: string): Promise<void>;
|
|
24
|
+
/**
|
|
25
|
+
* Check if a hosted user has an active Telegram bot.
|
|
26
|
+
*/
|
|
27
|
+
export declare function isHostedTelegramBotRunning(userId: string): boolean;
|
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode Telegram handling.
|
|
3
|
+
*
|
|
4
|
+
* Manages Telegram bots for hosted users:
|
|
5
|
+
* 1. Starts Telegram bot with user's token
|
|
6
|
+
* 2. Handles incoming messages
|
|
7
|
+
* 3. Routes to AI agent
|
|
8
|
+
* 4. Sends responses
|
|
9
|
+
*/
|
|
10
|
+
import { Bot } from "grammy";
|
|
11
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
12
|
+
import { runHostedAgentReply } from "./hosted-agent.js";
|
|
13
|
+
import { isHostedMode } from "./hosted-db.js";
|
|
14
|
+
const log = createSubsystemLogger("hosted-telegram");
|
|
15
|
+
const activeHostedBots = new Map();
|
|
16
|
+
/**
|
|
17
|
+
* Start a Telegram bot for a hosted user.
|
|
18
|
+
*/
|
|
19
|
+
export async function startHostedTelegramBot(params) {
|
|
20
|
+
if (!isHostedMode()) {
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
const { userId, userName, nodeId, botToken, sendEvent } = params;
|
|
24
|
+
log.info(`Starting Telegram bot for hosted user ${userName} (${userId})`);
|
|
25
|
+
// Check if already has an active bot
|
|
26
|
+
const existing = activeHostedBots.get(userId);
|
|
27
|
+
if (existing?.running) {
|
|
28
|
+
log.info(`User ${userName} already has an active Telegram bot`);
|
|
29
|
+
// Update node reference
|
|
30
|
+
existing.nodeId = nodeId;
|
|
31
|
+
existing.sendNodeEvent = sendEvent;
|
|
32
|
+
sendEvent("telegram.connected", {
|
|
33
|
+
userId,
|
|
34
|
+
message: "Telegram bot already running",
|
|
35
|
+
});
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
try {
|
|
39
|
+
// Create the bot
|
|
40
|
+
const bot = new Bot(botToken);
|
|
41
|
+
const session = {
|
|
42
|
+
bot,
|
|
43
|
+
running: false,
|
|
44
|
+
startedAt: Date.now(),
|
|
45
|
+
userId,
|
|
46
|
+
userName,
|
|
47
|
+
nodeId,
|
|
48
|
+
sendNodeEvent: sendEvent,
|
|
49
|
+
};
|
|
50
|
+
// Set up message handler
|
|
51
|
+
bot.on("message:text", async (ctx) => {
|
|
52
|
+
const text = ctx.message.text;
|
|
53
|
+
const chatId = ctx.chat.id;
|
|
54
|
+
const isGroup = ctx.chat.type === "group" || ctx.chat.type === "supergroup";
|
|
55
|
+
const senderId = ctx.from?.id?.toString() ?? "unknown";
|
|
56
|
+
const senderName = ctx.from?.first_name ?? ctx.from?.username ?? "User";
|
|
57
|
+
log.info(`Telegram message from ${senderName} for user ${userName}: ${text.slice(0, 50)}...`);
|
|
58
|
+
// Notify node
|
|
59
|
+
session.sendNodeEvent("telegram.message.received", {
|
|
60
|
+
userId,
|
|
61
|
+
from: senderId,
|
|
62
|
+
text: text.slice(0, 100),
|
|
63
|
+
isGroup,
|
|
64
|
+
});
|
|
65
|
+
try {
|
|
66
|
+
// Send typing indicator
|
|
67
|
+
await ctx.api.sendChatAction(chatId, "typing");
|
|
68
|
+
// Process through AI agent
|
|
69
|
+
const reply = await runHostedAgentReply({
|
|
70
|
+
userId,
|
|
71
|
+
userName,
|
|
72
|
+
nodeId: session.nodeId,
|
|
73
|
+
messageText: text,
|
|
74
|
+
senderJid: senderId,
|
|
75
|
+
chatJid: chatId.toString(),
|
|
76
|
+
isGroup,
|
|
77
|
+
sendNodeEvent: session.sendNodeEvent,
|
|
78
|
+
});
|
|
79
|
+
if (reply && reply.trim()) {
|
|
80
|
+
// Send reply
|
|
81
|
+
await ctx.reply(reply, { parse_mode: "HTML" }).catch(async () => {
|
|
82
|
+
// Fallback to plain text if HTML fails
|
|
83
|
+
await ctx.reply(reply);
|
|
84
|
+
});
|
|
85
|
+
log.info(`Telegram reply sent to ${chatId} for user ${userName}`);
|
|
86
|
+
session.sendNodeEvent("telegram.message.sent", {
|
|
87
|
+
userId,
|
|
88
|
+
to: chatId,
|
|
89
|
+
text: reply.slice(0, 100),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
catch (err) {
|
|
94
|
+
log.error(`Failed to process Telegram message for user ${userName}`, {
|
|
95
|
+
error: String(err),
|
|
96
|
+
});
|
|
97
|
+
session.sendNodeEvent("telegram.message.error", {
|
|
98
|
+
userId,
|
|
99
|
+
error: String(err),
|
|
100
|
+
});
|
|
101
|
+
// Send error message to user
|
|
102
|
+
try {
|
|
103
|
+
await ctx.reply(`Sorry, I encountered an error: ${String(err).slice(0, 100)}`);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Ignore send errors
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
// Handle errors
|
|
111
|
+
bot.catch((err) => {
|
|
112
|
+
log.error(`Telegram bot error for user ${userName}`, { error: String(err.message) });
|
|
113
|
+
});
|
|
114
|
+
// Store session
|
|
115
|
+
activeHostedBots.set(userId, session);
|
|
116
|
+
// Start the bot (polling mode)
|
|
117
|
+
log.info(`Starting Telegram polling for user ${userName}`);
|
|
118
|
+
// Get bot info first
|
|
119
|
+
const me = await bot.api.getMe();
|
|
120
|
+
log.info(`Telegram bot @${me.username} started for user ${userName}`);
|
|
121
|
+
// Start polling in background
|
|
122
|
+
bot.start({
|
|
123
|
+
onStart: () => {
|
|
124
|
+
session.running = true;
|
|
125
|
+
log.info(`Telegram bot polling started for user ${userName}`);
|
|
126
|
+
sendEvent("telegram.connected", {
|
|
127
|
+
userId,
|
|
128
|
+
botUsername: me.username,
|
|
129
|
+
message: `Telegram bot @${me.username} is ready! Send a message to the bot.`,
|
|
130
|
+
});
|
|
131
|
+
},
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
catch (err) {
|
|
135
|
+
log.error(`Failed to start Telegram bot for user ${userName}`, { error: String(err) });
|
|
136
|
+
sendEvent("telegram.error", {
|
|
137
|
+
userId,
|
|
138
|
+
error: `Failed to start Telegram bot: ${String(err)}`,
|
|
139
|
+
});
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Stop a Telegram bot for a hosted user.
|
|
144
|
+
*/
|
|
145
|
+
export async function stopHostedTelegramBot(userId) {
|
|
146
|
+
const session = activeHostedBots.get(userId);
|
|
147
|
+
if (session?.bot) {
|
|
148
|
+
try {
|
|
149
|
+
await session.bot.stop();
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// Ignore stop errors
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
activeHostedBots.delete(userId);
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Check if a hosted user has an active Telegram bot.
|
|
159
|
+
*/
|
|
160
|
+
export function isHostedTelegramBotRunning(userId) {
|
|
161
|
+
const session = activeHostedBots.get(userId);
|
|
162
|
+
return session?.running ?? false;
|
|
163
|
+
}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode WhatsApp handling.
|
|
3
|
+
*
|
|
4
|
+
* When a hosted user's node connects, this module handles:
|
|
5
|
+
* 1. Starting a WhatsApp session for the user
|
|
6
|
+
* 2. Sending QR code events to their connected node
|
|
7
|
+
* 3. Monitoring incoming messages
|
|
8
|
+
* 4. Processing messages through the AI agent
|
|
9
|
+
* 5. Sending responses back via WhatsApp
|
|
10
|
+
*/
|
|
11
|
+
import { createWaSocket } from "../web/session.js";
|
|
12
|
+
type HostedSession = {
|
|
13
|
+
sock: Awaited<ReturnType<typeof createWaSocket>> | null;
|
|
14
|
+
connected: boolean;
|
|
15
|
+
startedAt: number;
|
|
16
|
+
monitoring: boolean;
|
|
17
|
+
nodeId: string;
|
|
18
|
+
userId: string;
|
|
19
|
+
userName: string;
|
|
20
|
+
sendNodeEvent: (event: string, payload: unknown) => void;
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Start WhatsApp for a hosted user - handles pairing and message monitoring.
|
|
24
|
+
* Called when a hosted user's node connects to the gateway.
|
|
25
|
+
*/
|
|
26
|
+
export declare function startHostedWhatsAppPairing(params: {
|
|
27
|
+
userId: string;
|
|
28
|
+
userName: string;
|
|
29
|
+
nodeId: string;
|
|
30
|
+
sendEvent: (event: string, payload: unknown) => void;
|
|
31
|
+
}): Promise<void>;
|
|
32
|
+
/**
|
|
33
|
+
* Check if a hosted user has WhatsApp connected.
|
|
34
|
+
*/
|
|
35
|
+
export declare function isHostedUserWhatsAppConnected(userId: string): Promise<boolean>;
|
|
36
|
+
/**
|
|
37
|
+
* Get an active session for a hosted user.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getHostedSession(userId: string): HostedSession | undefined;
|
|
40
|
+
/**
|
|
41
|
+
* Clean up WhatsApp session for a hosted user.
|
|
42
|
+
*/
|
|
43
|
+
export declare function cleanupHostedWhatsAppSession(userId: string): void;
|
|
44
|
+
export {};
|
|
@@ -0,0 +1,247 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Hosted mode WhatsApp handling.
|
|
3
|
+
*
|
|
4
|
+
* When a hosted user's node connects, this module handles:
|
|
5
|
+
* 1. Starting a WhatsApp session for the user
|
|
6
|
+
* 2. Sending QR code events to their connected node
|
|
7
|
+
* 3. Monitoring incoming messages
|
|
8
|
+
* 4. Processing messages through the AI agent
|
|
9
|
+
* 5. Sending responses back via WhatsApp
|
|
10
|
+
*/
|
|
11
|
+
import { createSubsystemLogger } from "../logging/subsystem.js";
|
|
12
|
+
import { resolveHostedWhatsAppAuthDir } from "../web/accounts.js";
|
|
13
|
+
import { extractText } from "../web/inbound/extract.js";
|
|
14
|
+
import { sendMessageWhatsApp } from "../web/outbound.js";
|
|
15
|
+
import { createWaSocket, webAuthExists, readWebSelfId } from "../web/session.js";
|
|
16
|
+
import { runHostedAgentReply } from "./hosted-agent.js";
|
|
17
|
+
import { isHostedMode } from "./hosted-db.js";
|
|
18
|
+
const log = createSubsystemLogger("hosted-whatsapp");
|
|
19
|
+
const activeHostedSessions = new Map();
|
|
20
|
+
/**
|
|
21
|
+
* Start WhatsApp for a hosted user - handles pairing and message monitoring.
|
|
22
|
+
* Called when a hosted user's node connects to the gateway.
|
|
23
|
+
*/
|
|
24
|
+
export async function startHostedWhatsAppPairing(params) {
|
|
25
|
+
if (!isHostedMode()) {
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
const { userId, userName, nodeId, sendEvent } = params;
|
|
29
|
+
const authDir = resolveHostedWhatsAppAuthDir(userId);
|
|
30
|
+
log.info(`Starting WhatsApp for hosted user ${userName} (${userId})`);
|
|
31
|
+
// Check if already has an active session
|
|
32
|
+
const existing = activeHostedSessions.get(userId);
|
|
33
|
+
if (existing?.sock && existing.connected) {
|
|
34
|
+
log.info(`User ${userName} already has an active WhatsApp session`);
|
|
35
|
+
sendEvent("whatsapp.connected", {
|
|
36
|
+
userId,
|
|
37
|
+
message: "WhatsApp session already active",
|
|
38
|
+
});
|
|
39
|
+
// Update node reference in case of reconnection
|
|
40
|
+
existing.nodeId = nodeId;
|
|
41
|
+
existing.sendNodeEvent = sendEvent;
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
// Check if has stored auth (already linked)
|
|
45
|
+
const hasAuth = await webAuthExists(authDir);
|
|
46
|
+
const selfId = hasAuth ? readWebSelfId(authDir) : { jid: null, e164: null };
|
|
47
|
+
// Start or resume WhatsApp connection
|
|
48
|
+
try {
|
|
49
|
+
log.info(`Creating WhatsApp socket for user ${userName}`);
|
|
50
|
+
const sock = await createWaSocket(false, false, {
|
|
51
|
+
authDir,
|
|
52
|
+
onQr: (qr) => {
|
|
53
|
+
log.info(`QR code received for user ${userName}, sending to node ${nodeId}`);
|
|
54
|
+
sendEvent("whatsapp.qr", { qr, userId });
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
const session = {
|
|
58
|
+
sock,
|
|
59
|
+
connected: false,
|
|
60
|
+
startedAt: Date.now(),
|
|
61
|
+
monitoring: false,
|
|
62
|
+
nodeId,
|
|
63
|
+
userId,
|
|
64
|
+
userName,
|
|
65
|
+
sendNodeEvent: sendEvent,
|
|
66
|
+
};
|
|
67
|
+
activeHostedSessions.set(userId, session);
|
|
68
|
+
// Handle connection updates
|
|
69
|
+
sock.ev.on("connection.update", (update) => {
|
|
70
|
+
if (update.connection === "open") {
|
|
71
|
+
log.info(`WhatsApp connected for user ${userName}`);
|
|
72
|
+
session.connected = true;
|
|
73
|
+
// Read the linked phone number
|
|
74
|
+
const currentSelfId = readWebSelfId(authDir);
|
|
75
|
+
sendEvent("whatsapp.connected", {
|
|
76
|
+
userId,
|
|
77
|
+
phone: currentSelfId.e164 ?? currentSelfId.jid,
|
|
78
|
+
message: "WhatsApp connected successfully!",
|
|
79
|
+
});
|
|
80
|
+
// Start message monitoring if not already
|
|
81
|
+
if (!session.monitoring) {
|
|
82
|
+
session.monitoring = true;
|
|
83
|
+
startMessageMonitoring(session, authDir);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
else if (update.connection === "close") {
|
|
87
|
+
log.info(`WhatsApp connection closed for user ${userName}`);
|
|
88
|
+
session.connected = false;
|
|
89
|
+
session.monitoring = false;
|
|
90
|
+
// Don't delete session - allow reconnection
|
|
91
|
+
}
|
|
92
|
+
});
|
|
93
|
+
// If already authenticated, connection should open automatically
|
|
94
|
+
if (selfId.jid) {
|
|
95
|
+
log.info(`User ${userName} already has WhatsApp linked: ${selfId.e164 ?? selfId.jid}`);
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
catch (err) {
|
|
99
|
+
log.error(`Failed to start WhatsApp for user ${userName}`, { error: String(err) });
|
|
100
|
+
sendEvent("whatsapp.error", {
|
|
101
|
+
userId,
|
|
102
|
+
error: `Failed to start WhatsApp: ${String(err)}`,
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Start monitoring WhatsApp messages for a hosted user.
|
|
108
|
+
*/
|
|
109
|
+
function startMessageMonitoring(session, authDir) {
|
|
110
|
+
const { sock, userId, userName, nodeId, sendNodeEvent } = session;
|
|
111
|
+
if (!sock) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
log.info(`Starting message monitoring for user ${userName}`);
|
|
115
|
+
// Get the user's own JID to filter out own messages
|
|
116
|
+
const selfId = readWebSelfId(authDir);
|
|
117
|
+
const ownJid = selfId.jid;
|
|
118
|
+
sock.ev.on("messages.upsert", async (upsert) => {
|
|
119
|
+
if (upsert.type !== "notify") {
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
for (const msg of upsert.messages) {
|
|
123
|
+
try {
|
|
124
|
+
await handleIncomingMessage(session, msg, ownJid);
|
|
125
|
+
}
|
|
126
|
+
catch (err) {
|
|
127
|
+
log.error(`Error handling message for user ${userName}`, { error: String(err) });
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
});
|
|
131
|
+
log.info(`Message monitoring started for user ${userName}`);
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Handle an incoming WhatsApp message for a hosted user.
|
|
135
|
+
*/
|
|
136
|
+
async function handleIncomingMessage(session, msg, ownJid) {
|
|
137
|
+
const { sock, userId, userName, nodeId, sendNodeEvent } = session;
|
|
138
|
+
if (!sock) {
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
// Skip if no message content or key
|
|
142
|
+
if (!msg.message || !msg.key) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
// Skip own messages (echo)
|
|
146
|
+
const fromMe = msg.key.fromMe;
|
|
147
|
+
if (fromMe) {
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
// Extract message text
|
|
151
|
+
const text = extractText(msg.message);
|
|
152
|
+
if (!text || !text.trim()) {
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
// Get sender info
|
|
156
|
+
const remoteJid = msg.key.remoteJid;
|
|
157
|
+
if (!remoteJid) {
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
// Determine if it's a group or DM
|
|
161
|
+
const isGroup = remoteJid.endsWith("@g.us");
|
|
162
|
+
const senderJid = isGroup ? (msg.key.participant ?? remoteJid) : remoteJid;
|
|
163
|
+
log.info(`Message from ${senderJid} for user ${userName}: ${text.slice(0, 50)}...`);
|
|
164
|
+
// Notify the node that a message is being processed
|
|
165
|
+
sendNodeEvent("whatsapp.message.received", {
|
|
166
|
+
userId,
|
|
167
|
+
from: senderJid,
|
|
168
|
+
text: text.slice(0, 100),
|
|
169
|
+
isGroup,
|
|
170
|
+
});
|
|
171
|
+
try {
|
|
172
|
+
// Send typing indicator
|
|
173
|
+
await sock.presenceSubscribe(remoteJid);
|
|
174
|
+
await sock.sendPresenceUpdate("composing", remoteJid);
|
|
175
|
+
// Process through AI agent
|
|
176
|
+
const reply = await runHostedAgentReply({
|
|
177
|
+
userId,
|
|
178
|
+
userName,
|
|
179
|
+
nodeId,
|
|
180
|
+
messageText: text,
|
|
181
|
+
senderJid: senderJid ?? remoteJid,
|
|
182
|
+
chatJid: remoteJid,
|
|
183
|
+
isGroup,
|
|
184
|
+
sendNodeEvent,
|
|
185
|
+
});
|
|
186
|
+
// Stop typing indicator
|
|
187
|
+
await sock.sendPresenceUpdate("paused", remoteJid);
|
|
188
|
+
if (reply && reply.trim()) {
|
|
189
|
+
// Send reply via WhatsApp
|
|
190
|
+
const authDir = resolveHostedWhatsAppAuthDir(userId);
|
|
191
|
+
await sendMessageWhatsApp(remoteJid, reply, {
|
|
192
|
+
verbose: false,
|
|
193
|
+
accountId: userId, // Use userId as account for hosted mode
|
|
194
|
+
});
|
|
195
|
+
log.info(`Reply sent to ${remoteJid} for user ${userName}`);
|
|
196
|
+
sendNodeEvent("whatsapp.message.sent", {
|
|
197
|
+
userId,
|
|
198
|
+
to: remoteJid,
|
|
199
|
+
text: reply.slice(0, 100),
|
|
200
|
+
});
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
catch (err) {
|
|
204
|
+
log.error(`Failed to process/reply message for user ${userName}`, { error: String(err) });
|
|
205
|
+
sendNodeEvent("whatsapp.message.error", {
|
|
206
|
+
userId,
|
|
207
|
+
error: String(err),
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
/**
|
|
212
|
+
* Check if a hosted user has WhatsApp connected.
|
|
213
|
+
*/
|
|
214
|
+
export async function isHostedUserWhatsAppConnected(userId) {
|
|
215
|
+
const session = activeHostedSessions.get(userId);
|
|
216
|
+
if (session?.connected) {
|
|
217
|
+
return true;
|
|
218
|
+
}
|
|
219
|
+
const authDir = resolveHostedWhatsAppAuthDir(userId);
|
|
220
|
+
const hasAuth = await webAuthExists(authDir);
|
|
221
|
+
if (!hasAuth) {
|
|
222
|
+
return false;
|
|
223
|
+
}
|
|
224
|
+
const selfId = readWebSelfId(authDir);
|
|
225
|
+
return Boolean(selfId.jid);
|
|
226
|
+
}
|
|
227
|
+
/**
|
|
228
|
+
* Get an active session for a hosted user.
|
|
229
|
+
*/
|
|
230
|
+
export function getHostedSession(userId) {
|
|
231
|
+
return activeHostedSessions.get(userId);
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Clean up WhatsApp session for a hosted user.
|
|
235
|
+
*/
|
|
236
|
+
export function cleanupHostedWhatsAppSession(userId) {
|
|
237
|
+
const session = activeHostedSessions.get(userId);
|
|
238
|
+
if (session?.sock) {
|
|
239
|
+
try {
|
|
240
|
+
session.sock.end(new Error("cleanup"));
|
|
241
|
+
}
|
|
242
|
+
catch {
|
|
243
|
+
// Ignore cleanup errors
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
activeHostedSessions.delete(userId);
|
|
247
|
+
}
|
|
@@ -10,6 +10,9 @@ import { rawDataToString } from "../../../infra/ws.js";
|
|
|
10
10
|
import { isGatewayCliClient, isWebchatClient } from "../../../utils/message-channel.js";
|
|
11
11
|
import { authorizeGatewayConnect, isLocalDirectRequest } from "../../auth.js";
|
|
12
12
|
import { buildDeviceAuthPayload } from "../../device-auth.js";
|
|
13
|
+
import { registerHostedUserNode } from "../../hosted-db.js";
|
|
14
|
+
import { startHostedTelegramBot } from "../../hosted-telegram.js";
|
|
15
|
+
import { startHostedWhatsAppPairing } from "../../hosted-whatsapp.js";
|
|
13
16
|
import { isLoopbackAddress, isTrustedProxyAddress, resolveGatewayClientIp } from "../../net.js";
|
|
14
17
|
import { resolveNodeCommandAllowlist } from "../../node-command-policy.js";
|
|
15
18
|
import { GATEWAY_CLIENT_IDS } from "../../protocol/client-info.js";
|
|
@@ -488,7 +491,8 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
488
491
|
close(1008, truncateCloseReason(authMessage));
|
|
489
492
|
return;
|
|
490
493
|
}
|
|
491
|
-
|
|
494
|
+
// Skip pairing for: (1) control UI with shared auth, or (2) hosted-token auth
|
|
495
|
+
const skipPairing = (allowControlUiBypass && hasSharedAuth) || authMethod === "hosted-token";
|
|
492
496
|
if (device && devicePublicKey && !skipPairing) {
|
|
493
497
|
const requirePairing = async (reason, _paired) => {
|
|
494
498
|
const pairing = await requestDevicePairing({
|
|
@@ -719,6 +723,33 @@ export function attachGatewayWsMessageHandler(params) {
|
|
|
719
723
|
});
|
|
720
724
|
})
|
|
721
725
|
.catch((err) => logGateway.warn(`voicewake snapshot failed for ${nodeSession.nodeId}: ${formatForLog(err)}`));
|
|
726
|
+
// For hosted-token authenticated nodes, start messaging channels
|
|
727
|
+
if (authMethod === "hosted-token" && authResult.hostedUser) {
|
|
728
|
+
const { userId, userName, telegramBotToken } = authResult.hostedUser;
|
|
729
|
+
registerHostedUserNode(userId, nodeSession.nodeId);
|
|
730
|
+
const sendEvent = (event, payload) => {
|
|
731
|
+
context.nodeRegistry.sendEvent(nodeSession.nodeId, event, payload);
|
|
732
|
+
};
|
|
733
|
+
// Start Telegram bot if configured
|
|
734
|
+
if (telegramBotToken) {
|
|
735
|
+
void startHostedTelegramBot({
|
|
736
|
+
userId,
|
|
737
|
+
userName,
|
|
738
|
+
nodeId: nodeSession.nodeId,
|
|
739
|
+
botToken: telegramBotToken,
|
|
740
|
+
sendEvent,
|
|
741
|
+
}).catch((err) => logGateway.warn(`hosted Telegram bot failed for ${userName}: ${formatForLog(err)}`));
|
|
742
|
+
}
|
|
743
|
+
else {
|
|
744
|
+
// Fall back to WhatsApp pairing if no Telegram
|
|
745
|
+
void startHostedWhatsAppPairing({
|
|
746
|
+
userId,
|
|
747
|
+
userName,
|
|
748
|
+
nodeId: nodeSession.nodeId,
|
|
749
|
+
sendEvent,
|
|
750
|
+
}).catch((err) => logGateway.warn(`hosted WhatsApp pairing failed for ${userName}: ${formatForLog(err)}`));
|
|
751
|
+
}
|
|
752
|
+
}
|
|
722
753
|
}
|
|
723
754
|
logWs("out", "hello-ok", {
|
|
724
755
|
connId,
|
|
@@ -21,8 +21,8 @@ import { startDiagnosticHeartbeat, stopDiagnosticHeartbeat } from "../logging/di
|
|
|
21
21
|
import { createSubsystemLogger, runtimeForLogger } from "../logging/subsystem.js";
|
|
22
22
|
import { runOnboardingWizard } from "../wizard/onboarding.js";
|
|
23
23
|
import { startGatewayConfigReloader } from "./config-reload.js";
|
|
24
|
-
import { initHostedDb, isHostedMode } from "./hosted-db.js";
|
|
25
24
|
import { ExecApprovalManager } from "./exec-approval-manager.js";
|
|
25
|
+
import { initHostedDb, isHostedMode } from "./hosted-db.js";
|
|
26
26
|
import { NodeRegistry } from "./node-registry.js";
|
|
27
27
|
import { createChannelManager } from "./server-channels.js";
|
|
28
28
|
import { createAgentEventHandler } from "./server-chat.js";
|
package/dist/node-host/runner.js
CHANGED
|
@@ -758,7 +758,9 @@ async function handleInvoke(frame, client, skillBins) {
|
|
|
758
758
|
security === "allowlist" && analysisOk ? allowlistEval.allowlistSatisfied : false;
|
|
759
759
|
segments = analysis.segments;
|
|
760
760
|
}
|
|
761
|
-
|
|
761
|
+
// Skip macOS app exec host in hosted mode - use direct spawn instead
|
|
762
|
+
// The CLI is running, not the macOS app, so the app socket may be stale or unavailable
|
|
763
|
+
const useMacAppExec = process.platform === "darwin" && params.hostedMode !== true;
|
|
762
764
|
if (useMacAppExec) {
|
|
763
765
|
const approvalDecision = params.approvalDecision === "allow-once" || params.approvalDecision === "allow-always"
|
|
764
766
|
? params.approvalDecision
|