@johpaz/hive-agents 0.0.37 → 0.0.38
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 +16 -16
- package/dist/hive.js +107 -105
- package/dist/tool-worker.js +25 -36
- package/dist/ui/assets/{AgentCreateForm-tJZv9FZC.js → AgentCreateForm-0oFbN3gj.js} +1 -1
- package/dist/ui/assets/{AgentDetailPage-Du-mRcAX.js → AgentDetailPage-BJ4L2fNJ.js} +1 -1
- package/dist/ui/assets/AgentNewPage-B3n0LUck.js +1 -0
- package/dist/ui/{dist/assets/AgentsPage-YvSgWRiw.js → assets/AgentsPage-DGNLDXjR.js} +1 -1
- package/dist/ui/assets/{CanvasPage-DtMwGvxf.js → CanvasPage-CnMO1FN8.js} +1 -1
- package/dist/ui/assets/{ChannelsPage-BdBXWHjj.js → ChannelsPage-fbF8K4MR.js} +1 -1
- package/dist/ui/{dist/assets/DashboardPage-ghl1ZguH.js → assets/DashboardPage-VyXXp3U1.js} +1 -1
- package/dist/ui/assets/{LoginPage-CAmSI9Vy.js → LoginPage-DPj2s2Qq.js} +1 -1
- package/dist/ui/{dist/assets/LogsPage-DAPBHkwK.js → assets/LogsPage-B2lY9maY.js} +1 -1
- package/dist/ui/{dist/assets/MeetingPage-WjjGOqqU.js → assets/MeetingPage-2ky_hKiG.js} +1 -1
- package/dist/ui/{dist/assets/ProvidersPage-Ct6HsAi1.js → assets/ProvidersPage-CEyUM2tD.js} +1 -1
- package/dist/ui/{dist/assets/RecoverPage-DpW3l-yv.js → assets/RecoverPage-B-hDZUM2.js} +1 -1
- package/dist/ui/{dist/assets/SettingsPage-DBJ7_E6C.js → assets/SettingsPage-eO0i3g8p.js} +1 -1
- package/dist/ui/assets/{SetupPage-DKmLVUaj.js → SetupPage-ByYqTELb.js} +1 -1
- package/dist/ui/assets/WebChatPage-BuGT2AL0.js +16 -0
- package/dist/ui/assets/{alert-C-NE-P3s.js → alert-Bq6awLlW.js} +1 -1
- package/dist/ui/{dist/assets/alert-dialog-C5mzbHdP.js → assets/alert-dialog-DQvltYmf.js} +1 -1
- package/dist/ui/assets/{badge-ChpACfWO.js → badge-DXUDdTed.js} +1 -1
- package/dist/ui/assets/{dialog-QnZ0ad8O.js → dialog-bI9jImCS.js} +1 -1
- package/dist/ui/assets/{es-NQNoaWDx.js → es-Cg8zdT52.js} +1 -1
- package/dist/ui/{dist/assets/index-DMCjjdqf.js → assets/index-CQ7fn00w.js} +2 -2
- package/dist/ui/assets/{label-D2H1IR_J.js → label-CrH0Jj3v.js} +1 -1
- package/dist/ui/assets/useProviders-CnlC_qCS.js +1 -0
- package/dist/ui/dist/assets/{AgentCreateForm-tJZv9FZC.js → AgentCreateForm-0oFbN3gj.js} +1 -1
- package/dist/ui/dist/assets/{AgentDetailPage-Du-mRcAX.js → AgentDetailPage-BJ4L2fNJ.js} +1 -1
- package/dist/ui/dist/assets/AgentNewPage-B3n0LUck.js +1 -0
- package/dist/ui/{assets/AgentsPage-YvSgWRiw.js → dist/assets/AgentsPage-DGNLDXjR.js} +1 -1
- package/dist/ui/dist/assets/{CanvasPage-DtMwGvxf.js → CanvasPage-CnMO1FN8.js} +1 -1
- package/dist/ui/dist/assets/{ChannelsPage-BdBXWHjj.js → ChannelsPage-fbF8K4MR.js} +1 -1
- package/dist/ui/{assets/DashboardPage-ghl1ZguH.js → dist/assets/DashboardPage-VyXXp3U1.js} +1 -1
- package/dist/ui/dist/assets/{LoginPage-CAmSI9Vy.js → LoginPage-DPj2s2Qq.js} +1 -1
- package/dist/ui/{assets/LogsPage-DAPBHkwK.js → dist/assets/LogsPage-B2lY9maY.js} +1 -1
- package/dist/ui/{assets/MeetingPage-WjjGOqqU.js → dist/assets/MeetingPage-2ky_hKiG.js} +1 -1
- package/dist/ui/{assets/ProvidersPage-Ct6HsAi1.js → dist/assets/ProvidersPage-CEyUM2tD.js} +1 -1
- package/dist/ui/{assets/RecoverPage-DpW3l-yv.js → dist/assets/RecoverPage-B-hDZUM2.js} +1 -1
- package/dist/ui/{assets/SettingsPage-DBJ7_E6C.js → dist/assets/SettingsPage-eO0i3g8p.js} +1 -1
- package/dist/ui/dist/assets/{SetupPage-DKmLVUaj.js → SetupPage-ByYqTELb.js} +1 -1
- package/dist/ui/dist/assets/WebChatPage-BuGT2AL0.js +16 -0
- package/dist/ui/dist/assets/{alert-C-NE-P3s.js → alert-Bq6awLlW.js} +1 -1
- package/dist/ui/{assets/alert-dialog-C5mzbHdP.js → dist/assets/alert-dialog-DQvltYmf.js} +1 -1
- package/dist/ui/dist/assets/{badge-ChpACfWO.js → badge-DXUDdTed.js} +1 -1
- package/dist/ui/dist/assets/{dialog-QnZ0ad8O.js → dialog-bI9jImCS.js} +1 -1
- package/dist/ui/dist/assets/{es-NQNoaWDx.js → es-Cg8zdT52.js} +1 -1
- package/dist/ui/{assets/index-DMCjjdqf.js → dist/assets/index-CQ7fn00w.js} +2 -2
- package/dist/ui/dist/assets/{label-D2H1IR_J.js → label-CrH0Jj3v.js} +1 -1
- package/dist/ui/dist/assets/useProviders-CnlC_qCS.js +1 -0
- package/dist/ui/dist/index.html +1 -1
- package/dist/ui/index.html +1 -1
- package/package.json +1 -1
- package/packages/cli/src/commands/gateway.ts +1 -1
- package/packages/cli/src/commands/onboard.ts +1 -1
- package/packages/core/src/agent/agent-loop.ts +4 -14
- package/packages/core/src/agent/context-compiler.ts +1 -1
- package/packages/core/src/agent/conversation-store.ts +4 -5
- package/packages/core/src/agent/providers/index.ts +3 -4
- package/packages/core/src/agent/tool-selector.ts +3 -4
- package/packages/core/src/gateway/resolver.ts +5 -1
- package/packages/core/src/gateway/routes/chat.ts +16 -16
- package/packages/core/src/gateway/server.ts +44 -45
- package/packages/core/src/storage/seed.ts +39 -33
- package/packages/core/src/tool-runtime/index.ts +20 -0
- package/dist/ui/assets/AgentNewPage-DIFYd_Ys.js +0 -1
- package/dist/ui/assets/WebChatPage-CVRcKept.js +0 -16
- package/dist/ui/assets/useProviders-C6_QHsEi.js +0 -1
- package/dist/ui/dist/assets/AgentNewPage-DIFYd_Ys.js +0 -1
- package/dist/ui/dist/assets/WebChatPage-CVRcKept.js +0 -16
- package/dist/ui/dist/assets/useProviders-C6_QHsEi.js +0 -1
|
@@ -194,13 +194,9 @@ export async function* runAgent(
|
|
|
194
194
|
tool_calls: response.tool_calls,
|
|
195
195
|
reasoning_content: response.reasoning_content,
|
|
196
196
|
})
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
tool_calls: response.tool_calls,
|
|
201
|
-
reasoning_content: response.reasoning_content,
|
|
202
|
-
})
|
|
203
|
-
}
|
|
197
|
+
// Note: assistant messages with tool_calls are NOT persisted to DB.
|
|
198
|
+
// Only the final text response to the user is saved.
|
|
199
|
+
// Tool-call round-tripping happens in-memory via the 'messages' array above.
|
|
204
200
|
|
|
205
201
|
for (const tc of response.tool_calls) {
|
|
206
202
|
const toolName = tc.function.name
|
|
@@ -284,18 +280,12 @@ export async function* runAgent(
|
|
|
284
280
|
await opts.onStep({ type: "tool_result", message: toolResultLLM })
|
|
285
281
|
}
|
|
286
282
|
|
|
287
|
-
// Add tool result to messages for next model call
|
|
283
|
+
// Add tool result to messages for next model call (in-memory only, NOT persisted to DB)
|
|
288
284
|
messages.push({
|
|
289
285
|
role: "tool",
|
|
290
286
|
content: toolResultLLM,
|
|
291
287
|
tool_call_id: tc.id,
|
|
292
288
|
})
|
|
293
|
-
if (!opts.isolated) {
|
|
294
|
-
addMessage(opts.threadId, "tool", toolResultLLM, {
|
|
295
|
-
channel: opts.channel,
|
|
296
|
-
tool_call_id: tc.id,
|
|
297
|
-
})
|
|
298
|
-
}
|
|
299
289
|
|
|
300
290
|
// Dynamic tool injection: when search_knowledge finds tools (native or MCP), add them to ctx.tools
|
|
301
291
|
if (toolName === "search_knowledge") {
|
|
@@ -40,7 +40,7 @@ import { getUserDate, getUserTime } from "../utils/date"
|
|
|
40
40
|
const log = logger.child("context-compiler")
|
|
41
41
|
|
|
42
42
|
// Configuration constants
|
|
43
|
-
const KEEP_LAST_N_MESSAGES =
|
|
43
|
+
const KEEP_LAST_N_MESSAGES = 30 // Always keep last N messages (Strategy: SELECT) — only user+assistant text, no tool results
|
|
44
44
|
const DEFAULT_CONTEXT_WINDOW = 250000 // Default context window when model is unknown
|
|
45
45
|
const COMPACT_RATIO = 0.80 // Compact when estimated input exceeds 70% of context window
|
|
46
46
|
const MAX_SYSTEM_PROMPT_CHARS_CAP = 128000 // Hard cap for pathological prompts; normal budget is model-aware
|
|
@@ -97,7 +97,7 @@ export function getRecentMessages(threadId: string, n: number): StoredMessage[]
|
|
|
97
97
|
const db = getDb()
|
|
98
98
|
const rows = db.query(`
|
|
99
99
|
SELECT * FROM conversations
|
|
100
|
-
WHERE thread_id = ?
|
|
100
|
+
WHERE thread_id = ? AND role != 'tool'
|
|
101
101
|
ORDER BY id DESC
|
|
102
102
|
LIMIT ?
|
|
103
103
|
`).all(threadId, n) as StoredMessage[]
|
|
@@ -170,10 +170,9 @@ export function toAPIMessages(rows: StoredMessage[]): LLMMessage[] {
|
|
|
170
170
|
try { content = JSON.parse(r.content_multimodal) } catch { /* ignore */ }
|
|
171
171
|
}
|
|
172
172
|
const msg: LLMMessage = { role: r.role, content }
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
if (r.tool_call_id) msg.tool_call_id = r.tool_call_id
|
|
173
|
+
// Note: tool_calls and tool_call_id are NOT reconstructed from DB.
|
|
174
|
+
// Tool results are kept in-memory during iteration but not persisted,
|
|
175
|
+
// so historical messages only contain text conversation.
|
|
177
176
|
if (r.reasoning_content) msg.reasoning_content = r.reasoning_content
|
|
178
177
|
return msg
|
|
179
178
|
})
|
|
@@ -7,7 +7,6 @@
|
|
|
7
7
|
|
|
8
8
|
import type { Config } from "../../config/loader.ts"
|
|
9
9
|
import { logger } from "../../utils/logger.ts"
|
|
10
|
-
import { getDb } from "../../storage/sqlite.ts"
|
|
11
10
|
import { getAgentLoop } from "../agent-loop"
|
|
12
11
|
import { resolveUserId, resolveAgentId } from "../../storage/onboarding"
|
|
13
12
|
import type { ContentPart } from "../../multimodal/types"
|
|
@@ -34,6 +33,7 @@ export interface ModelOptions {
|
|
|
34
33
|
onStep?: (step: StepEvent) => Promise<void>
|
|
35
34
|
threadId?: string
|
|
36
35
|
userId?: string
|
|
36
|
+
agentId?: string
|
|
37
37
|
channel?: string
|
|
38
38
|
rawUserMessage?: string
|
|
39
39
|
signal?: AbortSignal
|
|
@@ -62,9 +62,8 @@ export class AgentRunner {
|
|
|
62
62
|
}
|
|
63
63
|
|
|
64
64
|
async generate(options: ModelOptions): Promise<ModelResponse> {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const agentId = resolveAgentId(null) || "main"
|
|
65
|
+
// Resolve agentId from explicit option or database (coordinator/first enabled)
|
|
66
|
+
const agentId = options.agentId || resolveAgentId(null) || "main"
|
|
68
67
|
|
|
69
68
|
// Resolve userId from database
|
|
70
69
|
const userId = options.userId || resolveUserId({})
|
|
@@ -29,7 +29,6 @@
|
|
|
29
29
|
*
|
|
30
30
|
* 6. Tool categorization: Tools are categorized by semantic domain:
|
|
31
31
|
* - scheduling (cron tools)
|
|
32
|
-
* - projects (project/task management)
|
|
33
32
|
* - filesystem (file operations)
|
|
34
33
|
* - web (search/fetch)
|
|
35
34
|
* - browser (browser automation)
|
|
@@ -527,10 +526,10 @@ function enrichToolDescription(tool: ToolDescriptor): string {
|
|
|
527
526
|
*
|
|
528
527
|
* Canonical format: `{safeServer}__{safeTool}` (double underscore as separator)
|
|
529
528
|
*/
|
|
530
|
-
export function mcpToolFullName(
|
|
529
|
+
export function mcpToolFullName(serverName: string, toolName: string): string {
|
|
531
530
|
const safe = (s: string) => s.replace(/\s+/g, '_').replace(/[^a-zA-Z0-9_\-]/g, '_')
|
|
532
|
-
const
|
|
533
|
-
const trimmed =
|
|
531
|
+
const full = `${safe(serverName)}__${safe(toolName)}`
|
|
532
|
+
const trimmed = full.length > 64 ? full.substring(0, 64) : full
|
|
534
533
|
return /^[a-zA-Z_]/.test(trimmed) ? trimmed : `_${trimmed}`.substring(0, 64)
|
|
535
534
|
}
|
|
536
535
|
|
|
@@ -2,6 +2,7 @@ import { getDb } from "../storage/sqlite"
|
|
|
2
2
|
|
|
3
3
|
export interface ResolveContextResult {
|
|
4
4
|
userId: string
|
|
5
|
+
threadId: string
|
|
5
6
|
agentId: string
|
|
6
7
|
isNewUser: boolean
|
|
7
8
|
}
|
|
@@ -51,8 +52,11 @@ export function resolveContext(options: ResolveContextOptions): ResolveContextRe
|
|
|
51
52
|
.get()
|
|
52
53
|
|
|
53
54
|
const agentId = coordinatorAgent?.id || "bee"
|
|
55
|
+
// One canonical conversation thread is shared across channels. Transport
|
|
56
|
+
// session IDs route replies; conversations.thread_id owns agent context.
|
|
57
|
+
const threadId = userId
|
|
54
58
|
|
|
55
|
-
return { userId, agentId, isNewUser }
|
|
59
|
+
return { userId, threadId, agentId, isNewUser }
|
|
56
60
|
}
|
|
57
61
|
|
|
58
62
|
export function getDefaultAgentId(): string {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* POST /api/chat
|
|
5
5
|
* {
|
|
6
6
|
* "message": "Mensaje para el coordinador",
|
|
7
|
-
* "thread_id": "
|
|
7
|
+
* "thread_id": "conversations.thread_id (opcional, usa el thread canónico del usuario si no existe)",
|
|
8
8
|
* "channel": "canal (opcional, default: webchat)"
|
|
9
9
|
* }
|
|
10
10
|
*/
|
|
@@ -12,12 +12,11 @@
|
|
|
12
12
|
import { getDb } from "../../storage/sqlite";
|
|
13
13
|
import { resolveUserId, resolveAgentId } from "../../storage/onboarding";
|
|
14
14
|
import { laneQueue } from "../lane-queue";
|
|
15
|
-
import { getRecentMessages } from "../../agent/conversation-store";
|
|
16
15
|
import { AgentRunner } from "../../agent/providers";
|
|
17
16
|
import { logger } from "../../utils/logger";
|
|
18
|
-
import { getUserDate, getUserTime } from "../../utils/date";
|
|
19
17
|
|
|
20
18
|
const log = logger.child("api:chat");
|
|
19
|
+
export const DEFAULT_CHAT_HISTORY_LIMIT = 40;
|
|
21
20
|
|
|
22
21
|
export interface ChatRequest {
|
|
23
22
|
message: string;
|
|
@@ -34,6 +33,11 @@ export interface ChatResponse {
|
|
|
34
33
|
error?: string;
|
|
35
34
|
}
|
|
36
35
|
|
|
36
|
+
export function resolveChatThreadId(finalUserId: string, requestedThreadId?: string): string {
|
|
37
|
+
const trimmedThreadId = requestedThreadId?.trim();
|
|
38
|
+
return trimmedThreadId || finalUserId || "default";
|
|
39
|
+
}
|
|
40
|
+
|
|
37
41
|
export async function handleChat(
|
|
38
42
|
req: Request,
|
|
39
43
|
addCorsHeaders: (res: Response, req: Request) => Response
|
|
@@ -60,8 +64,8 @@ export async function handleChat(
|
|
|
60
64
|
// Resolve agent ID (coordinator by default)
|
|
61
65
|
const finalAgentId = agentId || resolveAgentId(null) || "main";
|
|
62
66
|
|
|
63
|
-
//
|
|
64
|
-
const threadId = thread_id
|
|
67
|
+
// conversations.thread_id is the context key; never generate a per-request thread.
|
|
68
|
+
const threadId = resolveChatThreadId(finalUserId, thread_id);
|
|
65
69
|
|
|
66
70
|
log.info(`[chat] Processing message from user=${finalUserId} agent=${finalAgentId} thread=${threadId}`);
|
|
67
71
|
|
|
@@ -86,15 +90,10 @@ export async function handleChat(
|
|
|
86
90
|
// Format message with timestamp
|
|
87
91
|
const messageContent = `[Timestamp: ${exactTime} (${userTimezone})]\n${message}`;
|
|
88
92
|
|
|
89
|
-
//
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
role: row.role as "user" | "assistant" | "system",
|
|
94
|
-
content: row.content,
|
|
95
|
-
})),
|
|
96
|
-
{ role: "user" as const, content: messageContent }
|
|
97
|
-
];
|
|
93
|
+
// AgentLoop persists this user message, then compileContext loads the last
|
|
94
|
+
// 15 messages from conversations by threadId. Prepending history here is
|
|
95
|
+
// ineffective because AgentLoop.stream only consumes the latest user input.
|
|
96
|
+
const messages = [{ role: "user" as const, content: messageContent }];
|
|
98
97
|
|
|
99
98
|
// Get provider config from DB
|
|
100
99
|
const agent = db.query<any, [string]>(
|
|
@@ -124,6 +123,7 @@ export async function handleChat(
|
|
|
124
123
|
maxSteps: 15,
|
|
125
124
|
threadId,
|
|
126
125
|
userId: finalUserId,
|
|
126
|
+
agentId: finalAgentId,
|
|
127
127
|
channel,
|
|
128
128
|
onStep: async (step) => {
|
|
129
129
|
if (step.type === "text" && step.message) {
|
|
@@ -200,12 +200,12 @@ export async function handleChat(
|
|
|
200
200
|
export async function handleGetChatHistory(req: Request, addCorsHeaders: (r: Response, req: Request) => Response): Promise<Response> {
|
|
201
201
|
const url = new URL(req.url)
|
|
202
202
|
const threadId = url.searchParams.get("sessionId") || url.searchParams.get("threadId") || "default"
|
|
203
|
-
const limit = parseInt(url.searchParams.get("limit") ||
|
|
203
|
+
const limit = parseInt(url.searchParams.get("limit") || String(DEFAULT_CHAT_HISTORY_LIMIT))
|
|
204
204
|
|
|
205
205
|
const messages = getDb().query(`
|
|
206
206
|
SELECT id, thread_id, channel, role, content, tool_calls_json, tool_call_id, reasoning_content, token_count, created_at, updated_at FROM conversations
|
|
207
207
|
WHERE thread_id = ? AND role IN ('user', 'assistant')
|
|
208
|
-
ORDER BY
|
|
208
|
+
ORDER BY id DESC
|
|
209
209
|
LIMIT ?
|
|
210
210
|
`).all(threadId, limit) as Record<string, unknown>[]
|
|
211
211
|
|
|
@@ -373,7 +373,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
373
373
|
|
|
374
374
|
log.info(` Content: ${messageContent.substring(0, 150)}${messageContent.length > 150 ? "..." : ""}`);
|
|
375
375
|
|
|
376
|
-
const { userId } = resolveContext({
|
|
376
|
+
const { userId, threadId: conversationThreadId } = resolveContext({
|
|
377
377
|
channel: message.channel,
|
|
378
378
|
channelUserId: message.sessionId,
|
|
379
379
|
});
|
|
@@ -385,8 +385,8 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
385
385
|
channelManager.startTyping(message.channel, message.sessionId),
|
|
386
386
|
]);
|
|
387
387
|
|
|
388
|
-
//
|
|
389
|
-
const unifiedSessionId =
|
|
388
|
+
// conversationThreadId = conversations.thread_id canónico compartido por todos los canales
|
|
389
|
+
const unifiedSessionId = conversationThreadId;
|
|
390
390
|
// routingSessionId = peerId del canal → para enviar respuestas de vuelta al canal correcto
|
|
391
391
|
const routingSessionId = message.sessionId;
|
|
392
392
|
|
|
@@ -1940,7 +1940,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
1940
1940
|
return;
|
|
1941
1941
|
}
|
|
1942
1942
|
try {
|
|
1943
|
-
const { userId } = resolveContext({ channel: "webchat", channelUserId: sessionId });
|
|
1943
|
+
const { userId, threadId: conversationThreadId } = resolveContext({ channel: "webchat", channelUserId: sessionId });
|
|
1944
1944
|
const messages = [{ role: "user" as const, content: interactionMsg }];
|
|
1945
1945
|
let streamedContent = "";
|
|
1946
1946
|
const messageId = crypto.randomUUID();
|
|
@@ -1949,9 +1949,9 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
1949
1949
|
provider: dbProvider as any,
|
|
1950
1950
|
messages,
|
|
1951
1951
|
maxTokens: 4096,
|
|
1952
|
-
tools: prepareTools(agent,
|
|
1952
|
+
tools: prepareTools(agent, conversationThreadId),
|
|
1953
1953
|
maxSteps: 15,
|
|
1954
|
-
threadId:
|
|
1954
|
+
threadId: conversationThreadId,
|
|
1955
1955
|
userId,
|
|
1956
1956
|
onToken: async (token: string) => {
|
|
1957
1957
|
if (signal.aborted) return;
|
|
@@ -2015,7 +2015,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2015
2015
|
return;
|
|
2016
2016
|
}
|
|
2017
2017
|
try {
|
|
2018
|
-
const { userId } = resolveContext({ channel: "webchat", channelUserId: sessionId });
|
|
2018
|
+
const { userId, threadId: conversationThreadId } = resolveContext({ channel: "webchat", channelUserId: sessionId });
|
|
2019
2019
|
const messages = [{ role: "user" as const, content: interactionMsg }];
|
|
2020
2020
|
let streamedContent = "";
|
|
2021
2021
|
const messageId = crypto.randomUUID();
|
|
@@ -2024,9 +2024,9 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2024
2024
|
provider: dbProvider as any,
|
|
2025
2025
|
messages,
|
|
2026
2026
|
maxTokens: 4096,
|
|
2027
|
-
tools: prepareTools(agent,
|
|
2027
|
+
tools: prepareTools(agent, conversationThreadId),
|
|
2028
2028
|
maxSteps: 15,
|
|
2029
|
-
threadId:
|
|
2029
|
+
threadId: conversationThreadId,
|
|
2030
2030
|
userId,
|
|
2031
2031
|
onToken: async (token: string) => {
|
|
2032
2032
|
if (signal.aborted) return;
|
|
@@ -2157,14 +2157,14 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2157
2157
|
}
|
|
2158
2158
|
|
|
2159
2159
|
try {
|
|
2160
|
-
const
|
|
2161
|
-
const messages = [{ role: "user" as const, content: messageContent }];
|
|
2162
|
-
log.info(`Generating response for session ${unifiedSessionId}...`);
|
|
2163
|
-
|
|
2164
|
-
const { userId } = resolveContext({
|
|
2160
|
+
const { userId, threadId: conversationThreadId } = resolveContext({
|
|
2165
2161
|
channel: "webchat",
|
|
2166
2162
|
channelUserId: msg.sessionId,
|
|
2167
2163
|
});
|
|
2164
|
+
const unifiedSessionId = conversationThreadId;
|
|
2165
|
+
const routingSessionId = msg.sessionId;
|
|
2166
|
+
const messages = [{ role: "user" as const, content: messageContent }];
|
|
2167
|
+
log.info(`Generating response for session ${unifiedSessionId}...`);
|
|
2168
2168
|
|
|
2169
2169
|
// Streaming: send tokens as they arrive
|
|
2170
2170
|
let streamedContent = "";
|
|
@@ -2185,7 +2185,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2185
2185
|
ws.send(JSON.stringify({
|
|
2186
2186
|
type: "message",
|
|
2187
2187
|
id: messageId,
|
|
2188
|
-
sessionId:
|
|
2188
|
+
sessionId: routingSessionId,
|
|
2189
2189
|
content: token,
|
|
2190
2190
|
isChunk: true,
|
|
2191
2191
|
isStep: false,
|
|
@@ -2200,7 +2200,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2200
2200
|
if (trimmedMessage) {
|
|
2201
2201
|
ws.send(JSON.stringify({
|
|
2202
2202
|
type: "progress",
|
|
2203
|
-
sessionId:
|
|
2203
|
+
sessionId: routingSessionId,
|
|
2204
2204
|
content: trimmedMessage,
|
|
2205
2205
|
} as OutboundMessage));
|
|
2206
2206
|
}
|
|
@@ -2212,7 +2212,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2212
2212
|
const narration = getNarration(step.toolName);
|
|
2213
2213
|
ws.send(JSON.stringify({
|
|
2214
2214
|
type: "progress",
|
|
2215
|
-
sessionId:
|
|
2215
|
+
sessionId: routingSessionId,
|
|
2216
2216
|
content: narration,
|
|
2217
2217
|
} as OutboundMessage));
|
|
2218
2218
|
return;
|
|
@@ -2227,7 +2227,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2227
2227
|
if (userMessage) {
|
|
2228
2228
|
ws.send(JSON.stringify({
|
|
2229
2229
|
type: "progress",
|
|
2230
|
-
sessionId:
|
|
2230
|
+
sessionId: routingSessionId,
|
|
2231
2231
|
content: userMessage,
|
|
2232
2232
|
} as OutboundMessage));
|
|
2233
2233
|
}
|
|
@@ -2248,7 +2248,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2248
2248
|
let ttsProviderUsed: string | null = null;
|
|
2249
2249
|
let ttsMimeType: string | null = null;
|
|
2250
2250
|
|
|
2251
|
-
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId:
|
|
2251
|
+
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: routingSessionId } as OutboundMessage));
|
|
2252
2252
|
|
|
2253
2253
|
// Don't send text message if already streamed (content came via onToken)
|
|
2254
2254
|
const alreadyStreamed = streamedContent.length > 0;
|
|
@@ -2258,7 +2258,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2258
2258
|
if (!voiceCfg.ttsProvider) {
|
|
2259
2259
|
ws.send(JSON.stringify({
|
|
2260
2260
|
type: "message",
|
|
2261
|
-
sessionId:
|
|
2261
|
+
sessionId: routingSessionId,
|
|
2262
2262
|
content: `${content}\n\n🔊 Para recibir respuestas en audio, configura el proveedor TTS en Configuración > Canales > WebChat (ej: elevenlabs)`,
|
|
2263
2263
|
isStep: false,
|
|
2264
2264
|
} as OutboundMessage));
|
|
@@ -2273,7 +2273,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2273
2273
|
log.info(`Audio generated: ${base64Audio.length} bytes, mimeType: ${audioOutput.mimeType}`);
|
|
2274
2274
|
ws.send(JSON.stringify({
|
|
2275
2275
|
type: "message",
|
|
2276
|
-
sessionId:
|
|
2276
|
+
sessionId: routingSessionId,
|
|
2277
2277
|
content,
|
|
2278
2278
|
audio: base64Audio,
|
|
2279
2279
|
mimeType: audioOutput.mimeType,
|
|
@@ -2281,11 +2281,11 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2281
2281
|
} as OutboundMessage));
|
|
2282
2282
|
} catch (ttsError) {
|
|
2283
2283
|
log.error(`TTS failed: ${(ttsError as Error).message}), sending text instead`);
|
|
2284
|
-
ws.send(JSON.stringify({ type: "message", sessionId:
|
|
2284
|
+
ws.send(JSON.stringify({ type: "message", sessionId: routingSessionId, content, isStep: false } as OutboundMessage));
|
|
2285
2285
|
}
|
|
2286
2286
|
}
|
|
2287
2287
|
} else {
|
|
2288
|
-
ws.send(JSON.stringify({ type: "message", sessionId:
|
|
2288
|
+
ws.send(JSON.stringify({ type: "message", sessionId: routingSessionId, content, isStep: false } as OutboundMessage));
|
|
2289
2289
|
}
|
|
2290
2290
|
} else if (alreadyStreamed && shouldSpeak && voiceCfg.ttsProvider) {
|
|
2291
2291
|
try {
|
|
@@ -2295,7 +2295,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2295
2295
|
log.info(`Audio generated after streaming: ${base64Audio.length} bytes`);
|
|
2296
2296
|
ws.send(JSON.stringify({
|
|
2297
2297
|
type: "message",
|
|
2298
|
-
sessionId:
|
|
2298
|
+
sessionId: routingSessionId,
|
|
2299
2299
|
content,
|
|
2300
2300
|
audio: base64Audio,
|
|
2301
2301
|
mimeType: audioOutput.mimeType,
|
|
@@ -2349,7 +2349,12 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2349
2349
|
}
|
|
2350
2350
|
|
|
2351
2351
|
try {
|
|
2352
|
-
const
|
|
2352
|
+
const { userId, threadId: conversationThreadId } = resolveContext({
|
|
2353
|
+
channel: "webchat",
|
|
2354
|
+
channelUserId: msg.sessionId,
|
|
2355
|
+
});
|
|
2356
|
+
const unifiedSessionId = conversationThreadId;
|
|
2357
|
+
const routingSessionId = msg.sessionId;
|
|
2353
2358
|
|
|
2354
2359
|
// Multimodal: process image/document if present
|
|
2355
2360
|
let finalMessageContent = msg.content;
|
|
@@ -2418,11 +2423,6 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2418
2423
|
|
|
2419
2424
|
log.info(`Generating response for session ${unifiedSessionId} (multimodal: ${!!(msg.image || msg.document)})...`);
|
|
2420
2425
|
|
|
2421
|
-
const { userId } = resolveContext({
|
|
2422
|
-
channel: "webchat",
|
|
2423
|
-
channelUserId: msg.sessionId,
|
|
2424
|
-
});
|
|
2425
|
-
|
|
2426
2426
|
// Streaming: send tokens as they arrive
|
|
2427
2427
|
let streamedContent = "";
|
|
2428
2428
|
let messageId = crypto.randomUUID();
|
|
@@ -2443,7 +2443,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2443
2443
|
ws.send(JSON.stringify({
|
|
2444
2444
|
type: "message",
|
|
2445
2445
|
id: messageId,
|
|
2446
|
-
sessionId:
|
|
2446
|
+
sessionId: routingSessionId,
|
|
2447
2447
|
content: token,
|
|
2448
2448
|
isChunk: true,
|
|
2449
2449
|
isStep: false,
|
|
@@ -2458,7 +2458,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2458
2458
|
if (trimmedMessage) {
|
|
2459
2459
|
ws.send(JSON.stringify({
|
|
2460
2460
|
type: "progress",
|
|
2461
|
-
sessionId:
|
|
2461
|
+
sessionId: routingSessionId,
|
|
2462
2462
|
content: trimmedMessage,
|
|
2463
2463
|
} as OutboundMessage));
|
|
2464
2464
|
}
|
|
@@ -2470,7 +2470,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2470
2470
|
const narration = getNarration(step.toolName);
|
|
2471
2471
|
ws.send(JSON.stringify({
|
|
2472
2472
|
type: "progress",
|
|
2473
|
-
sessionId:
|
|
2473
|
+
sessionId: routingSessionId,
|
|
2474
2474
|
content: narration,
|
|
2475
2475
|
} as OutboundMessage));
|
|
2476
2476
|
return;
|
|
@@ -2485,7 +2485,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2485
2485
|
if (userMessage) {
|
|
2486
2486
|
ws.send(JSON.stringify({
|
|
2487
2487
|
type: "progress",
|
|
2488
|
-
sessionId:
|
|
2488
|
+
sessionId: routingSessionId,
|
|
2489
2489
|
content: userMessage,
|
|
2490
2490
|
} as OutboundMessage));
|
|
2491
2491
|
}
|
|
@@ -2506,7 +2506,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2506
2506
|
let ttsProviderUsed: string | null = null;
|
|
2507
2507
|
let ttsMimeType: string | null = null;
|
|
2508
2508
|
|
|
2509
|
-
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId:
|
|
2509
|
+
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: routingSessionId } as OutboundMessage));
|
|
2510
2510
|
|
|
2511
2511
|
// Don't send text message if already streamed (content came via onToken)
|
|
2512
2512
|
const alreadyStreamed = streamedContent.length > 0;
|
|
@@ -2516,7 +2516,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2516
2516
|
if (!voiceConfig.ttsProvider) {
|
|
2517
2517
|
ws.send(JSON.stringify({
|
|
2518
2518
|
type: "message",
|
|
2519
|
-
sessionId:
|
|
2519
|
+
sessionId: routingSessionId,
|
|
2520
2520
|
content: `${content}\n\n🔊 Para recibir respuestas en audio, configura el proveedor TTS en Configuración > Canales > WebChat (ej: elevenlabs)`,
|
|
2521
2521
|
isStep: false
|
|
2522
2522
|
} as OutboundMessage));
|
|
@@ -2530,7 +2530,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2530
2530
|
const base64Audio = (audioOutput.data as Buffer).toString("base64");
|
|
2531
2531
|
ws.send(JSON.stringify({
|
|
2532
2532
|
type: "message",
|
|
2533
|
-
sessionId:
|
|
2533
|
+
sessionId: routingSessionId,
|
|
2534
2534
|
content,
|
|
2535
2535
|
audio: base64Audio,
|
|
2536
2536
|
mimeType: audioOutput.mimeType,
|
|
@@ -2538,11 +2538,11 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2538
2538
|
} as OutboundMessage));
|
|
2539
2539
|
} catch (ttsError) {
|
|
2540
2540
|
log.error(`TTS failed: ${(ttsError as Error).message}), sending text instead`);
|
|
2541
|
-
ws.send(JSON.stringify({ type: "message", sessionId:
|
|
2541
|
+
ws.send(JSON.stringify({ type: "message", sessionId: routingSessionId, content, isStep: false } as OutboundMessage));
|
|
2542
2542
|
}
|
|
2543
2543
|
}
|
|
2544
2544
|
} else {
|
|
2545
|
-
ws.send(JSON.stringify({ type: "message", sessionId:
|
|
2545
|
+
ws.send(JSON.stringify({ type: "message", sessionId: routingSessionId, content, isStep: false } as OutboundMessage));
|
|
2546
2546
|
}
|
|
2547
2547
|
} else if (alreadyStreamed && shouldSpeak && voiceConfig.ttsProvider) {
|
|
2548
2548
|
try {
|
|
@@ -2552,7 +2552,7 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2552
2552
|
log.info(`Audio generated after streaming: ${base64Audio.length} bytes`);
|
|
2553
2553
|
ws.send(JSON.stringify({
|
|
2554
2554
|
type: "message",
|
|
2555
|
-
sessionId:
|
|
2555
|
+
sessionId: routingSessionId,
|
|
2556
2556
|
content,
|
|
2557
2557
|
audio: base64Audio,
|
|
2558
2558
|
mimeType: audioOutput.mimeType,
|
|
@@ -2563,15 +2563,14 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2563
2563
|
}
|
|
2564
2564
|
}
|
|
2565
2565
|
} catch (error) {
|
|
2566
|
-
const unifiedSessionId = msg.sessionId;
|
|
2567
2566
|
// Detener typing aunque falle — nunca dejar el spinner infinito
|
|
2568
|
-
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId:
|
|
2567
|
+
ws.send(JSON.stringify({ type: "typing", isTyping: false, sessionId: msg.sessionId } as OutboundMessage));
|
|
2569
2568
|
ws.send(JSON.stringify({
|
|
2570
2569
|
type: "error",
|
|
2571
|
-
sessionId:
|
|
2570
|
+
sessionId: msg.sessionId,
|
|
2572
2571
|
error: (error as Error).message,
|
|
2573
2572
|
} as OutboundMessage));
|
|
2574
|
-
log.error(`Error for session ${
|
|
2573
|
+
log.error(`Error for session ${msg.sessionId}: ${(error as Error).message}`);
|
|
2575
2574
|
}
|
|
2576
2575
|
});
|
|
2577
2576
|
|
|
@@ -2741,4 +2740,4 @@ export async function startGateway(config: Config): Promise<void> {
|
|
|
2741
2740
|
log.error(`Failed to reload configuration: ${(error as Error).message}`);
|
|
2742
2741
|
}
|
|
2743
2742
|
});
|
|
2744
|
-
}
|
|
2743
|
+
}
|