@agenticmail/enterprise 0.5.404 → 0.5.405

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.
@@ -0,0 +1,4984 @@
1
+ import {
2
+ buildRemotonPrompt
3
+ } from "./chunk-WPM52NBU.js";
4
+ import {
5
+ PROVIDER_REGISTRY,
6
+ resolveApiKeyForProvider,
7
+ resolveProvider
8
+ } from "./chunk-UF3ZJMJO.js";
9
+
10
+ // src/runtime/index.ts
11
+ import { nanoid as nanoid3 } from "nanoid";
12
+
13
+ // src/runtime/session-manager.ts
14
+ import { nanoid } from "nanoid";
15
+ var SessionManager = class {
16
+ db;
17
+ constructor(config) {
18
+ this.db = config.engineDb;
19
+ }
20
+ /**
21
+ * Create a new session.
22
+ */
23
+ async createSession(agentId, orgId, parentSessionId) {
24
+ var id = nanoid(21);
25
+ var now = Date.now();
26
+ await this.db.run(
27
+ `INSERT INTO agent_sessions (id, agent_id, org_id, status, token_count, turn_count, parent_session_id, created_at, updated_at)
28
+ VALUES (?, ?, ?, 'active', 0, 0, ?, ?, ?)`,
29
+ [id, agentId, orgId, parentSessionId || null, now, now]
30
+ );
31
+ return {
32
+ id,
33
+ agentId,
34
+ orgId,
35
+ messages: [],
36
+ status: "active",
37
+ tokenCount: 0,
38
+ turnCount: 0,
39
+ createdAt: now,
40
+ updatedAt: now,
41
+ parentSessionId
42
+ };
43
+ }
44
+ /**
45
+ * Get a session by ID, including all messages.
46
+ */
47
+ async getSession(sessionId) {
48
+ var rows = await this.db.query(
49
+ `SELECT * FROM agent_sessions WHERE id = ?`,
50
+ [sessionId]
51
+ );
52
+ if (!rows || rows.length === 0) return null;
53
+ var row = rows[0];
54
+ var messages = await this.getMessages(sessionId);
55
+ return {
56
+ id: row.id,
57
+ agentId: row.agent_id,
58
+ orgId: row.org_id,
59
+ messages,
60
+ status: row.status,
61
+ tokenCount: row.token_count || 0,
62
+ turnCount: row.turn_count || 0,
63
+ createdAt: row.created_at,
64
+ updatedAt: row.updated_at,
65
+ lastHeartbeatAt: row.last_heartbeat_at || row.updated_at,
66
+ parentSessionId: row.parent_session_id || void 0
67
+ };
68
+ }
69
+ /**
70
+ * Update session metadata (status, token count, turn count).
71
+ */
72
+ async updateSession(sessionId, updates) {
73
+ var setClauses = [];
74
+ var values = [];
75
+ if (updates.status !== void 0) {
76
+ setClauses.push("status = ?");
77
+ values.push(updates.status);
78
+ }
79
+ if (updates.tokenCount !== void 0) {
80
+ setClauses.push("token_count = ?");
81
+ values.push(updates.tokenCount);
82
+ }
83
+ if (updates.turnCount !== void 0) {
84
+ setClauses.push("turn_count = ?");
85
+ values.push(updates.turnCount);
86
+ }
87
+ if (updates.lastHeartbeatAt !== void 0) {
88
+ setClauses.push("last_heartbeat_at = ?");
89
+ values.push(updates.lastHeartbeatAt);
90
+ }
91
+ setClauses.push("updated_at = ?");
92
+ values.push(Date.now());
93
+ values.push(sessionId);
94
+ if (setClauses.length > 1) {
95
+ await this.db.run(
96
+ `UPDATE agent_sessions SET ${setClauses.join(", ")} WHERE id = ?`,
97
+ values
98
+ );
99
+ }
100
+ }
101
+ /**
102
+ * Append a message to a session.
103
+ */
104
+ async appendMessage(sessionId, message) {
105
+ var id = nanoid(21);
106
+ var now = Date.now();
107
+ var contentStr = typeof message.content === "string" ? message.content : JSON.stringify(message.content);
108
+ var toolCallsStr = message.tool_calls ? JSON.stringify(message.tool_calls) : null;
109
+ var toolResultsStr = message.tool_results ? JSON.stringify(message.tool_results) : null;
110
+ await this.db.run(
111
+ `INSERT INTO agent_session_messages (id, session_id, role, content, tool_calls, tool_results, created_at)
112
+ VALUES (?, ?, ?, ?, ?, ?, ?)`,
113
+ [id, sessionId, message.role, contentStr, toolCallsStr, toolResultsStr, now]
114
+ );
115
+ await this.db.run(
116
+ `UPDATE agent_sessions SET updated_at = ? WHERE id = ?`,
117
+ [now, sessionId]
118
+ );
119
+ }
120
+ /**
121
+ * Append multiple messages at once.
122
+ */
123
+ async appendMessages(sessionId, messages) {
124
+ for (var msg of messages) {
125
+ await this.appendMessage(sessionId, msg);
126
+ }
127
+ }
128
+ /**
129
+ * List sessions for an agent.
130
+ */
131
+ async listSessions(agentId, opts) {
132
+ var conditions = ["agent_id = ?"];
133
+ var values = [agentId];
134
+ if (opts?.status) {
135
+ conditions.push("status = ?");
136
+ values.push(opts.status);
137
+ }
138
+ var limit = opts?.limit ?? 50;
139
+ var offset = opts?.offset ?? 0;
140
+ var rows = await this.db.query(
141
+ `SELECT * FROM agent_sessions WHERE ${conditions.join(" AND ")} ORDER BY updated_at DESC LIMIT ? OFFSET ?`,
142
+ [...values, limit, offset]
143
+ );
144
+ return (rows || []).map(function(row) {
145
+ return {
146
+ id: row.id,
147
+ agentId: row.agent_id,
148
+ orgId: row.org_id,
149
+ status: row.status,
150
+ tokenCount: row.token_count || 0,
151
+ turnCount: row.turn_count || 0,
152
+ createdAt: row.created_at,
153
+ updatedAt: row.updated_at,
154
+ lastHeartbeatAt: row.last_heartbeat_at || row.updated_at,
155
+ parentSessionId: row.parent_session_id || void 0
156
+ };
157
+ });
158
+ }
159
+ /**
160
+ * Delete a session and all its messages.
161
+ */
162
+ async deleteSession(sessionId) {
163
+ await this.db.run(
164
+ `DELETE FROM agent_session_messages WHERE session_id = ?`,
165
+ [sessionId]
166
+ );
167
+ await this.db.run(
168
+ `DELETE FROM agent_sessions WHERE id = ?`,
169
+ [sessionId]
170
+ );
171
+ }
172
+ /**
173
+ * Compact a session by removing old messages and keeping a summary.
174
+ */
175
+ async compactSession(sessionId, keepLastN) {
176
+ var messages = await this.getMessages(sessionId);
177
+ var keep = keepLastN ?? 10;
178
+ if (messages.length <= keep) return;
179
+ await this.db.run(
180
+ `DELETE FROM agent_session_messages WHERE session_id = ?`,
181
+ [sessionId]
182
+ );
183
+ var recentMessages = messages.slice(-keep);
184
+ for (var msg of recentMessages) {
185
+ await this.appendMessage(sessionId, msg);
186
+ }
187
+ }
188
+ /**
189
+ * Replace all messages in a session.
190
+ */
191
+ async replaceMessages(sessionId, messages) {
192
+ await this.db.run(
193
+ `DELETE FROM agent_session_messages WHERE session_id = ?`,
194
+ [sessionId]
195
+ );
196
+ await this.appendMessages(sessionId, messages);
197
+ }
198
+ /**
199
+ * Find all active sessions (for resume on startup).
200
+ */
201
+ async findActiveSessions() {
202
+ var cutoff = new Date(Date.now() - 10 * 60 * 1e3).toISOString();
203
+ var rows = await this.db.query(
204
+ `SELECT * FROM agent_sessions WHERE status = 'active' OR status = 'resuming' OR (status = 'failed' AND updated_at > ?) ORDER BY updated_at DESC`,
205
+ [cutoff]
206
+ );
207
+ return (rows || []).map(function(row) {
208
+ return {
209
+ id: row.id,
210
+ agentId: row.agent_id,
211
+ orgId: row.org_id,
212
+ status: row.status,
213
+ tokenCount: row.token_count || 0,
214
+ turnCount: row.turn_count || 0,
215
+ createdAt: row.created_at,
216
+ updatedAt: row.updated_at,
217
+ lastHeartbeatAt: row.last_heartbeat_at || row.updated_at,
218
+ parentSessionId: row.parent_session_id || void 0
219
+ };
220
+ });
221
+ }
222
+ /**
223
+ * Touch a session (heartbeat) — updates the heartbeat and updated_at timestamps.
224
+ */
225
+ async touchSession(sessionId, updates) {
226
+ var now = Date.now();
227
+ var setClauses = ["updated_at = ?", "last_heartbeat_at = ?"];
228
+ var values = [now, now];
229
+ if (updates?.tokenCount !== void 0) {
230
+ setClauses.push("token_count = ?");
231
+ values.push(updates.tokenCount);
232
+ }
233
+ if (updates?.turnCount !== void 0) {
234
+ setClauses.push("turn_count = ?");
235
+ values.push(updates.turnCount);
236
+ }
237
+ values.push(sessionId);
238
+ await this.db.run(
239
+ `UPDATE agent_sessions SET ${setClauses.join(", ")} WHERE id = ?`,
240
+ values
241
+ );
242
+ }
243
+ /**
244
+ * Mark sessions as failed if they haven't sent a heartbeat within the timeout period.
245
+ * Returns the IDs of sessions that were marked stale.
246
+ */
247
+ async markStaleSessions(timeoutMs) {
248
+ var cutoff = Date.now() - timeoutMs;
249
+ var rows = await this.db.query(
250
+ `SELECT id FROM agent_sessions WHERE status = 'active' AND (last_heartbeat_at < ? OR (last_heartbeat_at IS NULL AND updated_at < ?))`,
251
+ [cutoff, cutoff]
252
+ );
253
+ var staleIds = (rows || []).map(function(r) {
254
+ return r.id;
255
+ });
256
+ for (var id of staleIds) {
257
+ await this.db.run(
258
+ `UPDATE agent_sessions SET status = 'failed', updated_at = ? WHERE id = ?`,
259
+ [Date.now(), id]
260
+ );
261
+ }
262
+ return staleIds;
263
+ }
264
+ // ─── Private ─────────────────────────────────────
265
+ async getMessages(sessionId) {
266
+ var rows = await this.db.query(
267
+ `SELECT * FROM agent_session_messages WHERE session_id = ? ORDER BY created_at ASC`,
268
+ [sessionId]
269
+ );
270
+ return (rows || []).map(function(row) {
271
+ var content;
272
+ if (typeof row.content === "object" && row.content !== null) {
273
+ content = row.content;
274
+ } else {
275
+ try {
276
+ content = JSON.parse(row.content);
277
+ } catch {
278
+ content = row.content;
279
+ }
280
+ }
281
+ var toolCalls;
282
+ if (row.tool_calls) {
283
+ toolCalls = typeof row.tool_calls === "object" ? row.tool_calls : void 0;
284
+ if (!toolCalls) {
285
+ try {
286
+ toolCalls = JSON.parse(row.tool_calls);
287
+ } catch {
288
+ }
289
+ }
290
+ }
291
+ var toolResults;
292
+ if (row.tool_results) {
293
+ toolResults = typeof row.tool_results === "object" ? row.tool_results : void 0;
294
+ if (!toolResults) {
295
+ try {
296
+ toolResults = JSON.parse(row.tool_results);
297
+ } catch {
298
+ }
299
+ }
300
+ }
301
+ return {
302
+ role: row.role,
303
+ content,
304
+ tool_calls: toolCalls || void 0,
305
+ tool_results: toolResults || void 0
306
+ };
307
+ });
308
+ }
309
+ };
310
+
311
+ // src/runtime/hooks.ts
312
+ var permissionCache = /* @__PURE__ */ new Map();
313
+ var PERMISSION_CACHE_TTL_MS = 3e4;
314
+ function getCachedPermission(cacheKey) {
315
+ var cached = permissionCache.get(cacheKey);
316
+ if (cached && cached.expires > Date.now()) return cached.result;
317
+ permissionCache.delete(cacheKey);
318
+ return null;
319
+ }
320
+ function setCachedPermission(cacheKey, result) {
321
+ permissionCache.set(cacheKey, { result, expires: Date.now() + PERMISSION_CACHE_TTL_MS });
322
+ }
323
+ var EXTERNAL_TOOLS = /* @__PURE__ */ new Set([
324
+ "ent_http_request",
325
+ "ent_http_graphql",
326
+ "ent_http_batch",
327
+ "ent_http_download",
328
+ "ent_email_send",
329
+ "ent_notification_send",
330
+ "ent_notification_broadcast",
331
+ "ent_notification_webhook",
332
+ "ent_notification_escalate",
333
+ "ent_workflow_request",
334
+ "ent_workflow_remind",
335
+ "ent_finance_expense",
336
+ "ent_finance_invoice",
337
+ "web_fetch",
338
+ "web_search",
339
+ "browser",
340
+ "agenticmail_reply",
341
+ "agenticmail_send",
342
+ "agenticmail_forward",
343
+ "bash"
344
+ ]);
345
+ var COMMUNICATION_TOOLS = /* @__PURE__ */ new Set([
346
+ "agenticmail_reply",
347
+ "agenticmail_send",
348
+ "agenticmail_forward",
349
+ "ent_notification_send",
350
+ "ent_notification_broadcast",
351
+ "agent_message_send",
352
+ "agent_message_broadcast"
353
+ ]);
354
+ function createRuntimeHooks(deps) {
355
+ var failMode = deps.failMode ?? "open";
356
+ return {
357
+ // ─── Before LLM Call ────────────────────────────
358
+ async beforeLLMCall(messages, agentId, _sessionId) {
359
+ var injectedMessages = [...messages];
360
+ if (deps.knowledgeBaseEnabled !== false) {
361
+ try {
362
+ var { knowledgeBase } = await import("./routes-UIUIEZKQ.js");
363
+ var kbs = await knowledgeBase.listForAgent(agentId);
364
+ if (kbs.length > 0) {
365
+ var contextParts = [];
366
+ for (var kb of kbs) {
367
+ var lastUserMsg = "";
368
+ for (var i = messages.length - 1; i >= 0; i--) {
369
+ if (messages[i].role === "user") {
370
+ lastUserMsg = typeof messages[i].content === "string" ? messages[i].content : "";
371
+ break;
372
+ }
373
+ }
374
+ if (lastUserMsg) {
375
+ var results = await knowledgeBase.search(kb.id, lastUserMsg, { limit: 3 });
376
+ for (var r of results) {
377
+ contextParts.push(`[KB: ${kb.name}] ${r.content}`);
378
+ }
379
+ }
380
+ }
381
+ if (contextParts.length > 0) {
382
+ var kbContext = `[Knowledge Base Context]
383
+ ${contextParts.join("\n\n")}`;
384
+ injectedMessages.splice(injectedMessages.length - 1, 0, {
385
+ role: "system",
386
+ content: kbContext
387
+ });
388
+ }
389
+ }
390
+ } catch {
391
+ }
392
+ }
393
+ if (deps.memoryEnabled !== false) {
394
+ try {
395
+ var { memoryManager } = await import("./routes-UIUIEZKQ.js");
396
+ var memories = await memoryManager.queryMemories({
397
+ agentId,
398
+ limit: 10,
399
+ sortBy: "importance"
400
+ });
401
+ if (memories.length > 0) {
402
+ var memoryContext = `[Agent Memory]
403
+ ${memories.map(function(m) {
404
+ return `- [${m.category}] ${m.title}: ${m.content.slice(0, 200)}`;
405
+ }).join("\n")}`;
406
+ injectedMessages.splice(injectedMessages.length - 1, 0, {
407
+ role: "system",
408
+ content: memoryContext
409
+ });
410
+ }
411
+ } catch {
412
+ }
413
+ }
414
+ if (deps.policyEnabled !== false) {
415
+ try {
416
+ var { policyEngine } = await import("./routes-UIUIEZKQ.js");
417
+ var policies = await policyEngine.getAgentPolicies(agentId, deps.orgId);
418
+ if (policies.length > 0) {
419
+ var policyText = policies.map(function(p) {
420
+ return `[Policy: ${p.name}] (${p.enforcement}) ${p.content.slice(0, 300)}`;
421
+ }).join("\n\n");
422
+ var policyContext = `[Organization Policies]
423
+ You must follow these policies:
424
+
425
+ ${policyText}`;
426
+ var insertIdx = 0;
427
+ for (var j = 0; j < injectedMessages.length; j++) {
428
+ if (injectedMessages[j].role === "system") insertIdx = j + 1;
429
+ else break;
430
+ }
431
+ injectedMessages.splice(insertIdx, 0, { role: "system", content: policyContext });
432
+ }
433
+ } catch {
434
+ }
435
+ }
436
+ return injectedMessages;
437
+ },
438
+ // ─── Budget Check ──────────────────────────────
439
+ async checkBudget(agentId, _orgId, _estimatedTokens) {
440
+ try {
441
+ var { lifecycle } = await import("./routes-UIUIEZKQ.js");
442
+ var now = Date.now();
443
+ var cacheKey = `budget_sync_${agentId}`;
444
+ var lastSync = globalThis[cacheKey] || 0;
445
+ if (now - lastSync > 6e4) {
446
+ globalThis[cacheKey] = now;
447
+ try {
448
+ var freshAgent = await lifecycle.loadAgentFromDb(agentId);
449
+ if (freshAgent) {
450
+ var existingAgent = lifecycle.getAgent(agentId);
451
+ if (existingAgent) {
452
+ if (freshAgent.budgetConfig) {
453
+ existingAgent.budgetConfig = freshAgent.budgetConfig;
454
+ }
455
+ if (freshAgent.usage) {
456
+ existingAgent.usage = freshAgent.usage;
457
+ }
458
+ console.log(`[budget] Synced from DB \u2014 budget: daily=${freshAgent.budgetConfig?.dailyTokenCap || freshAgent.budgetConfig?.dailyTokens || 0}, monthly=${freshAgent.budgetConfig?.monthlyTokenCap || freshAgent.budgetConfig?.monthlyTokens || 0}, usage: tokensToday=${freshAgent.usage?.tokensToday || 0}, tokensMonth=${freshAgent.usage?.tokensThisMonth || freshAgent.usage?.monthlyTokens || 0}`);
459
+ }
460
+ }
461
+ } catch {
462
+ }
463
+ }
464
+ var usage = lifecycle.getUsage(agentId);
465
+ if (usage) {
466
+ var lastUpdated = usage.lastUpdated || "";
467
+ var todayKey = (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
468
+ var lastKey = lastUpdated ? new Date(lastUpdated).toISOString().slice(0, 10) : todayKey;
469
+ if (lastKey < todayKey) {
470
+ console.log(`[budget] Daily reset triggered for agent ${agentId} (last update: ${lastKey}, today: ${todayKey})`);
471
+ usage.tokensToday = 0;
472
+ usage.costToday = 0;
473
+ usage.errorsToday = 0;
474
+ usage.toolCallsToday = 0;
475
+ usage.externalActionsToday = 0;
476
+ usage.totalSessionsToday = 0;
477
+ }
478
+ }
479
+ var budget = lifecycle.getBudget(agentId);
480
+ if (!budget) return { allowed: true };
481
+ if (!usage) return { allowed: true };
482
+ var u = usage;
483
+ var b = budget;
484
+ var dailySpend = u.costToday ?? u.dailyCostUsd ?? 0;
485
+ var monthlySpend = u.costThisMonth ?? u.monthlyCostUsd ?? 0;
486
+ var _weeklySpend = u.costThisWeek ?? 0;
487
+ var dailyTokens = u.tokensToday ?? u.dailyTokens ?? 0;
488
+ var monthlyTokens = u.tokensThisMonth ?? u.monthlyTokens ?? 0;
489
+ var dailyCostLimit = b.dailyCostCap || b.dailyCost || b.dailyLimitUsd || 0;
490
+ var monthlyCostLimit = b.monthlyCostCap || b.monthlyCost || b.monthlyLimitUsd || 0;
491
+ var dailyTokenLimit = b.dailyTokenCap || b.dailyTokens || 0;
492
+ var monthlyTokenLimit = b.monthlyTokenCap || b.monthlyTokens || 0;
493
+ if (dailyCostLimit > 0 && dailySpend >= dailyCostLimit) {
494
+ return {
495
+ allowed: false,
496
+ reason: `Daily cost budget exceeded: $${dailySpend.toFixed(2)} / $${dailyCostLimit.toFixed(2)}`,
497
+ remainingUsd: 0
498
+ };
499
+ }
500
+ if (monthlyCostLimit > 0 && monthlySpend >= monthlyCostLimit) {
501
+ return {
502
+ allowed: false,
503
+ reason: `Monthly cost budget exceeded: $${monthlySpend.toFixed(2)} / $${monthlyCostLimit.toFixed(2)}`,
504
+ remainingUsd: 0
505
+ };
506
+ }
507
+ if (dailyTokenLimit > 0 && dailyTokens >= dailyTokenLimit) {
508
+ return {
509
+ allowed: false,
510
+ reason: `Daily token budget exceeded: ${(dailyTokens / 1e6).toFixed(1)}M / ${(dailyTokenLimit / 1e6).toFixed(1)}M tokens`,
511
+ remainingUsd: 0
512
+ };
513
+ }
514
+ if (monthlyTokenLimit > 0 && monthlyTokens >= monthlyTokenLimit) {
515
+ return {
516
+ allowed: false,
517
+ reason: `Monthly token budget exceeded: ${(monthlyTokens / 1e6).toFixed(1)}M / ${(monthlyTokenLimit / 1e6).toFixed(1)}M tokens`,
518
+ remainingUsd: 0
519
+ };
520
+ }
521
+ var remaining = Infinity;
522
+ if (dailyCostLimit > 0) remaining = Math.min(remaining, dailyCostLimit - dailySpend);
523
+ if (monthlyCostLimit > 0) remaining = Math.min(remaining, monthlyCostLimit - monthlySpend);
524
+ if (remaining === Infinity) remaining = 100;
525
+ return { allowed: true, remainingUsd: remaining };
526
+ } catch (err) {
527
+ console.error(`[hooks] Budget check failed: ${err.message}`);
528
+ return { allowed: false, reason: "Budget check error \u2014 denying for safety" };
529
+ }
530
+ },
531
+ // ─── Record LLM Usage ──────────────────────────
532
+ async recordLLMUsage(agentId, orgId, usage) {
533
+ try {
534
+ var { lifecycle } = await import("./routes-UIUIEZKQ.js");
535
+ console.log(`[hooks] recordLLMUsage: agent=${agentId}, input=${usage.inputTokens}, output=${usage.outputTokens}`);
536
+ await lifecycle.recordLLMUsage(agentId, {
537
+ inputTokens: usage.inputTokens,
538
+ outputTokens: usage.outputTokens,
539
+ costUsd: usage.costUsd
540
+ });
541
+ } catch (recordErr) {
542
+ console.log(`[hooks] recordLLMUsage error: ${recordErr.message}`);
543
+ }
544
+ try {
545
+ var { activity } = await import("./routes-UIUIEZKQ.js");
546
+ await activity.record({
547
+ agentId,
548
+ orgId,
549
+ type: "llm_call",
550
+ data: {
551
+ inputTokens: usage.inputTokens,
552
+ outputTokens: usage.outputTokens,
553
+ costUsd: usage.costUsd
554
+ }
555
+ });
556
+ } catch {
557
+ }
558
+ },
559
+ // ─── Model Pricing Lookup ──────────────────────
560
+ async getModelPricing(provider, modelId) {
561
+ try {
562
+ var adminDb = deps.engineDb;
563
+ var rows = await adminDb.query(
564
+ `SELECT model_pricing_config FROM company_settings LIMIT 1`,
565
+ []
566
+ );
567
+ if (rows && rows.length > 0) {
568
+ var raw = rows[0].model_pricing_config;
569
+ if (raw) {
570
+ var config = typeof raw === "string" ? JSON.parse(raw) : raw;
571
+ var models = config.models || [];
572
+ var match = models.find(function(m) {
573
+ return m.provider === provider && m.modelId === modelId;
574
+ });
575
+ if (match) {
576
+ return {
577
+ inputCostPerMillion: match.inputCostPerMillion,
578
+ outputCostPerMillion: match.outputCostPerMillion
579
+ };
580
+ }
581
+ }
582
+ }
583
+ } catch {
584
+ }
585
+ return null;
586
+ },
587
+ // ─── Before Tool Call ───────────────────────────
588
+ async beforeToolCall(ctx) {
589
+ try {
590
+ var cacheKey = `${ctx.agentId}:${ctx.toolName}`;
591
+ var cached = getCachedPermission(cacheKey);
592
+ if (cached) return cached;
593
+ var { permissionEngine } = await import("./routes-UIUIEZKQ.js");
594
+ var permResult = await permissionEngine.checkPermission(ctx.agentId, ctx.toolName);
595
+ var result = {
596
+ allowed: permResult.allowed,
597
+ reason: permResult.reason || "Permission check",
598
+ requiresApproval: permResult.requiresApproval || false
599
+ };
600
+ if (result.allowed && deps.guardrailsEnabled !== false) {
601
+ try {
602
+ var { guardrails } = await import("./routes-UIUIEZKQ.js");
603
+ var status = await guardrails.getStatus(ctx.agentId);
604
+ if (status.paused || status.offDuty) {
605
+ result.allowed = false;
606
+ result.reason = status.offDuty ? "Agent is off duty \u2014 outside scheduled working hours" : "Agent is paused by guardrail intervention";
607
+ return result;
608
+ }
609
+ } catch {
610
+ }
611
+ }
612
+ if (result.allowed && deps.dlpEnabled !== false && ctx.parameters) {
613
+ try {
614
+ var { dlp } = await import("./routes-UIUIEZKQ.js");
615
+ var dlpResult = await dlp.scanParameters({
616
+ orgId: ctx.orgId,
617
+ agentId: ctx.agentId,
618
+ toolId: ctx.toolName,
619
+ parameters: ctx.parameters
620
+ });
621
+ if (dlpResult && !dlpResult.allowed) {
622
+ result.allowed = false;
623
+ result.reason = dlpResult.reason || "Blocked by DLP policy";
624
+ return result;
625
+ }
626
+ if (dlpResult?.modifiedContent) {
627
+ result.modifiedParameters = dlpResult.modifiedContent;
628
+ }
629
+ } catch {
630
+ }
631
+ }
632
+ if (result.requiresApproval && result.allowed) {
633
+ try {
634
+ var { approvals } = await import("./routes-UIUIEZKQ.js");
635
+ var approval = await approvals.createAndWait({
636
+ agentId: ctx.agentId,
637
+ orgId: ctx.orgId,
638
+ toolId: ctx.toolName,
639
+ toolName: ctx.toolName,
640
+ parameters: ctx.parameters,
641
+ reason: `Agent ${ctx.agentId} requests approval for ${ctx.toolName}`
642
+ });
643
+ if (approval && (approval.status === "denied" || approval.status === "expired")) {
644
+ result.allowed = false;
645
+ result.reason = approval.status === "expired" ? "Approval request expired" : `Denied: ${approval.decision?.reason || "No reason"}`;
646
+ }
647
+ if (approval) result.approvalId = approval.id;
648
+ } catch {
649
+ }
650
+ }
651
+ setCachedPermission(cacheKey, result);
652
+ return result;
653
+ } catch (err) {
654
+ var allowed = failMode === "open";
655
+ return {
656
+ allowed,
657
+ reason: allowed ? `Hook error (fail-open): ${err.message}` : `Hook error (fail-closed): ${err.message}`
658
+ };
659
+ }
660
+ },
661
+ // ─── After Tool Call ────────────────────────────
662
+ async afterToolCall(ctx, result) {
663
+ try {
664
+ var { activity } = await import("./routes-UIUIEZKQ.js");
665
+ await activity.record({
666
+ agentId: ctx.agentId,
667
+ orgId: ctx.orgId,
668
+ sessionId: ctx.sessionId,
669
+ type: result.success ? "tool_call_end" : "tool_call_error",
670
+ data: {
671
+ toolId: ctx.toolName,
672
+ toolName: ctx.toolName,
673
+ success: result.success,
674
+ error: result.error,
675
+ durationMs: result.durationMs
676
+ }
677
+ });
678
+ } catch {
679
+ }
680
+ try {
681
+ var { lifecycle } = await import("./routes-UIUIEZKQ.js");
682
+ await lifecycle.recordToolCall(ctx.agentId, {
683
+ toolId: ctx.toolName,
684
+ tokensUsed: 0,
685
+ costUsd: 0,
686
+ isExternalAction: EXTERNAL_TOOLS.has(ctx.toolName),
687
+ error: !result.success
688
+ });
689
+ } catch {
690
+ }
691
+ if (result.success && EXTERNAL_TOOLS.has(ctx.toolName)) {
692
+ try {
693
+ var { journal } = await import("./routes-UIUIEZKQ.js");
694
+ await journal.record({
695
+ orgId: ctx.orgId,
696
+ agentId: ctx.agentId,
697
+ sessionId: ctx.sessionId,
698
+ toolId: ctx.toolName,
699
+ toolName: ctx.toolName,
700
+ parameters: ctx.parameters,
701
+ result: result.output
702
+ });
703
+ } catch {
704
+ }
705
+ }
706
+ if (result.success && COMMUNICATION_TOOLS.has(ctx.toolName)) {
707
+ try {
708
+ var { commBus } = await import("./routes-UIUIEZKQ.js");
709
+ await commBus.observeMessage({
710
+ orgId: ctx.orgId,
711
+ agentId: ctx.agentId,
712
+ toolId: ctx.toolName,
713
+ parameters: ctx.parameters
714
+ });
715
+ } catch {
716
+ }
717
+ }
718
+ },
719
+ // ─── Session Lifecycle ──────────────────────────
720
+ async onSessionStart(sessionId, agentId, orgId) {
721
+ try {
722
+ var { activity } = await import("./routes-UIUIEZKQ.js");
723
+ await activity.record({
724
+ agentId,
725
+ orgId,
726
+ sessionId,
727
+ type: "session_start",
728
+ data: { sessionId }
729
+ });
730
+ } catch {
731
+ }
732
+ },
733
+ async onSessionEnd(sessionId, agentId, orgId) {
734
+ try {
735
+ var { activity } = await import("./routes-UIUIEZKQ.js");
736
+ await activity.record({
737
+ agentId,
738
+ orgId,
739
+ sessionId,
740
+ type: "session_end",
741
+ data: { sessionId }
742
+ });
743
+ } catch {
744
+ }
745
+ },
746
+ // ─── Context Compaction ─────────────────────────
747
+ async onContextCompaction(sessionId, agentId, summary) {
748
+ try {
749
+ var { memoryManager } = await import("./routes-UIUIEZKQ.js");
750
+ await memoryManager.createMemory({
751
+ agentId,
752
+ orgId: deps.orgId,
753
+ category: "session_learning",
754
+ title: `Context compaction \u2014 session ${sessionId || "unknown"} (${(/* @__PURE__ */ new Date()).toISOString()})`,
755
+ content: summary.slice(0, 1e4),
756
+ // Keep up to 10K chars of summary
757
+ source: "context_compaction",
758
+ importance: "high"
759
+ // High importance — this is the agent's working memory
760
+ });
761
+ } catch (err) {
762
+ console.warn(`[hooks] Failed to persist compaction summary: ${err?.message}`);
763
+ }
764
+ try {
765
+ var { TaskQueueManager } = await import("./task-queue-Y2ERCCRY.js");
766
+ var tq = new TaskQueueManager();
767
+ tq.db = deps.engineDb;
768
+ await tq.init();
769
+ var sessionTask = await tq.getTaskBySessionId(sessionId);
770
+ if (sessionTask) {
771
+ await tq.updateTask(sessionTask.id, {
772
+ activityLog: [...sessionTask.activityLog || [], {
773
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
774
+ type: "compaction",
775
+ agent: agentId,
776
+ detail: `Context compacted (${summary.length} chars summary). Agent continues from compacted state.`
777
+ }]
778
+ });
779
+ console.log(`[hooks] Task ${sessionTask.id.slice(0, 8)} updated with compaction event`);
780
+ }
781
+ } catch {
782
+ }
783
+ }
784
+ };
785
+ }
786
+ function createNoopHooks() {
787
+ return {
788
+ async beforeLLMCall(messages) {
789
+ return messages;
790
+ },
791
+ async checkBudget() {
792
+ return { allowed: true };
793
+ },
794
+ async recordLLMUsage() {
795
+ },
796
+ async getModelPricing() {
797
+ return null;
798
+ },
799
+ async beforeToolCall() {
800
+ return { allowed: true, reason: "no-op" };
801
+ },
802
+ async afterToolCall() {
803
+ },
804
+ async onSessionStart() {
805
+ },
806
+ async onSessionEnd() {
807
+ },
808
+ async onContextCompaction() {
809
+ }
810
+ };
811
+ }
812
+
813
+ // src/runtime/llm-client.ts
814
+ function estimateTokens(text) {
815
+ return Math.ceil(text.length / 4);
816
+ }
817
+ function estimateMessageTokens(messages) {
818
+ var total = 0;
819
+ for (var msg of messages) {
820
+ if (typeof msg.content === "string") {
821
+ total += estimateTokens(msg.content);
822
+ } else if (Array.isArray(msg.content)) {
823
+ for (var block of msg.content) {
824
+ if (block.type === "text") total += estimateTokens(block.text);
825
+ else if (block.type === "thinking") total += estimateTokens(block.thinking);
826
+ else if (block.type === "tool_use") total += estimateTokens(JSON.stringify(block.input));
827
+ else if (block.type === "tool_result") {
828
+ var resultContent = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
829
+ total += estimateTokens(resultContent);
830
+ }
831
+ }
832
+ }
833
+ if (msg.tool_calls) {
834
+ for (var tc of msg.tool_calls) {
835
+ total += estimateTokens(tc.name + JSON.stringify(tc.input));
836
+ }
837
+ }
838
+ if (msg.tool_results) {
839
+ for (var tr of msg.tool_results) {
840
+ total += estimateTokens(tr.content);
841
+ }
842
+ }
843
+ }
844
+ return total;
845
+ }
846
+ function convertToAnthropicMessages(messages) {
847
+ var result = [];
848
+ for (var msg of messages) {
849
+ if (msg.role === "system") continue;
850
+ if (msg.role === "user") {
851
+ if (msg.tool_results && msg.tool_results.length > 0) {
852
+ var toolResultBlocks = msg.tool_results.map(function(tr) {
853
+ return {
854
+ type: "tool_result",
855
+ tool_use_id: tr.tool_use_id,
856
+ content: tr.content,
857
+ is_error: tr.is_error || false
858
+ };
859
+ });
860
+ result.push({ role: "user", content: toolResultBlocks });
861
+ } else {
862
+ result.push({
863
+ role: "user",
864
+ content: typeof msg.content === "string" ? msg.content : msg.content
865
+ });
866
+ }
867
+ } else if (msg.role === "assistant") {
868
+ var content = [];
869
+ if (typeof msg.content === "string") {
870
+ if (msg.content) content.push({ type: "text", text: msg.content });
871
+ } else if (Array.isArray(msg.content)) {
872
+ content = msg.content.filter((b) => b.type !== "thinking");
873
+ }
874
+ if (msg.tool_calls) {
875
+ var existingToolIds = new Set(content.filter((b) => b.type === "tool_use").map((b) => b.id));
876
+ for (var tc of msg.tool_calls) {
877
+ if (!existingToolIds.has(tc.id)) {
878
+ content.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
879
+ }
880
+ }
881
+ }
882
+ if (content.length > 0) {
883
+ result.push({ role: "assistant", content });
884
+ }
885
+ }
886
+ }
887
+ return result;
888
+ }
889
+ function extractSystemPrompt(messages) {
890
+ var systemParts = [];
891
+ for (var msg of messages) {
892
+ if (msg.role === "system") {
893
+ if (typeof msg.content === "string") {
894
+ systemParts.push(msg.content);
895
+ } else if (Array.isArray(msg.content)) {
896
+ for (var block of msg.content) {
897
+ if (block.type === "text") systemParts.push(block.text);
898
+ }
899
+ }
900
+ }
901
+ }
902
+ return systemParts.join("\n\n");
903
+ }
904
+ async function callAnthropic(config, messages, tools, options, onEvent) {
905
+ var Anthropic;
906
+ try {
907
+ var mod = await import("@anthropic-ai/sdk");
908
+ Anthropic = mod.default || mod.Anthropic;
909
+ } catch {
910
+ throw new Error(
911
+ "Anthropic SDK not installed. Run: npm install @anthropic-ai/sdk"
912
+ );
913
+ }
914
+ var isOAuthToken = config.apiKey?.includes("sk-ant-oat") || false;
915
+ var useTokenAuth = config.authMode === "token" || isOAuthToken;
916
+ var clientOpts = {};
917
+ if (useTokenAuth) {
918
+ clientOpts.apiKey = null;
919
+ clientOpts.authToken = config.apiKey;
920
+ clientOpts.defaultHeaders = {
921
+ "anthropic-beta": "claude-code-20250219,oauth-2025-04-20",
922
+ "user-agent": "claude-cli/1.0.0 (external, cli)",
923
+ "x-app": "cli"
924
+ };
925
+ } else {
926
+ clientOpts.apiKey = config.apiKey;
927
+ }
928
+ if (config.baseUrl) {
929
+ clientOpts.baseURL = config.baseUrl;
930
+ }
931
+ var client = new Anthropic(clientOpts);
932
+ var systemPrompt = extractSystemPrompt(messages);
933
+ var anthropicMessages = convertToAnthropicMessages(messages);
934
+ var requestBody = {
935
+ model: config.modelId,
936
+ max_tokens: options.maxTokens,
937
+ temperature: options.temperature,
938
+ messages: anthropicMessages
939
+ };
940
+ if (systemPrompt) {
941
+ requestBody.system = systemPrompt;
942
+ }
943
+ if (tools.length > 0) {
944
+ requestBody.tools = tools.map(function(t) {
945
+ return { name: t.name, description: t.description, input_schema: t.input_schema };
946
+ });
947
+ }
948
+ if (config.thinkingLevel && config.thinkingLevel !== "off") {
949
+ var budgetMap = { low: 2048, medium: 8192, high: 16384 };
950
+ var budgetTokens = budgetMap[config.thinkingLevel] || 8192;
951
+ requestBody.thinking = {
952
+ type: "enabled",
953
+ budget_tokens: budgetTokens
954
+ };
955
+ if (requestBody.max_tokens <= budgetTokens) {
956
+ requestBody.max_tokens = budgetTokens + 4096;
957
+ }
958
+ requestBody.temperature = 1;
959
+ }
960
+ var events = [];
961
+ var toolCalls = [];
962
+ var textParts = [];
963
+ var thinkingParts = [];
964
+ var stopReason = "end_turn";
965
+ var usage = { inputTokens: 0, outputTokens: 0 };
966
+ var stream = client.messages.stream(requestBody, {
967
+ signal: options.signal
968
+ });
969
+ for await (var event of stream) {
970
+ if (event.type === "content_block_delta") {
971
+ var delta = event.delta;
972
+ if (delta.type === "text_delta") {
973
+ textParts.push(delta.text);
974
+ var textEvent = { type: "text_delta", text: delta.text };
975
+ events.push(textEvent);
976
+ onEvent?.(textEvent);
977
+ } else if (delta.type === "thinking_delta") {
978
+ thinkingParts.push(delta.thinking);
979
+ var thinkEvent = { type: "thinking_delta", text: delta.thinking };
980
+ events.push(thinkEvent);
981
+ onEvent?.(thinkEvent);
982
+ } else if (delta.type === "input_json_delta") {
983
+ }
984
+ } else if (event.type === "content_block_start") {
985
+ var block = event.content_block;
986
+ if (block?.type === "tool_use") {
987
+ var tcStartEvent = {
988
+ type: "tool_call_start",
989
+ toolName: block.name,
990
+ toolCallId: block.id
991
+ };
992
+ events.push(tcStartEvent);
993
+ onEvent?.(tcStartEvent);
994
+ }
995
+ } else if (event.type === "message_delta") {
996
+ var msgDelta = event.delta;
997
+ if (msgDelta.stop_reason) {
998
+ stopReason = msgDelta.stop_reason;
999
+ }
1000
+ if (event.usage) {
1001
+ usage.outputTokens += event.usage.output_tokens || 0;
1002
+ }
1003
+ } else if (event.type === "message_start") {
1004
+ var message = event.message;
1005
+ if (message?.usage) {
1006
+ usage.inputTokens = message.usage.input_tokens || 0;
1007
+ usage.outputTokens = message.usage.output_tokens || 0;
1008
+ }
1009
+ }
1010
+ }
1011
+ var finalMessage = await stream.finalMessage();
1012
+ if (finalMessage.content) {
1013
+ for (var block of finalMessage.content) {
1014
+ if (block.type === "tool_use") {
1015
+ var tuBlock = block;
1016
+ toolCalls.push({
1017
+ id: tuBlock.id,
1018
+ name: tuBlock.name,
1019
+ input: tuBlock.input || {}
1020
+ });
1021
+ }
1022
+ }
1023
+ }
1024
+ if (finalMessage.usage) {
1025
+ usage.inputTokens = finalMessage.usage.input_tokens;
1026
+ usage.outputTokens = finalMessage.usage.output_tokens;
1027
+ }
1028
+ stopReason = finalMessage.stop_reason;
1029
+ return {
1030
+ events,
1031
+ toolCalls,
1032
+ textContent: textParts.join(""),
1033
+ thinkingContent: thinkingParts.join(""),
1034
+ stopReason,
1035
+ usage
1036
+ };
1037
+ }
1038
+ function convertToOpenAIMessages(messages) {
1039
+ var result = [];
1040
+ for (var msg of messages) {
1041
+ if (msg.role === "system") {
1042
+ result.push({
1043
+ role: "system",
1044
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
1045
+ });
1046
+ } else if (msg.role === "user") {
1047
+ if (msg.tool_results && msg.tool_results.length > 0) {
1048
+ for (var tr of msg.tool_results) {
1049
+ result.push({
1050
+ role: "tool",
1051
+ tool_call_id: tr.tool_use_id,
1052
+ content: tr.content
1053
+ });
1054
+ }
1055
+ } else {
1056
+ result.push({
1057
+ role: "user",
1058
+ content: typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content)
1059
+ });
1060
+ }
1061
+ } else if (msg.role === "assistant") {
1062
+ var entry = {
1063
+ role: "assistant",
1064
+ content: typeof msg.content === "string" ? msg.content : null
1065
+ };
1066
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
1067
+ entry.tool_calls = msg.tool_calls.map(function(tc) {
1068
+ return {
1069
+ id: tc.id,
1070
+ type: "function",
1071
+ function: { name: tc.name, arguments: JSON.stringify(tc.input) }
1072
+ };
1073
+ });
1074
+ }
1075
+ result.push(entry);
1076
+ }
1077
+ }
1078
+ return result;
1079
+ }
1080
+ async function callOpenAICompatible(config, messages, tools, options, onEvent) {
1081
+ var OpenAI;
1082
+ try {
1083
+ var mod = await import("openai");
1084
+ OpenAI = mod.default || mod.OpenAI;
1085
+ } catch {
1086
+ throw new Error(
1087
+ "OpenAI SDK not installed. Run: npm install openai"
1088
+ );
1089
+ }
1090
+ var clientOpts = { apiKey: config.apiKey || "not-needed" };
1091
+ if (config.baseURL) clientOpts.baseURL = config.baseURL;
1092
+ if (config.headers) clientOpts.defaultHeaders = config.headers;
1093
+ var client = new OpenAI(clientOpts);
1094
+ var openaiMessages = convertToOpenAIMessages(messages);
1095
+ var requestBody = {
1096
+ model: config.modelId,
1097
+ max_tokens: options.maxTokens,
1098
+ temperature: options.temperature,
1099
+ messages: openaiMessages,
1100
+ stream: true,
1101
+ stream_options: { include_usage: true }
1102
+ };
1103
+ if (tools.length > 0) {
1104
+ requestBody.tools = tools.map(function(t) {
1105
+ return {
1106
+ type: "function",
1107
+ function: {
1108
+ name: t.name,
1109
+ description: t.description,
1110
+ parameters: t.input_schema
1111
+ }
1112
+ };
1113
+ });
1114
+ }
1115
+ var events = [];
1116
+ var toolCalls = [];
1117
+ var textParts = [];
1118
+ var stopReason = "end_turn";
1119
+ var usage = { inputTokens: 0, outputTokens: 0 };
1120
+ var partialToolCalls = /* @__PURE__ */ new Map();
1121
+ var stream = await client.chat.completions.create(requestBody, {
1122
+ signal: options.signal
1123
+ });
1124
+ for await (var chunk of stream) {
1125
+ var choice = chunk.choices?.[0];
1126
+ if (!choice) continue;
1127
+ var delta = choice.delta;
1128
+ if (delta?.content) {
1129
+ textParts.push(delta.content);
1130
+ var textEvent = { type: "text_delta", text: delta.content };
1131
+ events.push(textEvent);
1132
+ onEvent?.(textEvent);
1133
+ }
1134
+ if (delta?.tool_calls) {
1135
+ for (var tcDelta of delta.tool_calls) {
1136
+ var idx = tcDelta.index ?? 0;
1137
+ if (!partialToolCalls.has(idx)) {
1138
+ partialToolCalls.set(idx, {
1139
+ id: tcDelta.id || "",
1140
+ name: tcDelta.function?.name || "",
1141
+ args: ""
1142
+ });
1143
+ if (tcDelta.function?.name) {
1144
+ var tcStartEvent = {
1145
+ type: "tool_call_start",
1146
+ toolName: tcDelta.function.name,
1147
+ toolCallId: tcDelta.id || ""
1148
+ };
1149
+ events.push(tcStartEvent);
1150
+ onEvent?.(tcStartEvent);
1151
+ }
1152
+ }
1153
+ var partial = partialToolCalls.get(idx);
1154
+ if (tcDelta.id) partial.id = tcDelta.id;
1155
+ if (tcDelta.function?.name) partial.name = tcDelta.function.name;
1156
+ if (tcDelta.function?.arguments) partial.args += tcDelta.function.arguments;
1157
+ }
1158
+ }
1159
+ if (choice.finish_reason) {
1160
+ if (choice.finish_reason === "tool_calls") stopReason = "tool_use";
1161
+ else if (choice.finish_reason === "length") stopReason = "max_tokens";
1162
+ else if (choice.finish_reason === "stop") stopReason = "end_turn";
1163
+ }
1164
+ if (chunk.usage) {
1165
+ usage.inputTokens = chunk.usage.prompt_tokens || 0;
1166
+ usage.outputTokens = chunk.usage.completion_tokens || 0;
1167
+ }
1168
+ }
1169
+ for (var [, partial] of partialToolCalls) {
1170
+ var parsedInput = {};
1171
+ try {
1172
+ parsedInput = JSON.parse(partial.args || "{}");
1173
+ } catch {
1174
+ }
1175
+ toolCalls.push({ id: partial.id, name: partial.name, input: parsedInput });
1176
+ }
1177
+ return {
1178
+ events,
1179
+ toolCalls,
1180
+ textContent: textParts.join(""),
1181
+ thinkingContent: "",
1182
+ stopReason,
1183
+ usage
1184
+ };
1185
+ }
1186
+ function convertToGeminiContents(messages) {
1187
+ var systemParts = [];
1188
+ var contents = [];
1189
+ for (var msg of messages) {
1190
+ if (msg.role === "system") {
1191
+ var text = typeof msg.content === "string" ? msg.content : "";
1192
+ if (Array.isArray(msg.content)) {
1193
+ for (var b of msg.content) {
1194
+ if (b.type === "text") text += (text ? "\n" : "") + b.text;
1195
+ }
1196
+ }
1197
+ if (text) systemParts.push({ text });
1198
+ continue;
1199
+ }
1200
+ if (msg.role === "user") {
1201
+ if (msg.tool_results && msg.tool_results.length > 0) {
1202
+ var frParts = msg.tool_results.map(function(tr) {
1203
+ return {
1204
+ functionResponse: {
1205
+ name: tr.tool_use_id,
1206
+ response: { content: tr.content }
1207
+ }
1208
+ };
1209
+ });
1210
+ contents.push({ role: "user", parts: frParts });
1211
+ } else {
1212
+ var userText = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1213
+ contents.push({ role: "user", parts: [{ text: userText }] });
1214
+ }
1215
+ } else if (msg.role === "assistant") {
1216
+ var parts = [];
1217
+ if (typeof msg.content === "string" && msg.content) {
1218
+ parts.push({ text: msg.content });
1219
+ } else if (Array.isArray(msg.content)) {
1220
+ for (var block of msg.content) {
1221
+ if (block.type === "text") parts.push({ text: block.text });
1222
+ }
1223
+ }
1224
+ if (msg.tool_calls) {
1225
+ for (var tc of msg.tool_calls) {
1226
+ parts.push({ functionCall: { name: tc.name, args: tc.input } });
1227
+ }
1228
+ }
1229
+ if (parts.length > 0) contents.push({ role: "model", parts });
1230
+ }
1231
+ }
1232
+ return {
1233
+ systemInstruction: systemParts.length > 0 ? { parts: systemParts } : void 0,
1234
+ contents
1235
+ };
1236
+ }
1237
+ async function callGoogle(config, messages, tools, options, onEvent) {
1238
+ var { systemInstruction, contents } = convertToGeminiContents(messages);
1239
+ var requestBody = {
1240
+ contents,
1241
+ generationConfig: {
1242
+ maxOutputTokens: options.maxTokens,
1243
+ temperature: options.temperature
1244
+ }
1245
+ };
1246
+ if (systemInstruction) {
1247
+ requestBody.systemInstruction = systemInstruction;
1248
+ }
1249
+ if (tools.length > 0) {
1250
+ requestBody.tools = [{
1251
+ functionDeclarations: tools.map(function(t) {
1252
+ return {
1253
+ name: t.name,
1254
+ description: t.description,
1255
+ parameters: t.input_schema
1256
+ };
1257
+ })
1258
+ }];
1259
+ }
1260
+ var url = `https://generativelanguage.googleapis.com/v1beta/models/${config.modelId}:streamGenerateContent?key=${config.apiKey}&alt=sse`;
1261
+ var resp = await fetch(url, {
1262
+ method: "POST",
1263
+ headers: { "Content-Type": "application/json" },
1264
+ body: JSON.stringify(requestBody),
1265
+ signal: options.signal
1266
+ });
1267
+ if (!resp.ok) {
1268
+ var errText = await resp.text().catch(function() {
1269
+ return "";
1270
+ });
1271
+ var err = new Error(`Gemini API error ${resp.status}: ${errText}`);
1272
+ err.status = resp.status;
1273
+ throw err;
1274
+ }
1275
+ var events = [];
1276
+ var toolCalls = [];
1277
+ var textParts = [];
1278
+ var stopReason = "end_turn";
1279
+ var usage = { inputTokens: 0, outputTokens: 0 };
1280
+ var toolCallCounter = 0;
1281
+ var reader = resp.body.getReader();
1282
+ var decoder = new TextDecoder();
1283
+ var buffer = "";
1284
+ while (true) {
1285
+ var { done, value } = await reader.read();
1286
+ if (done) break;
1287
+ buffer += decoder.decode(value, { stream: true });
1288
+ var lines = buffer.split("\n");
1289
+ buffer = lines.pop() || "";
1290
+ for (var line of lines) {
1291
+ if (!line.startsWith("data: ")) continue;
1292
+ var jsonStr = line.slice(6).trim();
1293
+ if (!jsonStr || jsonStr === "[DONE]") continue;
1294
+ var chunk;
1295
+ try {
1296
+ chunk = JSON.parse(jsonStr);
1297
+ } catch {
1298
+ continue;
1299
+ }
1300
+ var candidate = chunk.candidates?.[0];
1301
+ if (!candidate) continue;
1302
+ var cParts = candidate.content?.parts || [];
1303
+ for (var part of cParts) {
1304
+ if (part.text) {
1305
+ textParts.push(part.text);
1306
+ var textEvent = { type: "text_delta", text: part.text };
1307
+ events.push(textEvent);
1308
+ onEvent?.(textEvent);
1309
+ }
1310
+ if (part.functionCall) {
1311
+ var tcId = "gemini_tc_" + ++toolCallCounter;
1312
+ var tcStartEvent = {
1313
+ type: "tool_call_start",
1314
+ toolName: part.functionCall.name,
1315
+ toolCallId: tcId
1316
+ };
1317
+ events.push(tcStartEvent);
1318
+ onEvent?.(tcStartEvent);
1319
+ toolCalls.push({
1320
+ id: tcId,
1321
+ name: part.functionCall.name,
1322
+ input: part.functionCall.args || {}
1323
+ });
1324
+ }
1325
+ }
1326
+ if (candidate.finishReason) {
1327
+ if (candidate.finishReason === "STOP") stopReason = "end_turn";
1328
+ else if (candidate.finishReason === "MAX_TOKENS") stopReason = "max_tokens";
1329
+ else if (candidate.finishReason === "TOOL_USE" || toolCalls.length > 0) stopReason = "tool_use";
1330
+ }
1331
+ if (chunk.usageMetadata) {
1332
+ usage.inputTokens = chunk.usageMetadata.promptTokenCount || 0;
1333
+ usage.outputTokens = chunk.usageMetadata.candidatesTokenCount || 0;
1334
+ }
1335
+ }
1336
+ }
1337
+ if (toolCalls.length > 0 && stopReason === "end_turn") {
1338
+ stopReason = "tool_use";
1339
+ }
1340
+ return {
1341
+ events,
1342
+ toolCalls,
1343
+ textContent: textParts.join(""),
1344
+ thinkingContent: "",
1345
+ stopReason,
1346
+ usage
1347
+ };
1348
+ }
1349
+ function convertToOllamaMessages(messages) {
1350
+ var result = [];
1351
+ for (var msg of messages) {
1352
+ if (msg.role === "system") {
1353
+ var text = typeof msg.content === "string" ? msg.content : "";
1354
+ if (Array.isArray(msg.content)) {
1355
+ for (var b of msg.content) {
1356
+ if (b.type === "text") text += (text ? "\n" : "") + b.text;
1357
+ }
1358
+ }
1359
+ result.push({ role: "system", content: text });
1360
+ } else if (msg.role === "user") {
1361
+ if (msg.tool_results && msg.tool_results.length > 0) {
1362
+ for (var tr of msg.tool_results) {
1363
+ result.push({ role: "tool", content: tr.content });
1364
+ }
1365
+ } else {
1366
+ var userText = typeof msg.content === "string" ? msg.content : JSON.stringify(msg.content);
1367
+ result.push({ role: "user", content: userText });
1368
+ }
1369
+ } else if (msg.role === "assistant") {
1370
+ var entry = {
1371
+ role: "assistant",
1372
+ content: typeof msg.content === "string" ? msg.content : ""
1373
+ };
1374
+ if (msg.tool_calls && msg.tool_calls.length > 0) {
1375
+ entry.tool_calls = msg.tool_calls.map(function(tc) {
1376
+ return {
1377
+ function: { name: tc.name, arguments: tc.input }
1378
+ };
1379
+ });
1380
+ }
1381
+ result.push(entry);
1382
+ }
1383
+ }
1384
+ return result;
1385
+ }
1386
+ async function callOllama(config, messages, tools, options, onEvent) {
1387
+ var baseURL = config.baseURL || "http://localhost:11434";
1388
+ var ollamaMessages = convertToOllamaMessages(messages);
1389
+ var requestBody = {
1390
+ model: config.modelId,
1391
+ messages: ollamaMessages,
1392
+ stream: true,
1393
+ options: {
1394
+ temperature: options.temperature,
1395
+ num_predict: options.maxTokens
1396
+ }
1397
+ };
1398
+ if (tools.length > 0) {
1399
+ requestBody.tools = tools.map(function(t) {
1400
+ return {
1401
+ type: "function",
1402
+ function: {
1403
+ name: t.name,
1404
+ description: t.description,
1405
+ parameters: t.input_schema
1406
+ }
1407
+ };
1408
+ });
1409
+ }
1410
+ var resp = await fetch(baseURL + "/api/chat", {
1411
+ method: "POST",
1412
+ headers: { "Content-Type": "application/json" },
1413
+ body: JSON.stringify(requestBody),
1414
+ signal: options.signal
1415
+ });
1416
+ if (!resp.ok) {
1417
+ var errText = await resp.text().catch(function() {
1418
+ return "";
1419
+ });
1420
+ var err = new Error(`Ollama error ${resp.status}: ${errText}`);
1421
+ err.status = resp.status;
1422
+ throw err;
1423
+ }
1424
+ var events = [];
1425
+ var toolCalls = [];
1426
+ var textParts = [];
1427
+ var stopReason = "end_turn";
1428
+ var usage = { inputTokens: 0, outputTokens: 0 };
1429
+ var toolCallCounter = 0;
1430
+ var reader = resp.body.getReader();
1431
+ var decoder = new TextDecoder();
1432
+ var buffer = "";
1433
+ while (true) {
1434
+ var { done, value } = await reader.read();
1435
+ if (done) break;
1436
+ buffer += decoder.decode(value, { stream: true });
1437
+ var lines = buffer.split("\n");
1438
+ buffer = lines.pop() || "";
1439
+ for (var line of lines) {
1440
+ var trimmed = line.trim();
1441
+ if (!trimmed) continue;
1442
+ var chunk;
1443
+ try {
1444
+ chunk = JSON.parse(trimmed);
1445
+ } catch {
1446
+ continue;
1447
+ }
1448
+ if (chunk.message?.content) {
1449
+ textParts.push(chunk.message.content);
1450
+ var textEvent = { type: "text_delta", text: chunk.message.content };
1451
+ events.push(textEvent);
1452
+ onEvent?.(textEvent);
1453
+ }
1454
+ if (chunk.message?.tool_calls) {
1455
+ for (var tc of chunk.message.tool_calls) {
1456
+ var tcId = "ollama_tc_" + ++toolCallCounter;
1457
+ var tcStartEvent = {
1458
+ type: "tool_call_start",
1459
+ toolName: tc.function?.name || "",
1460
+ toolCallId: tcId
1461
+ };
1462
+ events.push(tcStartEvent);
1463
+ onEvent?.(tcStartEvent);
1464
+ toolCalls.push({
1465
+ id: tcId,
1466
+ name: tc.function?.name || "",
1467
+ input: tc.function?.arguments || {}
1468
+ });
1469
+ }
1470
+ }
1471
+ if (chunk.done) {
1472
+ if (chunk.prompt_eval_count) usage.inputTokens = chunk.prompt_eval_count;
1473
+ if (chunk.eval_count) usage.outputTokens = chunk.eval_count;
1474
+ }
1475
+ }
1476
+ }
1477
+ if (toolCalls.length > 0) {
1478
+ stopReason = "tool_use";
1479
+ }
1480
+ return {
1481
+ events,
1482
+ toolCalls,
1483
+ textContent: textParts.join(""),
1484
+ thinkingContent: "",
1485
+ stopReason,
1486
+ usage
1487
+ };
1488
+ }
1489
+ var DEFAULT_MAX_RETRY_DURATION_MS = 36e5;
1490
+ var DEFAULT_MAX_RETRIES = 200;
1491
+ var DEFAULT_BASE_DELAY_MS = 1e3;
1492
+ var DEFAULT_MAX_DELAY_MS = 6e4;
1493
+ function isRetryableError(err) {
1494
+ var status = err.status || err.statusCode || err?.response?.status;
1495
+ if (status === 400 || status === 401 || status === 403) {
1496
+ return false;
1497
+ }
1498
+ if (status === 429 || status === 500 || status === 502 || status === 503) {
1499
+ return true;
1500
+ }
1501
+ var message = String(err.message || err || "").toLowerCase();
1502
+ if (message.includes("econnreset") || message.includes("etimedout") || message.includes("fetch failed") || message.includes("network") || message.includes("socket hang up") || message.includes("econnrefused")) {
1503
+ return true;
1504
+ }
1505
+ return false;
1506
+ }
1507
+ function parseRetryAfterMs(err) {
1508
+ var retryAfter = err.headers?.get?.("retry-after") || err.headers?.["retry-after"] || err.response?.headers?.get?.("retry-after") || err.response?.headers?.["retry-after"];
1509
+ if (!retryAfter) return 0;
1510
+ var seconds = Number(retryAfter);
1511
+ if (!isNaN(seconds) && seconds > 0) {
1512
+ return Math.ceil(seconds * 1e3);
1513
+ }
1514
+ var date = Date.parse(retryAfter);
1515
+ if (!isNaN(date)) {
1516
+ var delayMs = date - Date.now();
1517
+ return delayMs > 0 ? delayMs : 0;
1518
+ }
1519
+ return 0;
1520
+ }
1521
+ async function callWithRetry(fn, retryConfig, onEvent) {
1522
+ var maxDuration = retryConfig?.maxRetryDurationMs ?? DEFAULT_MAX_RETRY_DURATION_MS;
1523
+ var maxRetries = retryConfig?.maxRetries ?? DEFAULT_MAX_RETRIES;
1524
+ var baseDelay = retryConfig?.baseDelayMs ?? DEFAULT_BASE_DELAY_MS;
1525
+ var maxDelay = retryConfig?.maxDelayMs ?? DEFAULT_MAX_DELAY_MS;
1526
+ var attempt = 0;
1527
+ var startTime = Date.now();
1528
+ while (true) {
1529
+ try {
1530
+ return await fn();
1531
+ } catch (err) {
1532
+ var elapsed = Date.now() - startTime;
1533
+ if (!isRetryableError(err) || elapsed >= maxDuration || attempt >= maxRetries) {
1534
+ throw err;
1535
+ }
1536
+ attempt++;
1537
+ var retryAfterMs = parseRetryAfterMs(err);
1538
+ var exponentialDelay = Math.min(
1539
+ baseDelay * Math.pow(2, attempt - 1) + Math.random() * baseDelay,
1540
+ maxDelay
1541
+ );
1542
+ var delayMs = retryAfterMs > 0 ? Math.max(retryAfterMs, exponentialDelay) : exponentialDelay;
1543
+ var remainingMs = maxDuration - elapsed;
1544
+ if (delayMs > remainingMs) {
1545
+ delayMs = remainingMs;
1546
+ }
1547
+ var reason = err.message || String(err);
1548
+ onEvent?.({
1549
+ type: "retry",
1550
+ attempt,
1551
+ maxRetries,
1552
+ delayMs: Math.round(delayMs),
1553
+ reason
1554
+ });
1555
+ await new Promise(function(resolve) {
1556
+ setTimeout(resolve, delayMs);
1557
+ });
1558
+ }
1559
+ }
1560
+ }
1561
+ async function callLLM(config, messages, tools, options, onEvent, retryConfig) {
1562
+ var invokeLLM = function() {
1563
+ var providerDef = resolveProvider(config.provider);
1564
+ var apiType = providerDef ? providerDef.apiType : "openai-compatible";
1565
+ var baseURL = config.baseUrl || (providerDef ? providerDef.baseUrl : void 0);
1566
+ switch (apiType) {
1567
+ case "anthropic":
1568
+ return callAnthropic(
1569
+ { modelId: config.modelId, apiKey: config.apiKey, thinkingLevel: config.thinkingLevel, authMode: config.authMode, baseUrl: baseURL },
1570
+ messages,
1571
+ tools,
1572
+ options,
1573
+ onEvent
1574
+ );
1575
+ case "openai-compatible":
1576
+ return callOpenAICompatible(
1577
+ { modelId: config.modelId, apiKey: config.apiKey, baseURL, headers: config.headers },
1578
+ messages,
1579
+ tools,
1580
+ options,
1581
+ onEvent
1582
+ );
1583
+ case "google":
1584
+ return callGoogle(
1585
+ { modelId: config.modelId, apiKey: config.apiKey },
1586
+ messages,
1587
+ tools,
1588
+ options,
1589
+ onEvent
1590
+ );
1591
+ case "ollama":
1592
+ return callOllama(
1593
+ { modelId: config.modelId, baseURL },
1594
+ messages,
1595
+ tools,
1596
+ options,
1597
+ onEvent
1598
+ );
1599
+ default:
1600
+ throw new Error(`Unsupported API type "${apiType}" for provider "${config.provider}"`);
1601
+ }
1602
+ };
1603
+ return callWithRetry(invokeLLM, retryConfig, onEvent);
1604
+ }
1605
+ function toolsToDefinitions(tools) {
1606
+ return tools.map(function(t) {
1607
+ return {
1608
+ name: t.name,
1609
+ description: t.description,
1610
+ input_schema: t.parameters || t.input_schema || { type: "object", properties: {} }
1611
+ };
1612
+ });
1613
+ }
1614
+
1615
+ // src/runtime/tool-executor.ts
1616
+ var DEFAULT_TOOL_TIMEOUT_MS = 3e4;
1617
+ var MAX_RESULT_CHARS = 2e5;
1618
+ var ToolRegistry = class {
1619
+ tools = /* @__PURE__ */ new Map();
1620
+ register(tools) {
1621
+ for (var tool of tools) {
1622
+ this.tools.set(tool.name, tool);
1623
+ }
1624
+ }
1625
+ get(name) {
1626
+ return this.tools.get(name);
1627
+ }
1628
+ has(name) {
1629
+ return this.tools.has(name);
1630
+ }
1631
+ list() {
1632
+ return Array.from(this.tools.values());
1633
+ }
1634
+ clear() {
1635
+ this.tools.clear();
1636
+ }
1637
+ };
1638
+ async function executeTool(tool, toolCall, options) {
1639
+ var timeoutMs = options?.timeoutMs ?? DEFAULT_TOOL_TIMEOUT_MS;
1640
+ var started = Date.now();
1641
+ try {
1642
+ var validationError = validateToolInput(tool, toolCall.input);
1643
+ if (validationError) {
1644
+ return {
1645
+ result: { success: false, error: validationError, durationMs: Date.now() - started },
1646
+ content: `Error: ${validationError}`
1647
+ };
1648
+ }
1649
+ var toolResult = await executeWithTimeout(
1650
+ tool.execute(toolCall.id, toolCall.input),
1651
+ timeoutMs,
1652
+ options?.signal
1653
+ );
1654
+ var content = formatToolResult(toolResult);
1655
+ var durationMs = Date.now() - started;
1656
+ return {
1657
+ result: { success: true, output: content, durationMs },
1658
+ content: truncateResult(content)
1659
+ };
1660
+ } catch (err) {
1661
+ var durationMs = Date.now() - started;
1662
+ var errorMessage = err?.message || String(err);
1663
+ if (err?.name === "AbortError" || err?.name === "TimeoutError") {
1664
+ errorMessage = `Tool "${tool.name}" timed out after ${timeoutMs}ms`;
1665
+ }
1666
+ return {
1667
+ result: { success: false, error: errorMessage, durationMs },
1668
+ content: `Error executing ${tool.name}: ${errorMessage}`
1669
+ };
1670
+ }
1671
+ }
1672
+ function validateToolInput(tool, input) {
1673
+ var schema = tool.parameters;
1674
+ if (!schema || !schema.required) return null;
1675
+ for (var field of schema.required) {
1676
+ if (input[field] === void 0 || input[field] === null) {
1677
+ return `Missing required parameter: "${field}" for tool "${tool.name}"`;
1678
+ }
1679
+ }
1680
+ if (schema.properties) {
1681
+ for (var [key, prop] of Object.entries(schema.properties)) {
1682
+ if (input[key] === void 0) continue;
1683
+ var expectedType = prop.type;
1684
+ var actualValue = input[key];
1685
+ var actualType = typeof actualValue;
1686
+ if (expectedType === "string" && actualType !== "string") {
1687
+ return `Parameter "${key}" must be a string, got ${actualType}`;
1688
+ }
1689
+ if (expectedType === "number" && actualType !== "number") {
1690
+ return `Parameter "${key}" must be a number, got ${actualType}`;
1691
+ }
1692
+ if (expectedType === "boolean" && actualType !== "boolean") {
1693
+ return `Parameter "${key}" must be a boolean, got ${actualType}`;
1694
+ }
1695
+ if (expectedType === "array" && !Array.isArray(actualValue)) {
1696
+ return `Parameter "${key}" must be an array`;
1697
+ }
1698
+ }
1699
+ }
1700
+ return null;
1701
+ }
1702
+ async function executeWithTimeout(promise, timeoutMs, signal) {
1703
+ if (signal?.aborted) {
1704
+ throw new Error("Aborted");
1705
+ }
1706
+ return new Promise(function(resolve, reject) {
1707
+ var timer = setTimeout(function() {
1708
+ var err = new Error("Tool execution timed out");
1709
+ err.name = "TimeoutError";
1710
+ reject(err);
1711
+ }, timeoutMs);
1712
+ var onAbort = function() {
1713
+ clearTimeout(timer);
1714
+ var err = new Error("Tool execution aborted");
1715
+ err.name = "AbortError";
1716
+ reject(err);
1717
+ };
1718
+ if (signal) {
1719
+ signal.addEventListener("abort", onAbort, { once: true });
1720
+ }
1721
+ promise.then(
1722
+ function(value) {
1723
+ clearTimeout(timer);
1724
+ signal?.removeEventListener("abort", onAbort);
1725
+ resolve(value);
1726
+ },
1727
+ function(err) {
1728
+ clearTimeout(timer);
1729
+ signal?.removeEventListener("abort", onAbort);
1730
+ reject(err);
1731
+ }
1732
+ );
1733
+ });
1734
+ }
1735
+ function formatToolResult(result) {
1736
+ var anyResult = result;
1737
+ if ((!result.content || result.content.length === 0) && anyResult.output) {
1738
+ return String(anyResult.output);
1739
+ }
1740
+ if (!result.content || result.content.length === 0) {
1741
+ return "(no output)";
1742
+ }
1743
+ var parts = [];
1744
+ for (var block of result.content) {
1745
+ if (block.type === "text") {
1746
+ parts.push(block.text);
1747
+ } else if (block.type === "image") {
1748
+ parts.push(`[Image: ${block.mimeType}]`);
1749
+ }
1750
+ }
1751
+ return parts.join("\n");
1752
+ }
1753
+ function truncateResult(content) {
1754
+ if (content.length <= MAX_RESULT_CHARS) return content;
1755
+ var truncated = content.slice(0, MAX_RESULT_CHARS);
1756
+ return truncated + `
1757
+
1758
+ ... [truncated, ${content.length - MAX_RESULT_CHARS} chars omitted]`;
1759
+ }
1760
+
1761
+ // src/runtime/compaction.ts
1762
+ var COMPACTION_THRESHOLD = 0.8;
1763
+ var TARGET_USAGE = 0.45;
1764
+ var MIN_KEEP_RECENT = 10;
1765
+ var MAX_KEEP_RECENT = 30;
1766
+ var SUMMARY_MAX_TOKENS = 4096;
1767
+ var CHUNK_MAX_CHARS = 8e4;
1768
+ var MAX_PARALLEL_CHUNKS = 3;
1769
+ var TOOL_RESULT_TRIM_THRESHOLD = 2e3;
1770
+ var TOOL_RESULT_TRIM_TO = 400;
1771
+ var HIGH_IMPORTANCE_PATTERNS = [
1772
+ /error|fail|exception|crash|bug/i,
1773
+ /decision|decided|chose|choosing/i,
1774
+ /important|critical|must|required/i,
1775
+ /password|secret|key|token|credential/i,
1776
+ /\b[a-f0-9]{8}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{4}-[a-f0-9]{12}\b/,
1777
+ // UUIDs
1778
+ /https?:\/\/\S{20,}/,
1779
+ // Long URLs
1780
+ /\/[a-zA-Z][\w/.-]{10,}/
1781
+ // File paths
1782
+ ];
1783
+ async function compactContext(messages, config, hooks, options) {
1784
+ const startMs = Date.now();
1785
+ const contextWindowSize = config.contextWindowSize ?? 2e5;
1786
+ const tokensBefore = estimateMessageTokens(messages);
1787
+ const targetTokens = Math.floor(contextWindowSize * TARGET_USAGE);
1788
+ if (tokensBefore <= contextWindowSize * COMPACTION_THRESHOLD) {
1789
+ return messages;
1790
+ }
1791
+ const tokensToFree = tokensBefore - targetTokens;
1792
+ console.log(`[compaction] Need to free ~${tokensToFree} tokens (${tokensBefore} \u2192 target ${targetTokens})`);
1793
+ const systemMessages = messages.filter((m) => m.role === "system");
1794
+ const nonSystem = messages.filter((m) => m.role !== "system");
1795
+ if (nonSystem.length <= MIN_KEEP_RECENT) {
1796
+ return messages;
1797
+ }
1798
+ const tier1Messages = trimToolResults(nonSystem);
1799
+ const tier1Tokens = estimateMessageTokens([...systemMessages, ...tier1Messages]);
1800
+ if (tier1Tokens <= contextWindowSize * COMPACTION_THRESHOLD) {
1801
+ const stats = {
1802
+ strategy: "tier1_trim",
1803
+ messagesBefore: messages.length,
1804
+ messagesAfter: systemMessages.length + tier1Messages.length,
1805
+ tokensBefore,
1806
+ tokensAfter: tier1Tokens,
1807
+ msElapsed: Date.now() - startMs
1808
+ };
1809
+ console.log(`[compaction] Tier 1 (trim tool results) sufficient: ${tokensBefore} \u2192 ${tier1Tokens} tokens in ${stats.msElapsed}ms`);
1810
+ return [...systemMessages, ...tier1Messages];
1811
+ }
1812
+ const keepCount = calculateKeepRecent(tier1Messages, targetTokens, estimateMessageTokens(systemMessages));
1813
+ const { toSummarize, keepRecent } = splitAtSafeBoundary(tier1Messages, keepCount);
1814
+ if (toSummarize.length === 0) {
1815
+ return messages;
1816
+ }
1817
+ const groups = groupMessages(toSummarize);
1818
+ const sortedGroups = groups.sort((a, b) => b.importance - a.importance);
1819
+ const previousSummaries = sortedGroups.filter((g) => g.isPreviousSummary);
1820
+ const regularGroups = sortedGroups.filter((g) => !g.isPreviousSummary);
1821
+ const keepTokenBudget = estimateMessageTokens(systemMessages) + estimateMessageTokens(keepRecent);
1822
+ const summaryBudget = targetTokens - keepTokenBudget;
1823
+ if (!options?.apiKey || summaryBudget < 1e3) {
1824
+ const summary = buildExtractiveSummary(previousSummaries, regularGroups, summaryBudget);
1825
+ const result = assembleFinal(systemMessages, summary, keepRecent);
1826
+ const stats = {
1827
+ strategy: "tier2_extractive",
1828
+ messagesBefore: messages.length,
1829
+ messagesAfter: result.length,
1830
+ tokensBefore,
1831
+ tokensAfter: estimateMessageTokens(result),
1832
+ msElapsed: Date.now() - startMs,
1833
+ previousSummariesChained: previousSummaries.length
1834
+ };
1835
+ console.log(`[compaction] Tier 2 (extractive): ${stats.tokensBefore} \u2192 ${stats.tokensAfter} tokens in ${stats.msElapsed}ms`);
1836
+ await persistSummary(hooks, options?.sessionId, config.agentId, summary);
1837
+ return result;
1838
+ }
1839
+ try {
1840
+ const transcript = buildTranscript(previousSummaries, regularGroups);
1841
+ const summary = await llmSummarize(transcript, config, options.apiKey, summaryBudget);
1842
+ const result = assembleFinal(systemMessages, summary.text, keepRecent);
1843
+ const tokensAfter = estimateMessageTokens(result);
1844
+ const stats = {
1845
+ strategy: "tier3_llm",
1846
+ messagesBefore: messages.length,
1847
+ messagesAfter: result.length,
1848
+ tokensBefore,
1849
+ tokensAfter,
1850
+ msElapsed: Date.now() - startMs,
1851
+ summaryTokens: estimateTokens(summary.text),
1852
+ llmInputTokens: summary.inputTokens,
1853
+ llmOutputTokens: summary.outputTokens,
1854
+ chunksUsed: summary.chunks,
1855
+ previousSummariesChained: previousSummaries.length
1856
+ };
1857
+ console.log(`[compaction] Tier 3 (LLM): ${stats.tokensBefore} \u2192 ${stats.tokensAfter} tokens in ${stats.msElapsed}ms (${summary.chunks} chunks, ${summary.inputTokens}in/${summary.outputTokens}out)`);
1858
+ await persistSummary(hooks, options?.sessionId, config.agentId, summary.text);
1859
+ return result;
1860
+ } catch (err) {
1861
+ console.warn(`[compaction] LLM summarization failed: ${err.message} \u2014 falling back to extractive`);
1862
+ const summary = buildExtractiveSummary(previousSummaries, regularGroups, summaryBudget);
1863
+ const result = assembleFinal(systemMessages, summary, keepRecent);
1864
+ await persistSummary(hooks, options?.sessionId, config.agentId, summary);
1865
+ console.log(`[compaction] Extractive fallback: ${tokensBefore} \u2192 ${estimateMessageTokens(result)} tokens in ${Date.now() - startMs}ms`);
1866
+ return result;
1867
+ }
1868
+ }
1869
+ function trimToolResults(messages) {
1870
+ return messages.map((msg) => {
1871
+ if (!Array.isArray(msg.content)) return msg;
1872
+ let modified = false;
1873
+ const newContent = msg.content.map((block) => {
1874
+ if (block.type === "tool_result") {
1875
+ const content = typeof block.content === "string" ? block.content : JSON.stringify(block.content);
1876
+ if (content.length > TOOL_RESULT_TRIM_THRESHOLD) {
1877
+ modified = true;
1878
+ return {
1879
+ ...block,
1880
+ content: content.slice(0, TOOL_RESULT_TRIM_TO) + `
1881
+ ... [trimmed ${content.length - TOOL_RESULT_TRIM_TO} chars]`
1882
+ };
1883
+ }
1884
+ }
1885
+ return block;
1886
+ });
1887
+ return modified ? { ...msg, content: newContent } : msg;
1888
+ });
1889
+ }
1890
+ function calculateKeepRecent(messages, targetTokens, systemTokens) {
1891
+ const keepBudget = Math.floor((targetTokens - systemTokens) * 0.6);
1892
+ let tokens = 0;
1893
+ let count = 0;
1894
+ for (let i = messages.length - 1; i >= 0; i--) {
1895
+ const msgTokens = estimateMessageTokens([messages[i]]);
1896
+ if (tokens + msgTokens > keepBudget && count >= MIN_KEEP_RECENT) break;
1897
+ tokens += msgTokens;
1898
+ count++;
1899
+ if (count >= MAX_KEEP_RECENT) break;
1900
+ }
1901
+ return Math.max(MIN_KEEP_RECENT, count);
1902
+ }
1903
+ function splitAtSafeBoundary(messages, keepCount) {
1904
+ let cutIndex = messages.length - keepCount;
1905
+ for (let i = cutIndex; i > 0; i--) {
1906
+ const msg = messages[i];
1907
+ if (msg.role === "user" && Array.isArray(msg.content)) {
1908
+ const hasToolResult = msg.content.some((b) => b.type === "tool_result");
1909
+ if (hasToolResult) continue;
1910
+ }
1911
+ cutIndex = i;
1912
+ break;
1913
+ }
1914
+ return {
1915
+ toSummarize: messages.slice(0, cutIndex),
1916
+ keepRecent: messages.slice(cutIndex)
1917
+ };
1918
+ }
1919
+ function groupMessages(messages) {
1920
+ const groups = [];
1921
+ let i = 0;
1922
+ while (i < messages.length) {
1923
+ const msg = messages[i];
1924
+ if (msg.role === "user" && typeof msg.content === "string" && msg.content.includes("[CONTEXT COMPACTION")) {
1925
+ groups.push({
1926
+ messages: [msg],
1927
+ tokens: estimateMessageTokens([msg]),
1928
+ importance: 10,
1929
+ // Highest — contains all prior context
1930
+ isPreviousSummary: true,
1931
+ isToolPair: false
1932
+ });
1933
+ i++;
1934
+ continue;
1935
+ }
1936
+ if (msg.role === "assistant" && Array.isArray(msg.content) && msg.content.some((b) => b.type === "tool_use") && i + 1 < messages.length) {
1937
+ const next = messages[i + 1];
1938
+ if (next.role === "user" && Array.isArray(next.content) && next.content.some((b) => b.type === "tool_result")) {
1939
+ const pair = [msg, next];
1940
+ groups.push({
1941
+ messages: pair,
1942
+ tokens: estimateMessageTokens(pair),
1943
+ importance: scoreImportance(pair),
1944
+ isToolPair: true,
1945
+ isPreviousSummary: false
1946
+ });
1947
+ i += 2;
1948
+ continue;
1949
+ }
1950
+ }
1951
+ groups.push({
1952
+ messages: [msg],
1953
+ tokens: estimateMessageTokens([msg]),
1954
+ importance: scoreImportance([msg]),
1955
+ isToolPair: false,
1956
+ isPreviousSummary: false
1957
+ });
1958
+ i++;
1959
+ }
1960
+ return groups;
1961
+ }
1962
+ function scoreImportance(messages) {
1963
+ let score = 1;
1964
+ for (const msg of messages) {
1965
+ const text = extractText(msg);
1966
+ if (msg.role === "user") score += 1;
1967
+ for (const pattern of HIGH_IMPORTANCE_PATTERNS) {
1968
+ if (pattern.test(text)) {
1969
+ score += 2;
1970
+ break;
1971
+ }
1972
+ }
1973
+ if (Array.isArray(msg.content)) {
1974
+ for (const block of msg.content) {
1975
+ if (block.type === "tool_result" && block.is_error) score += 3;
1976
+ }
1977
+ }
1978
+ if (text.length < 20) score -= 1;
1979
+ if (text.length > 5e3) score -= 1;
1980
+ }
1981
+ return Math.max(0, score);
1982
+ }
1983
+ function extractText(msg) {
1984
+ if (typeof msg.content === "string") return msg.content;
1985
+ if (Array.isArray(msg.content)) {
1986
+ return msg.content.map((b) => {
1987
+ if (b.type === "text") return b.text || "";
1988
+ if (b.type === "tool_use") return `${b.name}(${JSON.stringify(b.input || {}).slice(0, 200)})`;
1989
+ if (b.type === "tool_result") return String(b.content || "").slice(0, 500);
1990
+ return "";
1991
+ }).join(" ");
1992
+ }
1993
+ return "";
1994
+ }
1995
+ function buildExtractiveSummary(previousSummaries, groups, tokenBudget) {
1996
+ const parts = [];
1997
+ let usedTokens = 0;
1998
+ for (const sg of previousSummaries) {
1999
+ const text = extractText(sg.messages[0]);
2000
+ const content = text.replace(/^\[CONTEXT COMPACTION[^\]]*\]\s*/s, "");
2001
+ const tokens = estimateTokens(content);
2002
+ if (usedTokens + tokens < tokenBudget * 0.4) {
2003
+ parts.push("## Prior Context (from earlier compaction)\n" + content);
2004
+ usedTokens += tokens;
2005
+ }
2006
+ }
2007
+ parts.push("\n## Recent Activity Summary");
2008
+ for (const group of groups) {
2009
+ if (usedTokens >= tokenBudget) break;
2010
+ for (const msg of group.messages) {
2011
+ const text = extractText(msg);
2012
+ if (!text) continue;
2013
+ const maxLen = group.importance >= 5 ? 800 : group.importance >= 3 ? 400 : 200;
2014
+ const truncated = text.length > maxLen ? text.slice(0, maxLen) + "..." : text;
2015
+ const line = `[${msg.role}]: ${truncated}`;
2016
+ const lineTokens = estimateTokens(line);
2017
+ if (usedTokens + lineTokens > tokenBudget) break;
2018
+ parts.push(line);
2019
+ usedTokens += lineTokens;
2020
+ }
2021
+ }
2022
+ return parts.join("\n");
2023
+ }
2024
+ function buildTranscript(previousSummaries, groups) {
2025
+ const parts = [];
2026
+ for (const sg of previousSummaries) {
2027
+ const text = extractText(sg.messages[0]);
2028
+ const content = text.replace(/^\[CONTEXT COMPACTION[^\]]*\]\s*/s, "");
2029
+ parts.push("=== PRIOR COMPACTION SUMMARY ===\n" + content.slice(0, 2e4) + "\n=== END PRIOR SUMMARY ===");
2030
+ }
2031
+ const _chronoGroups = [...groups];
2032
+ for (const group of groups) {
2033
+ for (const msg of group.messages) {
2034
+ const text = extractText(msg);
2035
+ if (text.length > 0) {
2036
+ parts.push(`[${msg.role}]: ${text.slice(0, 1500)}`);
2037
+ }
2038
+ }
2039
+ }
2040
+ return parts.join("\n\n");
2041
+ }
2042
+ var SUMMARY_SYSTEM_PROMPT = `You are a context compaction engine for an AI agent mid-task. Create a dense, lossless summary that the agent MUST be able to continue working from without any other context.
2043
+
2044
+ RULES:
2045
+ - PRESERVE ALL: IDs, paths, URLs, emails, phone numbers, credentials (names only), version numbers, dates, amounts \u2014 use EXACT values
2046
+ - PRESERVE: Task goals, constraints, decisions made, errors encountered, workarounds found
2047
+ - PRESERVE: Current state \u2014 what was just done, what's next, any pending operations
2048
+ - COMPRESS: Routine tool calls (just note what tool was called and the outcome)
2049
+ - COMPRESS: Repeated similar operations (batch into counts: "read 12 files from /src/...")
2050
+ - OMIT: Pleasantries, acknowledgments, thinking-out-loud that didn't lead to decisions
2051
+ - FORMAT: Use ## headers for sections. Use bullet lists. Be dense but readable.
2052
+ - LENGTH: Use ALL available tokens. More detail = better continuation.
2053
+
2054
+ Required sections:
2055
+ ## Task & Goal
2056
+ ## Completed Work (chronological)
2057
+ ## Key Data (IDs, paths, URLs, names \u2014 EXACT values)
2058
+ ## Decisions & Rationale
2059
+ ## Current State
2060
+ ## Next Steps
2061
+ ## Errors & Lessons (if any)`;
2062
+ async function llmSummarize(transcript, config, apiKey, _tokenBudget) {
2063
+ if (transcript.length <= CHUNK_MAX_CHARS) {
2064
+ return singleChunkSummarize(transcript, config, apiKey);
2065
+ }
2066
+ const chunks = splitIntoChunks(transcript, CHUNK_MAX_CHARS);
2067
+ const limitedChunks = chunks.slice(0, MAX_PARALLEL_CHUNKS);
2068
+ console.log(`[compaction] Splitting transcript into ${limitedChunks.length} chunks for parallel summarization`);
2069
+ const chunkResults = await Promise.all(
2070
+ limitedChunks.map(
2071
+ (chunk, idx) => singleChunkSummarize(
2072
+ `[Chunk ${idx + 1}/${limitedChunks.length}]
2073
+ ${chunk}`,
2074
+ config,
2075
+ apiKey
2076
+ ).catch((err) => {
2077
+ console.warn(`[compaction] Chunk ${idx + 1} failed: ${err.message}`);
2078
+ return null;
2079
+ })
2080
+ )
2081
+ );
2082
+ const validResults = chunkResults.filter((r) => r !== null);
2083
+ if (validResults.length === 0) {
2084
+ throw new Error("All chunks failed");
2085
+ }
2086
+ if (validResults.length === 1) {
2087
+ return { ...validResults[0], chunks: limitedChunks.length };
2088
+ }
2089
+ const mergedTranscript = validResults.map((r, i) => `=== Part ${i + 1} ===
2090
+ ${r.text}`).join("\n\n");
2091
+ const merged = await singleChunkSummarize(
2092
+ `Merge these partial summaries into one cohesive summary:
2093
+
2094
+ ${mergedTranscript}`,
2095
+ config,
2096
+ apiKey
2097
+ );
2098
+ return {
2099
+ text: merged.text,
2100
+ inputTokens: validResults.reduce((s, r) => s + r.inputTokens, 0) + merged.inputTokens,
2101
+ outputTokens: validResults.reduce((s, r) => s + r.outputTokens, 0) + merged.outputTokens,
2102
+ chunks: limitedChunks.length
2103
+ };
2104
+ }
2105
+ async function singleChunkSummarize(transcript, config, apiKey) {
2106
+ const response = await callLLM(
2107
+ {
2108
+ provider: config.model.provider,
2109
+ modelId: config.model.modelId,
2110
+ apiKey
2111
+ },
2112
+ [
2113
+ { role: "system", content: SUMMARY_SYSTEM_PROMPT },
2114
+ { role: "user", content: `Summarize this conversation:
2115
+
2116
+ ${transcript}` }
2117
+ ],
2118
+ [],
2119
+ { maxTokens: SUMMARY_MAX_TOKENS, temperature: 0.2 }
2120
+ );
2121
+ const text = response.textContent || "";
2122
+ if (text.length < 50) throw new Error("Summary too short");
2123
+ return {
2124
+ text,
2125
+ inputTokens: response.usage?.inputTokens || 0,
2126
+ outputTokens: response.usage?.outputTokens || 0,
2127
+ chunks: 1
2128
+ };
2129
+ }
2130
+ function splitIntoChunks(text, maxChars) {
2131
+ const chunks = [];
2132
+ let start = 0;
2133
+ while (start < text.length) {
2134
+ let end = Math.min(start + maxChars, text.length);
2135
+ if (end < text.length) {
2136
+ const lastParagraph = text.lastIndexOf("\n\n", end);
2137
+ if (lastParagraph > start + maxChars * 0.5) {
2138
+ end = lastParagraph + 2;
2139
+ }
2140
+ }
2141
+ chunks.push(text.slice(start, end));
2142
+ start = end;
2143
+ }
2144
+ return chunks;
2145
+ }
2146
+ function assembleFinal(systemMessages, summaryText, keepRecent) {
2147
+ const summaryMessage = {
2148
+ role: "user",
2149
+ content: `[CONTEXT COMPACTION \u2014 Your earlier conversation was compressed to fit the context window. The summary below is authoritative \u2014 treat it as ground truth. Continue from where you left off.]
2150
+
2151
+ ${summaryText}`
2152
+ };
2153
+ return [...systemMessages, summaryMessage, ...keepRecent];
2154
+ }
2155
+ async function persistSummary(hooks, sessionId, agentId, summaryText) {
2156
+ try {
2157
+ await hooks.onContextCompaction(sessionId || "", agentId, summaryText);
2158
+ console.log(`[compaction] Summary persisted to agent memory`);
2159
+ } catch (err) {
2160
+ console.warn(`[compaction] Memory save failed: ${err?.message}`);
2161
+ }
2162
+ }
2163
+
2164
+ // src/runtime/agent-loop.ts
2165
+ var DEFAULT_MAX_TURNS = 0;
2166
+ var DEFAULT_MAX_TOKENS = 8192;
2167
+ var DEFAULT_TEMPERATURE = 0.7;
2168
+ var DEFAULT_CONTEXT_WINDOW = 2e6;
2169
+ function fixOrphanedToolBlocks(messages) {
2170
+ let fixed = false;
2171
+ const allToolUseIds = /* @__PURE__ */ new Set();
2172
+ const allToolResultIds = /* @__PURE__ */ new Set();
2173
+ for (const m of messages) {
2174
+ if (!Array.isArray(m.content)) continue;
2175
+ for (const block of m.content) {
2176
+ if (block.type === "tool_use" && block.id) allToolUseIds.add(block.id);
2177
+ if (block.type === "tool_result" && block.tool_use_id) allToolResultIds.add(block.tool_use_id);
2178
+ }
2179
+ }
2180
+ let result = messages.map((m) => {
2181
+ if (!Array.isArray(m.content)) return m;
2182
+ if (m.role === "user") {
2183
+ const filtered = m.content.filter((block) => {
2184
+ if (block.type === "tool_result" && block.tool_use_id && !allToolUseIds.has(block.tool_use_id)) {
2185
+ fixed = true;
2186
+ return false;
2187
+ }
2188
+ return true;
2189
+ });
2190
+ if (filtered.length === 0) return null;
2191
+ if (filtered.length !== m.content.length) return { ...m, content: filtered };
2192
+ return m;
2193
+ }
2194
+ if (m.role === "assistant") {
2195
+ const filtered = m.content.filter((block) => {
2196
+ if (block.type === "tool_use" && block.id && !allToolResultIds.has(block.id)) {
2197
+ fixed = true;
2198
+ return false;
2199
+ }
2200
+ return true;
2201
+ });
2202
+ if (filtered.length === 0) return null;
2203
+ if (filtered.length !== m.content.length) return { ...m, content: filtered };
2204
+ return m;
2205
+ }
2206
+ return m;
2207
+ }).filter(Boolean);
2208
+ for (let i = 0; i < result.length; i++) {
2209
+ const m = result[i];
2210
+ if (m.role !== "assistant" || !Array.isArray(m.content)) continue;
2211
+ const toolUseIds = m.content.filter((b) => b.type === "tool_use" && b.id).map((b) => b.id);
2212
+ if (toolUseIds.length === 0) continue;
2213
+ const next = result[i + 1];
2214
+ if (next && next.role === "user" && Array.isArray(next.content)) {
2215
+ const hasResults = next.content.some((b) => b.type === "tool_result" && toolUseIds.includes(b.tool_use_id));
2216
+ if (hasResults) continue;
2217
+ }
2218
+ for (let j = i + 2; j < result.length; j++) {
2219
+ const candidate = result[j];
2220
+ if (candidate.role !== "user" || !Array.isArray(candidate.content)) continue;
2221
+ const matchingResults = candidate.content.filter((b) => b.type === "tool_result" && toolUseIds.includes(b.tool_use_id));
2222
+ if (matchingResults.length === 0) continue;
2223
+ const remaining = candidate.content.filter((b) => !(b.type === "tool_result" && toolUseIds.includes(b.tool_use_id)));
2224
+ const toolResultMsg = { role: "user", content: matchingResults };
2225
+ if (remaining.length === 0) {
2226
+ result.splice(j, 1);
2227
+ } else {
2228
+ result[j] = { ...candidate, content: remaining };
2229
+ }
2230
+ result.splice(i + 1, 0, toolResultMsg);
2231
+ fixed = true;
2232
+ console.log(`[agent-loop] Reordered tool_result to fix adjacency (moved from index ${j} to ${i + 1})`);
2233
+ break;
2234
+ }
2235
+ const nextAfterFix = result[i + 1];
2236
+ const adjacencyOk = nextAfterFix && nextAfterFix.role === "user" && Array.isArray(nextAfterFix.content) && nextAfterFix.content.some((b) => b.type === "tool_result" && toolUseIds.includes(b.tool_use_id));
2237
+ if (!adjacencyOk) {
2238
+ const filtered = m.content.filter((b) => !(b.type === "tool_use" && toolUseIds.includes(b.id)));
2239
+ if (filtered.length === 0) {
2240
+ result.splice(i, 1);
2241
+ i--;
2242
+ } else {
2243
+ result[i] = { ...m, content: filtered };
2244
+ }
2245
+ fixed = true;
2246
+ }
2247
+ }
2248
+ if (fixed) console.log(`[agent-loop] Fixed orphaned/non-adjacent tool_use/tool_result blocks`);
2249
+ return result;
2250
+ }
2251
+ async function runAgentLoop(config, initialMessages, hooks, options) {
2252
+ var maxTurns = config.maxTurns ?? DEFAULT_MAX_TURNS;
2253
+ var maxTokens = config.maxTokens ?? DEFAULT_MAX_TOKENS;
2254
+ var temperature = config.temperature ?? DEFAULT_TEMPERATURE;
2255
+ var contextWindowSize = config.contextWindowSize ?? DEFAULT_CONTEXT_WINDOW;
2256
+ var toolTimeout = options.toolTimeoutMs ?? 12e4;
2257
+ var registry = new ToolRegistry();
2258
+ registry.register(config.tools);
2259
+ var toolDefs = toolsToDefinitions(config.tools);
2260
+ globalThis.__currentSessionTools = config.tools;
2261
+ var messages = [];
2262
+ if (config.systemPrompt) {
2263
+ messages.push({ role: "system", content: config.systemPrompt });
2264
+ }
2265
+ messages.push(...initialMessages);
2266
+ var turnCount = 0;
2267
+ var totalTextContent = "";
2268
+ var lastStopReason = "end_turn";
2269
+ var sessionId = options.sessionId || "";
2270
+ var cumulativeUsage = { inputTokens: 0, outputTokens: 0, costUsd: 0 };
2271
+ var sessionStartEvent = { type: "session_start", sessionId: config.agentId };
2272
+ options.onEvent?.(sessionStartEvent);
2273
+ while (maxTurns === 0 || turnCount < maxTurns) {
2274
+ if (options.signal?.aborted) {
2275
+ return buildResult(messages, "paused", turnCount, totalTextContent, "aborted", cumulativeUsage);
2276
+ }
2277
+ turnCount++;
2278
+ if (options.onHeartbeat) {
2279
+ try {
2280
+ await options.onHeartbeat({ turnCount, tokenCount: estimateMessageTokens(messages), timestamp: Date.now() });
2281
+ } catch {
2282
+ }
2283
+ }
2284
+ var turnStartEvent = { type: "turn_start", turnNumber: turnCount };
2285
+ options.onEvent?.(turnStartEvent);
2286
+ try {
2287
+ messages = await hooks.beforeLLMCall(messages, config.agentId, sessionId);
2288
+ } catch (err) {
2289
+ console.warn(`[runtime] beforeLLMCall hook error: ${err.message}`);
2290
+ }
2291
+ var estimatedTokens = estimateMessageTokens(messages);
2292
+ if (estimatedTokens > contextWindowSize * COMPACTION_THRESHOLD) {
2293
+ messages = await compactContext(messages, config, hooks, { apiKey: options.apiKey, sessionId: options.sessionId });
2294
+ }
2295
+ messages = fixOrphanedToolBlocks(messages);
2296
+ if (hooks.checkBudget) {
2297
+ try {
2298
+ var budgetResult = await hooks.checkBudget(config.agentId, config.orgId, estimateMessageTokens(messages));
2299
+ if (!budgetResult.allowed) {
2300
+ console.log(`[agent-loop] BUDGET EXCEEDED for agent ${config.agentId}: ${budgetResult.reason}`);
2301
+ var budgetEvent = { type: "budget_exceeded", reason: budgetResult.reason || "Budget limit reached" };
2302
+ options.onEvent?.(budgetEvent);
2303
+ return buildResult(messages, "completed", turnCount, totalTextContent, "budget_exceeded", cumulativeUsage);
2304
+ }
2305
+ if (budgetResult.remainingUsd !== void 0 && budgetResult.remainingUsd < 1) {
2306
+ var warnEvent = { type: "budget_warning", remainingUsd: budgetResult.remainingUsd, usedUsd: 0 };
2307
+ options.onEvent?.(warnEvent);
2308
+ }
2309
+ } catch {
2310
+ }
2311
+ }
2312
+ var llmCallStart = Date.now();
2313
+ var llmResponse = void 0;
2314
+ var llmRetryMax = 3;
2315
+ var llmRetryMaxRateLimit = 5;
2316
+ var llmRetryDelay = 2e3;
2317
+ for (var llmAttempt = 0; llmAttempt <= llmRetryMax; llmAttempt++) {
2318
+ try {
2319
+ llmResponse = await callLLM(
2320
+ {
2321
+ provider: config.model.provider,
2322
+ modelId: config.model.modelId,
2323
+ apiKey: options.apiKey,
2324
+ thinkingLevel: config.model.thinkingLevel,
2325
+ baseUrl: config.model.baseUrl,
2326
+ headers: config.model.headers,
2327
+ authMode: config.model.authMode
2328
+ },
2329
+ messages,
2330
+ toolDefs,
2331
+ { maxTokens, temperature, signal: options.signal },
2332
+ options.onEvent,
2333
+ options.retryConfig
2334
+ );
2335
+ console.log(`[agent-loop] LLM responded in ${Date.now() - llmCallStart}ms (${estimateMessageTokens(messages)} input tokens)`);
2336
+ break;
2337
+ } catch (err) {
2338
+ var isOrphanError = /tool_use_id.*tool_result|tool_use.*without.*tool_result|tool_result.*without.*tool_use/i.test(err.message);
2339
+ if (isOrphanError && llmAttempt < llmRetryMax) {
2340
+ var idMatch = err.message.match(/toolu_[A-Za-z0-9_]+/g);
2341
+ var orphanIds = idMatch ? new Set(idMatch) : null;
2342
+ console.warn(`[agent-loop] Orphaned tool blocks detected (ids: ${idMatch?.join(", ") || "unknown"}) \u2014 force-removing and retrying`);
2343
+ messages = fixOrphanedToolBlocks(messages);
2344
+ if (orphanIds) {
2345
+ messages = messages.map((m) => {
2346
+ if (!Array.isArray(m.content)) return m;
2347
+ var filtered = m.content.filter((block) => {
2348
+ if (block.type === "tool_use" && block.id && orphanIds.has(block.id)) return false;
2349
+ if (block.type === "tool_result" && block.tool_use_id && orphanIds.has(block.tool_use_id)) return false;
2350
+ return true;
2351
+ });
2352
+ if (filtered.length === 0) return null;
2353
+ if (filtered.length !== m.content.length) return { ...m, content: filtered };
2354
+ return m;
2355
+ }).filter(Boolean);
2356
+ }
2357
+ continue;
2358
+ }
2359
+ var isRateLimit = /429|rate.limit|rate_limit/i.test(err.message);
2360
+ var isTransient = isRateLimit || /premature close|ECONNRESET|ETIMEDOUT|socket hang up|fetch failed|network|502|503|529|overloaded/i.test(err.message);
2361
+ var effectiveMax = isRateLimit ? llmRetryMaxRateLimit : llmRetryMax;
2362
+ if (isTransient && llmAttempt < effectiveMax) {
2363
+ var delay = isRateLimit ? Math.min(3e4 * Math.pow(2, llmAttempt), 12e4) : llmRetryDelay * Math.pow(2, llmAttempt);
2364
+ console.warn(`[agent-loop] LLM call failed (attempt ${llmAttempt + 1}/${llmRetryMax + 1}): ${err.message} \u2014 retrying in ${delay}ms`);
2365
+ await new Promise((r) => setTimeout(r, delay));
2366
+ continue;
2367
+ }
2368
+ var fallbackModels = options.fallbackModels;
2369
+ if (fallbackModels && fallbackModels.length > 0) {
2370
+ var primaryModel = config.model.provider + "/" + config.model.modelId;
2371
+ var remainingFallbacks = fallbackModels.filter(function(m) {
2372
+ return m !== primaryModel;
2373
+ });
2374
+ for (var fi = 0; fi < remainingFallbacks.length; fi++) {
2375
+ var fbModel = remainingFallbacks[fi];
2376
+ var fbParts = fbModel.split("/");
2377
+ var fbProvider = fbParts[0];
2378
+ var fbModelId = fbParts.slice(1).join("/");
2379
+ console.log(`[agent-loop] Primary model failed \u2014 trying fallback ${fi + 1}/${remainingFallbacks.length}: ${fbModel}`);
2380
+ try {
2381
+ var fbApiKey = options.apiKey;
2382
+ if (fbProvider !== config.model.provider && options.resolveApiKey) {
2383
+ var resolved = await options.resolveApiKey(fbProvider);
2384
+ if (resolved) fbApiKey = resolved;
2385
+ }
2386
+ llmResponse = await callLLM(
2387
+ { provider: fbProvider, modelId: fbModelId, apiKey: fbApiKey, thinkingLevel: config.model.thinkingLevel },
2388
+ messages,
2389
+ toolDefs,
2390
+ { maxTokens, temperature, signal: options.signal },
2391
+ options.onEvent,
2392
+ options.retryConfig
2393
+ );
2394
+ console.log(`[agent-loop] Fallback model ${fbModel} succeeded in ${Date.now() - llmCallStart}ms`);
2395
+ break;
2396
+ } catch (fbErr) {
2397
+ console.warn(`[agent-loop] Fallback model ${fbModel} also failed: ${fbErr.message}`);
2398
+ if (fi === remainingFallbacks.length - 1) {
2399
+ console.error(`[agent-loop] All models failed (primary + ${remainingFallbacks.length} fallbacks): ${err.message}`);
2400
+ var errorEvent = { type: "error", message: `All models failed. Primary: ${err.message}`, retryable: false };
2401
+ options.onEvent?.(errorEvent);
2402
+ return buildResult(messages, "failed", turnCount, totalTextContent, "error", cumulativeUsage);
2403
+ }
2404
+ }
2405
+ }
2406
+ if (llmResponse) break;
2407
+ }
2408
+ if (!llmResponse) {
2409
+ console.error(`[agent-loop] LLM call failed after ${llmAttempt + 1} attempts: ${err.message}`);
2410
+ var errorEvent2 = { type: "error", message: err.message, retryable: isTransient };
2411
+ options.onEvent?.(errorEvent2);
2412
+ return buildResult(messages, "failed", turnCount, totalTextContent, "error", cumulativeUsage);
2413
+ }
2414
+ }
2415
+ }
2416
+ if (hooks.recordLLMUsage) {
2417
+ try {
2418
+ var usageInput = llmResponse.usage?.inputTokens || 0;
2419
+ var usageOutput = llmResponse.usage?.outputTokens || 0;
2420
+ if (usageInput === 0 && usageOutput === 0) {
2421
+ usageInput = Math.ceil(estimateMessageTokens(messages) * 0.9);
2422
+ usageOutput = Math.ceil((llmResponse.textContent?.length || 100) / 4);
2423
+ }
2424
+ var costUsd = await estimateCostAsync(hooks, config.model, usageInput, usageOutput);
2425
+ await hooks.recordLLMUsage(config.agentId, config.orgId, {
2426
+ inputTokens: usageInput,
2427
+ outputTokens: usageOutput,
2428
+ costUsd
2429
+ });
2430
+ cumulativeUsage.inputTokens += usageInput;
2431
+ cumulativeUsage.outputTokens += usageOutput;
2432
+ cumulativeUsage.costUsd += costUsd;
2433
+ } catch {
2434
+ }
2435
+ }
2436
+ var assistantMessage = buildAssistantMessage(llmResponse);
2437
+ messages.push(assistantMessage);
2438
+ totalTextContent += llmResponse.textContent;
2439
+ if (llmResponse.stopReason === "end_turn") {
2440
+ lastStopReason = "end_turn";
2441
+ var turnEndEvent = { type: "turn_end", stopReason: "end_turn" };
2442
+ options.onEvent?.(turnEndEvent);
2443
+ if (options.onCheckpoint) {
2444
+ try {
2445
+ var currentTokens = estimateMessageTokens(messages);
2446
+ await options.onCheckpoint({ messages, turnCount, tokenCount: currentTokens });
2447
+ } catch (err) {
2448
+ console.warn("[runtime] Checkpoint save error:", err.message);
2449
+ }
2450
+ }
2451
+ break;
2452
+ }
2453
+ if (llmResponse.stopReason === "tool_use" && llmResponse.toolCalls.length > 0) {
2454
+ lastStopReason = "tool_use";
2455
+ var toolResults = [];
2456
+ for (var toolCall of llmResponse.toolCalls) {
2457
+ var hookResult = await hooks.beforeToolCall({
2458
+ toolCallId: toolCall.id,
2459
+ toolName: toolCall.name,
2460
+ parameters: toolCall.input,
2461
+ agentId: config.agentId,
2462
+ orgId: config.orgId,
2463
+ sessionId
2464
+ });
2465
+ if (!hookResult.allowed) {
2466
+ console.log(`[agent-loop] \u{1F6AB} Tool ${toolCall.name} BLOCKED: ${hookResult.reason} (session: ${sessionId})`);
2467
+ var blockedEvent = {
2468
+ type: "tool_call_end",
2469
+ toolName: toolCall.name,
2470
+ result: hookResult.reason,
2471
+ blocked: true
2472
+ };
2473
+ options.onEvent?.(blockedEvent);
2474
+ toolResults.push({
2475
+ tool_use_id: toolCall.id,
2476
+ content: `Tool call blocked: ${hookResult.reason}`,
2477
+ is_error: true
2478
+ });
2479
+ await hooks.afterToolCall(
2480
+ { toolCallId: toolCall.id, toolName: toolCall.name, parameters: toolCall.input, agentId: config.agentId, orgId: config.orgId, sessionId },
2481
+ { success: false, error: hookResult.reason }
2482
+ );
2483
+ continue;
2484
+ }
2485
+ var effectiveInput = hookResult.modifiedParameters || toolCall.input;
2486
+ var effectiveToolCall = { ...toolCall, input: effectiveInput };
2487
+ var tool = registry.get(toolCall.name);
2488
+ if (!tool) {
2489
+ toolResults.push({
2490
+ tool_use_id: toolCall.id,
2491
+ content: `Unknown tool: "${toolCall.name}"`,
2492
+ is_error: true
2493
+ });
2494
+ var unknownEvent = {
2495
+ type: "tool_call_end",
2496
+ toolName: toolCall.name,
2497
+ result: "unknown tool",
2498
+ blocked: true
2499
+ };
2500
+ options.onEvent?.(unknownEvent);
2501
+ continue;
2502
+ }
2503
+ console.log(`[agent-loop] \u{1F527} Executing tool: ${toolCall.name} (session: ${sessionId})`);
2504
+ try {
2505
+ const { describeToolActivity } = await import("./agent-status-VQORDJXV.js");
2506
+ if (options.runtime?._reportStatus) {
2507
+ options.runtime._reportStatus({
2508
+ status: "online",
2509
+ currentActivity: { type: "tool_call", detail: describeToolActivity(toolCall.name), tool: toolCall.name, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
2510
+ });
2511
+ }
2512
+ } catch {
2513
+ }
2514
+ var { result, content } = await executeTool(tool, effectiveToolCall, {
2515
+ timeoutMs: toolTimeout,
2516
+ signal: options.signal
2517
+ });
2518
+ if (!result.success) {
2519
+ console.log(`[agent-loop] \u274C Tool ${toolCall.name} failed: ${result.error?.slice(0, 200)}`);
2520
+ } else {
2521
+ console.log(`[agent-loop] \u2705 Tool ${toolCall.name} succeeded (${content.length} chars): ${content.slice(0, 300)}`);
2522
+ }
2523
+ try {
2524
+ const { activity } = await import("./routes-UIUIEZKQ.js");
2525
+ activity.recordToolCallCompact({
2526
+ agentId: config.agentId,
2527
+ orgId: config.orgId,
2528
+ sessionId,
2529
+ tool: toolCall.name,
2530
+ success: !!result.success,
2531
+ durationMs: result.durationMs,
2532
+ error: result.error?.slice(0, 200)
2533
+ });
2534
+ } catch {
2535
+ }
2536
+ try {
2537
+ if (options.runtime?._reportStatus) {
2538
+ options.runtime._reportStatus({ currentActivity: null });
2539
+ }
2540
+ } catch {
2541
+ }
2542
+ if (result._dynamicTools?.length) {
2543
+ var newTools = result._dynamicTools;
2544
+ var existingNames = new Set(config.tools.map((t) => t.name));
2545
+ var added = 0;
2546
+ for (var nt of newTools) {
2547
+ if (!existingNames.has(nt.name)) {
2548
+ config.tools.push(nt);
2549
+ toolDefs.push({ name: nt.name, description: nt.description, input_schema: nt.parameters });
2550
+ existingNames.add(nt.name);
2551
+ added++;
2552
+ }
2553
+ }
2554
+ if (added) {
2555
+ console.log(`[agent-loop] Dynamically loaded ${added} new tools`);
2556
+ globalThis.__currentSessionTools = config.tools;
2557
+ }
2558
+ }
2559
+ toolResults.push({
2560
+ tool_use_id: toolCall.id,
2561
+ content,
2562
+ is_error: !result.success
2563
+ });
2564
+ var tcEndEvent = {
2565
+ type: "tool_call_end",
2566
+ toolName: toolCall.name,
2567
+ result: result.success ? "success" : result.error
2568
+ };
2569
+ options.onEvent?.(tcEndEvent);
2570
+ await hooks.afterToolCall(
2571
+ { toolCallId: toolCall.id, toolName: toolCall.name, parameters: effectiveInput, agentId: config.agentId, orgId: config.orgId, sessionId },
2572
+ result
2573
+ );
2574
+ }
2575
+ messages.push({
2576
+ role: "user",
2577
+ content: toolResults.map(function(tr) {
2578
+ return { type: "tool_result", tool_use_id: tr.tool_use_id, content: tr.content, is_error: tr.is_error };
2579
+ }),
2580
+ tool_results: toolResults.map(function(tr) {
2581
+ return { tool_use_id: tr.tool_use_id, content: tr.content, is_error: tr.is_error };
2582
+ })
2583
+ });
2584
+ if (options.onCheckpoint) {
2585
+ try {
2586
+ var currentTokens = estimateMessageTokens(messages);
2587
+ await options.onCheckpoint({ messages, turnCount, tokenCount: currentTokens });
2588
+ } catch (err) {
2589
+ console.warn("[runtime] Checkpoint save error:", err.message);
2590
+ }
2591
+ }
2592
+ var TERMINAL_TOOLS = /* @__PURE__ */ new Set([
2593
+ "google_chat_send_message",
2594
+ "google_chat_send_card",
2595
+ "google_chat_send_dm",
2596
+ "google_chat_reply_message"
2597
+ ]);
2598
+ var allTerminal = llmResponse.toolCalls.length > 0 && llmResponse.toolCalls.every(function(tc) {
2599
+ return TERMINAL_TOOLS.has(tc.name);
2600
+ }) && toolResults.every(function(tr) {
2601
+ return !tr.is_error;
2602
+ });
2603
+ if (allTerminal) {
2604
+ console.log(`[agent-loop] \u26A1 Auto-end: all tools were terminal send tools, skipping confirmation LLM call`);
2605
+ lastStopReason = "end_turn";
2606
+ var autoEndEvent = { type: "turn_end", stopReason: "end_turn" };
2607
+ options.onEvent?.(autoEndEvent);
2608
+ break;
2609
+ }
2610
+ continue;
2611
+ }
2612
+ if (llmResponse.stopReason === "max_tokens") {
2613
+ lastStopReason = "max_tokens";
2614
+ messages = await compactContext(messages, config, hooks, { apiKey: options.apiKey, sessionId: options.sessionId });
2615
+ messages = fixOrphanedToolBlocks(messages);
2616
+ if (options.onCheckpoint) {
2617
+ try {
2618
+ var currentTokens = estimateMessageTokens(messages);
2619
+ await options.onCheckpoint({ messages, turnCount, tokenCount: currentTokens });
2620
+ } catch (err) {
2621
+ console.warn("[runtime] Checkpoint save error:", err.message);
2622
+ }
2623
+ }
2624
+ continue;
2625
+ }
2626
+ lastStopReason = llmResponse.stopReason;
2627
+ break;
2628
+ }
2629
+ if (maxTurns > 0 && turnCount >= maxTurns) {
2630
+ lastStopReason = "max_turns";
2631
+ }
2632
+ var sessionEndEvent = {
2633
+ type: "session_end",
2634
+ summary: totalTextContent.slice(0, 200)
2635
+ };
2636
+ options.onEvent?.(sessionEndEvent);
2637
+ return buildResult(
2638
+ messages,
2639
+ lastStopReason === "end_turn" ? "completed" : lastStopReason === "max_turns" ? "completed" : "completed",
2640
+ turnCount,
2641
+ totalTextContent,
2642
+ lastStopReason,
2643
+ cumulativeUsage
2644
+ );
2645
+ }
2646
+ var FALLBACK_PRICES = {
2647
+ // Anthropic (Feb 2026 — 1M context window)
2648
+ "claude-opus-4-6": { input: 5, output: 25 },
2649
+ "claude-sonnet-4-6": { input: 3, output: 15 },
2650
+ "claude-sonnet-4-5-20250929": { input: 3, output: 15 },
2651
+ "claude-haiku-4-5-20251001": { input: 0.8, output: 4 },
2652
+ // OpenAI
2653
+ "gpt-4o": { input: 2.5, output: 10 },
2654
+ "gpt-4o-mini": { input: 0.15, output: 0.6 },
2655
+ "gpt-4.1": { input: 2, output: 8 },
2656
+ "gpt-4.1-mini": { input: 0.4, output: 1.6 },
2657
+ "gpt-4.1-nano": { input: 0.1, output: 0.4 },
2658
+ "o3": { input: 10, output: 40 },
2659
+ "o4-mini": { input: 1.1, output: 4.4 },
2660
+ // Google Gemini (up to 2M context)
2661
+ "gemini-2.5-pro": { input: 2.5, output: 15 },
2662
+ "gemini-2.5-flash": { input: 0.15, output: 0.6 },
2663
+ "gemini-2.0-flash": { input: 0.1, output: 0.4 },
2664
+ "gemini-3-pro": { input: 2.5, output: 15 },
2665
+ // DeepSeek (128K context)
2666
+ "deepseek-chat": { input: 0.14, output: 0.28 },
2667
+ "deepseek-chat-v3": { input: 0.14, output: 0.28 },
2668
+ "deepseek-reasoner": { input: 0.55, output: 2.19 },
2669
+ // xAI Grok (2M context window)
2670
+ "grok-4": { input: 3, output: 15 },
2671
+ "grok-4-fast": { input: 0.2, output: 0.5 },
2672
+ "grok-3": { input: 3, output: 15 },
2673
+ "grok-3-mini": { input: 0.3, output: 0.5 },
2674
+ // Mistral
2675
+ "mistral-large-latest": { input: 2, output: 6 },
2676
+ "mistral-small-latest": { input: 0.1, output: 0.3 },
2677
+ "codestral-latest": { input: 0.3, output: 0.9 },
2678
+ // Meta Llama (via Groq, Together, etc.)
2679
+ "llama-3.3-70b-versatile": { input: 0.59, output: 0.79 },
2680
+ "llama-3.1-8b-instant": { input: 0.05, output: 0.08 },
2681
+ "meta-llama/Llama-3.3-70B-Instruct-Turbo": { input: 0.88, output: 0.88 }
2682
+ };
2683
+ var _PRICING_CACHE_TTL = 5 * 6e4;
2684
+ async function estimateCostAsync(hooks, model, inputTokens, outputTokens) {
2685
+ if (hooks.getModelPricing) {
2686
+ try {
2687
+ var dbPricing = await hooks.getModelPricing(model.provider, model.modelId);
2688
+ if (dbPricing) {
2689
+ return (inputTokens * dbPricing.inputCostPerMillion + outputTokens * dbPricing.outputCostPerMillion) / 1e6;
2690
+ }
2691
+ } catch {
2692
+ }
2693
+ }
2694
+ var p = FALLBACK_PRICES[model.modelId] || { input: 3, output: 15 };
2695
+ return (inputTokens * p.input + outputTokens * p.output) / 1e6;
2696
+ }
2697
+ function buildAssistantMessage(response) {
2698
+ var content = [];
2699
+ if (response.thinkingContent) {
2700
+ content.push({ type: "thinking", thinking: response.thinkingContent });
2701
+ }
2702
+ if (response.textContent) {
2703
+ content.push({ type: "text", text: response.textContent });
2704
+ }
2705
+ for (var tc of response.toolCalls) {
2706
+ content.push({ type: "tool_use", id: tc.id, name: tc.name, input: tc.input });
2707
+ }
2708
+ return {
2709
+ role: "assistant",
2710
+ content: content.length === 1 && content[0].type === "text" ? content[0].text : content,
2711
+ tool_calls: response.toolCalls.length > 0 ? response.toolCalls : void 0
2712
+ };
2713
+ }
2714
+ function buildResult(messages, status, turnCount, textContent, stopReason, usage) {
2715
+ return {
2716
+ messages,
2717
+ status,
2718
+ tokenCount: estimateMessageTokens(messages),
2719
+ turnCount,
2720
+ textContent,
2721
+ stopReason,
2722
+ usage
2723
+ };
2724
+ }
2725
+
2726
+ // src/agent-tools/tool-resolver.ts
2727
+ var TIER_MAP = {
2728
+ // Tier 1 — Agent can't function without these
2729
+ core: 1,
2730
+ // read/write/search/bash — fundamental
2731
+ memory: 1,
2732
+ // agent memory is always needed
2733
+ // Tier 2 — Common workflows, loaded by context
2734
+ gws_chat: 2,
2735
+ // only when source is Google Chat
2736
+ browser: 2,
2737
+ system: 2,
2738
+ visual_memory: 2,
2739
+ meeting_voice: 2,
2740
+ meeting_lifecycle: 2,
2741
+ gws_gmail: 2,
2742
+ gws_calendar: 2,
2743
+ gws_drive: 2,
2744
+ gws_docs: 2,
2745
+ gws_tasks: 2,
2746
+ gws_contacts: 2,
2747
+ agenticmail: 2,
2748
+ ent_http: 2,
2749
+ // Tier 3 — Specialist, on-demand only
2750
+ gws_sheets: 3,
2751
+ gws_slides: 3,
2752
+ gws_forms: 3,
2753
+ gws_maps: 3,
2754
+ ent_database: 3,
2755
+ ent_spreadsheet: 3,
2756
+ ent_documents: 3,
2757
+ ent_security: 3,
2758
+ ent_code: 3,
2759
+ ent_diff: 3,
2760
+ remotion_video: 3,
2761
+ ent_knowledge: 2,
2762
+ local_filesystem: 2,
2763
+ local_shell: 2,
2764
+ management: 1,
2765
+ // always loaded — agent must know its position
2766
+ msg_whatsapp: 2,
2767
+ msg_telegram: 2,
2768
+ mcp_bridge: 3,
2769
+ filesystem: 2,
2770
+ web: 2,
2771
+ smtp_email: 2
2772
+ };
2773
+ var TOOL_REGISTRY = {
2774
+ // ── Core (8) ──
2775
+ read: "core",
2776
+ write: "core",
2777
+ edit: "core",
2778
+ bash: "core",
2779
+ glob: "core",
2780
+ grep: "core",
2781
+ web_fetch: "core",
2782
+ web_search: "core",
2783
+ // ── Browser (1) ──
2784
+ browser: "browser",
2785
+ // ── System (1) ──
2786
+ system_capabilities: "system",
2787
+ // ── Memory (4) ──
2788
+ memory: "memory",
2789
+ memory_context: "memory",
2790
+ memory_reflect: "memory",
2791
+ memory_stats: "memory",
2792
+ // ── Visual Memory (10) ──
2793
+ vision_capture: "visual_memory",
2794
+ vision_compare: "visual_memory",
2795
+ vision_diff: "visual_memory",
2796
+ vision_health: "visual_memory",
2797
+ vision_ocr: "visual_memory",
2798
+ vision_query: "visual_memory",
2799
+ vision_session_end: "visual_memory",
2800
+ vision_session_start: "visual_memory",
2801
+ vision_similar: "visual_memory",
2802
+ vision_track: "visual_memory",
2803
+ // ── Meeting Voice (4) ──
2804
+ meeting_speak: "meeting_voice",
2805
+ meeting_action: "meeting_voice",
2806
+ meeting_audio_setup: "meeting_voice",
2807
+ meeting_voices: "meeting_voice",
2808
+ // ── Meeting Lifecycle (8) ──
2809
+ meeting_join: "meeting_lifecycle",
2810
+ meeting_rsvp: "meeting_lifecycle",
2811
+ meeting_can_join: "meeting_lifecycle",
2812
+ meeting_prepare: "meeting_lifecycle",
2813
+ meeting_record: "meeting_lifecycle",
2814
+ meeting_save: "meeting_lifecycle",
2815
+ meetings_scan_inbox: "meeting_lifecycle",
2816
+ meetings_upcoming: "meeting_lifecycle",
2817
+ // ── Google Chat (14) ──
2818
+ google_chat_send_message: "gws_chat",
2819
+ google_chat_list_messages: "gws_chat",
2820
+ google_chat_list_spaces: "gws_chat",
2821
+ google_chat_get_space: "gws_chat",
2822
+ google_chat_find_dm: "gws_chat",
2823
+ google_chat_add_member: "gws_chat",
2824
+ google_chat_list_members: "gws_chat",
2825
+ google_chat_delete_message: "gws_chat",
2826
+ google_chat_update_message: "gws_chat",
2827
+ google_chat_react: "gws_chat",
2828
+ google_chat_send_image: "gws_chat",
2829
+ google_chat_upload_attachment: "gws_chat",
2830
+ google_chat_download_attachment: "gws_chat",
2831
+ google_chat_setup_space: "gws_chat",
2832
+ // ── SMTP/IMAP Email (10) ──
2833
+ email_send: "smtp_email",
2834
+ email_reply: "smtp_email",
2835
+ email_forward: "smtp_email",
2836
+ email_search: "smtp_email",
2837
+ email_read: "smtp_email",
2838
+ email_list: "smtp_email",
2839
+ email_folders: "smtp_email",
2840
+ email_move: "smtp_email",
2841
+ email_delete: "smtp_email",
2842
+ email_mark_read: "smtp_email",
2843
+ // ── Gmail (15) ──
2844
+ gmail_search: "gws_gmail",
2845
+ gmail_read: "gws_gmail",
2846
+ gmail_send: "gws_gmail",
2847
+ gmail_reply: "gws_gmail",
2848
+ gmail_forward: "gws_gmail",
2849
+ gmail_trash: "gws_gmail",
2850
+ gmail_modify: "gws_gmail",
2851
+ gmail_labels: "gws_gmail",
2852
+ gmail_drafts: "gws_gmail",
2853
+ gmail_thread: "gws_gmail",
2854
+ gmail_attachment: "gws_gmail",
2855
+ gmail_profile: "gws_gmail",
2856
+ gmail_get_signature: "gws_gmail",
2857
+ gmail_set_signature: "gws_gmail",
2858
+ gmail_vacation: "gws_gmail",
2859
+ // ── Google Calendar (6) ──
2860
+ google_calendar_list: "gws_calendar",
2861
+ google_calendar_events: "gws_calendar",
2862
+ google_calendar_create_event: "gws_calendar",
2863
+ google_calendar_update_event: "gws_calendar",
2864
+ google_calendar_delete_event: "gws_calendar",
2865
+ google_calendar_freebusy: "gws_calendar",
2866
+ // ── Google Drive (7) ──
2867
+ google_drive_list: "gws_drive",
2868
+ google_drive_get: "gws_drive",
2869
+ google_drive_create: "gws_drive",
2870
+ google_drive_delete: "gws_drive",
2871
+ google_drive_move: "gws_drive",
2872
+ google_drive_share: "gws_drive",
2873
+ google_drive_request_access: "gws_drive",
2874
+ // ── Google Docs (3) ──
2875
+ google_docs_create: "gws_docs",
2876
+ google_docs_read: "gws_docs",
2877
+ google_docs_write: "gws_docs",
2878
+ // ── Google Sheets (7) ──
2879
+ google_sheets_read: "gws_sheets",
2880
+ google_sheets_write: "gws_sheets",
2881
+ google_sheets_append: "gws_sheets",
2882
+ google_sheets_create: "gws_sheets",
2883
+ google_sheets_get: "gws_sheets",
2884
+ google_sheets_add_sheet: "gws_sheets",
2885
+ google_sheets_clear: "gws_sheets",
2886
+ // ── Google Contacts (5) ──
2887
+ google_contacts_list: "gws_contacts",
2888
+ google_contacts_search: "gws_contacts",
2889
+ google_contacts_create: "gws_contacts",
2890
+ google_contacts_update: "gws_contacts",
2891
+ google_contacts_search_directory: "gws_contacts",
2892
+ // ── Google Slides (12) ──
2893
+ google_slides_create: "gws_slides",
2894
+ google_slides_get: "gws_slides",
2895
+ google_slides_get_page: "gws_slides",
2896
+ google_slides_add_slide: "gws_slides",
2897
+ google_slides_delete_slide: "gws_slides",
2898
+ google_slides_duplicate_slide: "gws_slides",
2899
+ google_slides_insert_text: "gws_slides",
2900
+ google_slides_replace_text: "gws_slides",
2901
+ google_slides_create_textbox: "gws_slides",
2902
+ google_slides_add_image: "gws_slides",
2903
+ google_slides_batch_update: "gws_slides",
2904
+ google_slides_thumbnail: "gws_slides",
2905
+ // ── Google Forms (8) ──
2906
+ google_forms_create: "gws_forms",
2907
+ google_forms_get: "gws_forms",
2908
+ google_forms_add_question: "gws_forms",
2909
+ google_forms_delete_item: "gws_forms",
2910
+ google_forms_update_info: "gws_forms",
2911
+ google_forms_publish_settings: "gws_forms",
2912
+ google_forms_get_response: "gws_forms",
2913
+ google_forms_list_responses: "gws_forms",
2914
+ // ── Google Tasks (7) ──
2915
+ google_tasks_list: "gws_tasks",
2916
+ google_tasks_list_tasklists: "gws_tasks",
2917
+ google_tasks_create: "gws_tasks",
2918
+ google_tasks_create_list: "gws_tasks",
2919
+ google_tasks_update: "gws_tasks",
2920
+ google_tasks_complete: "gws_tasks",
2921
+ google_tasks_delete: "gws_tasks",
2922
+ // ── Google Maps (10) ──
2923
+ google_maps_search: "gws_maps",
2924
+ google_maps_place_details: "gws_maps",
2925
+ google_maps_nearby: "gws_maps",
2926
+ google_maps_geocode: "gws_maps",
2927
+ google_maps_directions: "gws_maps",
2928
+ google_maps_distance: "gws_maps",
2929
+ google_maps_elevation: "gws_maps",
2930
+ google_maps_timezone: "gws_maps",
2931
+ google_maps_autocomplete: "gws_maps",
2932
+ google_maps_static: "gws_maps",
2933
+ // ── Enterprise: Database (6) ──
2934
+ ent_db_query: "ent_database",
2935
+ ent_db_schema: "ent_database",
2936
+ ent_db_tables: "ent_database",
2937
+ ent_db_sample: "ent_database",
2938
+ ent_db_explain: "ent_database",
2939
+ ent_db_connections: "ent_database",
2940
+ // ── Enterprise: Spreadsheet (8) ──
2941
+ ent_sheet_read: "ent_spreadsheet",
2942
+ ent_sheet_write: "ent_spreadsheet",
2943
+ ent_sheet_filter: "ent_spreadsheet",
2944
+ ent_sheet_aggregate: "ent_spreadsheet",
2945
+ ent_sheet_transform: "ent_spreadsheet",
2946
+ ent_sheet_merge: "ent_spreadsheet",
2947
+ ent_sheet_pivot: "ent_spreadsheet",
2948
+ ent_sheet_convert: "ent_spreadsheet",
2949
+ // ── Enterprise: Documents (8) ──
2950
+ ent_doc_generate_pdf: "ent_documents",
2951
+ ent_doc_generate_docx: "ent_documents",
2952
+ ent_doc_ocr: "ent_documents",
2953
+ ent_doc_parse_invoice: "ent_documents",
2954
+ ent_doc_convert: "ent_documents",
2955
+ ent_doc_extract_tables: "ent_documents",
2956
+ ent_doc_fill_form: "ent_documents",
2957
+ ent_doc_merge_pdfs: "ent_documents",
2958
+ // ── Enterprise: HTTP (4) ──
2959
+ ent_http_request: "ent_http",
2960
+ ent_http_graphql: "ent_http",
2961
+ ent_http_batch: "ent_http",
2962
+ ent_http_download: "ent_http",
2963
+ // ── Enterprise: Security (6) ──
2964
+ ent_sec_scan_secrets: "ent_security",
2965
+ ent_sec_scan_pii: "ent_security",
2966
+ ent_sec_redact_pii: "ent_security",
2967
+ ent_sec_scan_deps: "ent_security",
2968
+ ent_sec_compliance_check: "ent_security",
2969
+ ent_sec_hash: "ent_security",
2970
+ // ── Enterprise: Code Sandbox (5) ──
2971
+ ent_code_run_js: "ent_code",
2972
+ ent_code_run_python: "ent_code",
2973
+ ent_code_run_shell: "ent_code",
2974
+ ent_code_transform_json: "ent_code",
2975
+ ent_code_regex: "ent_code",
2976
+ // ── Enterprise: Diff (4) ──
2977
+ ent_diff_text: "ent_diff",
2978
+ ent_diff_json: "ent_diff",
2979
+ ent_diff_spreadsheet: "ent_diff",
2980
+ ent_diff_summary: "ent_diff",
2981
+ // ── Remotion Video (8) ──
2982
+ remotion_create_project: "remotion_video",
2983
+ remotion_create_composition: "remotion_video",
2984
+ remotion_render: "remotion_video",
2985
+ remotion_render_still: "remotion_video",
2986
+ remotion_list_compositions: "remotion_video",
2987
+ remotion_preview_url: "remotion_video",
2988
+ remotion_add_asset: "remotion_video",
2989
+ remotion_install_package: "remotion_video",
2990
+ remotion_share_file: "remotion_video",
2991
+ // ── Knowledge Search (3) ──
2992
+ knowledge_base_search: "ent_knowledge",
2993
+ knowledge_hub_search: "ent_knowledge",
2994
+ knowledge_search_stats: "ent_knowledge",
2995
+ // ── AgenticMail (40) ──
2996
+ agenticmail_inbox: "agenticmail",
2997
+ agenticmail_read: "agenticmail",
2998
+ agenticmail_send: "agenticmail",
2999
+ agenticmail_reply: "agenticmail",
3000
+ agenticmail_forward: "agenticmail",
3001
+ agenticmail_delete: "agenticmail",
3002
+ agenticmail_search: "agenticmail",
3003
+ agenticmail_move: "agenticmail",
3004
+ agenticmail_mark_read: "agenticmail",
3005
+ agenticmail_mark_unread: "agenticmail",
3006
+ agenticmail_folders: "agenticmail",
3007
+ agenticmail_create_folder: "agenticmail",
3008
+ agenticmail_list_folder: "agenticmail",
3009
+ agenticmail_digest: "agenticmail",
3010
+ agenticmail_contacts: "agenticmail",
3011
+ agenticmail_drafts: "agenticmail",
3012
+ agenticmail_signatures: "agenticmail",
3013
+ agenticmail_templates: "agenticmail",
3014
+ agenticmail_template_send: "agenticmail",
3015
+ agenticmail_tags: "agenticmail",
3016
+ agenticmail_rules: "agenticmail",
3017
+ agenticmail_schedule: "agenticmail",
3018
+ agenticmail_spam: "agenticmail",
3019
+ agenticmail_batch_read: "agenticmail",
3020
+ agenticmail_batch_delete: "agenticmail",
3021
+ agenticmail_batch_mark_read: "agenticmail",
3022
+ agenticmail_batch_mark_unread: "agenticmail",
3023
+ agenticmail_batch_move: "agenticmail",
3024
+ agenticmail_whoami: "agenticmail",
3025
+ agenticmail_update_metadata: "agenticmail",
3026
+ agenticmail_storage: "agenticmail",
3027
+ agenticmail_list_agents: "agenticmail",
3028
+ agenticmail_message_agent: "agenticmail",
3029
+ agenticmail_call_agent: "agenticmail",
3030
+ agenticmail_check_messages: "agenticmail",
3031
+ agenticmail_check_tasks: "agenticmail",
3032
+ agenticmail_claim_task: "agenticmail",
3033
+ agenticmail_complete_task: "agenticmail",
3034
+ agenticmail_submit_result: "agenticmail",
3035
+ agenticmail_wait_for_email: "agenticmail",
3036
+ // ── Local: Filesystem (7) ──
3037
+ file_read: "local_filesystem",
3038
+ file_write: "local_filesystem",
3039
+ file_edit: "local_filesystem",
3040
+ file_list: "local_filesystem",
3041
+ file_search: "local_filesystem",
3042
+ file_move: "local_filesystem",
3043
+ file_delete: "local_filesystem",
3044
+ // ── Local: Shell & System (7) ──
3045
+ shell_exec: "local_shell",
3046
+ shell_interactive: "local_shell",
3047
+ shell_sudo: "local_shell",
3048
+ shell_install: "local_shell",
3049
+ shell_session_list: "local_shell",
3050
+ shell_session_kill: "local_shell",
3051
+ system_info: "local_shell",
3052
+ check_dependency: "local_shell",
3053
+ install_dependency: "local_shell",
3054
+ check_environment: "local_shell",
3055
+ cleanup_installed: "local_shell",
3056
+ // ── Messaging: WhatsApp (16) ──
3057
+ whatsapp_connect: "msg_whatsapp",
3058
+ whatsapp_status: "msg_whatsapp",
3059
+ whatsapp_send: "msg_whatsapp",
3060
+ whatsapp_send_media: "msg_whatsapp",
3061
+ whatsapp_get_groups: "msg_whatsapp",
3062
+ whatsapp_send_voice: "msg_whatsapp",
3063
+ whatsapp_send_location: "msg_whatsapp",
3064
+ whatsapp_send_contact: "msg_whatsapp",
3065
+ whatsapp_react: "msg_whatsapp",
3066
+ whatsapp_typing: "msg_whatsapp",
3067
+ whatsapp_read_receipts: "msg_whatsapp",
3068
+ whatsapp_profile: "msg_whatsapp",
3069
+ whatsapp_group_manage: "msg_whatsapp",
3070
+ whatsapp_delete_message: "msg_whatsapp",
3071
+ whatsapp_forward: "msg_whatsapp",
3072
+ whatsapp_disconnect: "msg_whatsapp",
3073
+ // ── Messaging: Telegram (4) ──
3074
+ telegram_send: "msg_telegram",
3075
+ telegram_send_media: "msg_telegram",
3076
+ telegram_download_file: "msg_telegram",
3077
+ telegram_get_me: "msg_telegram",
3078
+ telegram_get_chat: "msg_telegram",
3079
+ // ── Management (11) ──
3080
+ team_status: "management",
3081
+ team_delegate_task: "management",
3082
+ team_tasks: "management",
3083
+ team_reassign_task: "management",
3084
+ team_feedback: "management",
3085
+ team_resolve_escalation: "management",
3086
+ team_forward_escalation: "management",
3087
+ team_org_chart: "management",
3088
+ task_update: "management",
3089
+ my_tasks: "management",
3090
+ escalate: "management"
3091
+ };
3092
+ var sessionToolStates = /* @__PURE__ */ new Map();
3093
+ function clearSessionToolState(sessionId) {
3094
+ sessionToolStates.delete(sessionId);
3095
+ }
3096
+ var _ALL_SETS = Object.keys(TIER_MAP);
3097
+ var _COMMON_T2 = ["local_filesystem", "local_shell", "browser", "system", "ent_knowledge"];
3098
+ var CONTEXT_PROMOTIONS = {
3099
+ // Meeting: voice + lifecycle essential, plus common tools
3100
+ meeting: ["meeting_voice", "meeting_lifecycle", ..._COMMON_T2],
3101
+ // Chat (webchat/generic): common tools only — rest loaded on demand via signals or request_tools
3102
+ chat: _COMMON_T2,
3103
+ // Messaging channels: load the relevant channel's tools + common tools
3104
+ // The agent's OWN channel tools are essential (Tier 1-like for that session)
3105
+ // Other channel tools and specialist tools load on demand
3106
+ whatsapp: ["msg_whatsapp", "smtp_email", ..._COMMON_T2],
3107
+ telegram: ["msg_telegram", "smtp_email", ..._COMMON_T2],
3108
+ email: ["agenticmail", "gws_gmail", "smtp_email", ..._COMMON_T2],
3109
+ // Task/full: broader set for agent-to-agent work
3110
+ task: ["agenticmail", ..._COMMON_T2],
3111
+ full: _ALL_SETS
3112
+ };
3113
+ var SIGNAL_RULES = [
3114
+ // Email mentions
3115
+ {
3116
+ patterns: [/\bemail\b/i, /\bgmail\b/i, /\binbox\b/i, /\bsend.*mail\b/i],
3117
+ sets: ["gws_gmail", "smtp_email"]
3118
+ },
3119
+ // Calendar mentions
3120
+ {
3121
+ patterns: [/\bcalendar\b/i, /\bschedule\b/i, /\bmeeting.*upcoming\b/i, /\bevent\b/i, /\bfree.*busy\b/i],
3122
+ sets: ["gws_calendar"]
3123
+ },
3124
+ // Drive/docs
3125
+ {
3126
+ patterns: [/\bdrive\b/i, /\bdocument\b/i, /\bgoogle doc\b/i],
3127
+ sets: ["gws_drive", "gws_docs"]
3128
+ },
3129
+ // Sheets
3130
+ {
3131
+ patterns: [/\bspreadsheet\b/i, /\bsheet\b/i, /\bcsv\b/i, /\bexcel\b/i],
3132
+ sets: ["gws_sheets"]
3133
+ },
3134
+ // Slides
3135
+ {
3136
+ patterns: [/\bslide\b/i, /\bpresentation\b/i, /\bpowerpoint\b/i],
3137
+ sets: ["gws_slides"]
3138
+ },
3139
+ // Forms
3140
+ {
3141
+ patterns: [/\bform\b/i, /\bsurvey\b/i, /\bquestionnaire\b/i],
3142
+ sets: ["gws_forms"]
3143
+ },
3144
+ // Maps
3145
+ {
3146
+ patterns: [/\bmap\b/i, /\bdirection\b/i, /\bplace\b/i, /\brestaurant\b/i, /\bnearby\b/i, /\bgeocode\b/i, /\bdistance\b/i],
3147
+ sets: ["gws_maps"]
3148
+ },
3149
+ // Meeting join — broad patterns to catch casual phrasing
3150
+ {
3151
+ patterns: [/\bjoin.*meeting\b/i, /\bmeeting.*join\b/i, /\bgoogle meet\b/i, /meet\.google\.com/i, /\bjoin.*call\b/i, /\bvideo.*call\b/i, /\bjoin.*meet\b/i, /\bjoin.*again\b/i, /\brejoin\b/i, /\bjoin back\b/i, /\bjoin the\b/i, /\bget.*in.*meeting\b/i],
3152
+ sets: ["meeting_lifecycle", "meeting_voice"]
3153
+ },
3154
+ // Database
3155
+ {
3156
+ patterns: [/\bdatabase\b/i, /\bsql\b/i, /\bquery\b/i, /\bpostgres\b/i],
3157
+ sets: ["ent_database"]
3158
+ },
3159
+ // Security
3160
+ {
3161
+ patterns: [/\bsecurity\b/i, /\bscan.*secret\b/i, /\bpii\b/i, /\bcompliance\b/i],
3162
+ sets: ["ent_security"]
3163
+ },
3164
+ // PDF/docs
3165
+ {
3166
+ patterns: [/\bpdf\b/i, /\binvoice\b/i, /\bocr\b/i, /\bdocx\b/i],
3167
+ sets: ["ent_documents"]
3168
+ },
3169
+ // Code
3170
+ {
3171
+ patterns: [/\brun.*python\b/i, /\brun.*javascript\b/i, /\bcode.*sandbox\b/i, /\bregex\b/i],
3172
+ sets: ["ent_code"]
3173
+ },
3174
+ // Contacts
3175
+ {
3176
+ patterns: [/\bcontact\b/i, /\bphone.*number\b/i, /\bdirectory\b/i],
3177
+ sets: ["gws_contacts"]
3178
+ },
3179
+ // AgenticMail
3180
+ {
3181
+ patterns: [/\bagenticmail\b/i, /\bagent.*email\b/i],
3182
+ sets: ["agenticmail"]
3183
+ },
3184
+ // Knowledge base / hub
3185
+ {
3186
+ patterns: [/\bknowledge\s*base\b/i, /\bknowledge\s*hub\b/i, /\bfaq\b/i, /\bdocumentation\b/i, /\bwiki\b/i, /\bhow\s+do\s+(we|i)\b/i, /\bwhat.*(policy|process|procedure)\b/i, /\bcompany.*(guide|handbook)\b/i],
3187
+ sets: ["ent_knowledge"]
3188
+ },
3189
+ // WhatsApp (cross-channel)
3190
+ {
3191
+ patterns: [/\bwhatsapp\b/i, /\bsend.*whatsapp\b/i, /\bwa\s+message\b/i],
3192
+ sets: ["msg_whatsapp"]
3193
+ },
3194
+ // Telegram (cross-channel)
3195
+ {
3196
+ patterns: [/\btelegram\b/i, /\bsend.*telegram\b/i, /\btg\s+message\b/i],
3197
+ sets: ["msg_telegram"]
3198
+ },
3199
+ // Spreadsheet / CSV
3200
+ {
3201
+ patterns: [/\bcsv\b/i, /\bspreadsheet\b/i, /\btransform.*data\b/i, /\bpivot\b/i],
3202
+ sets: ["ent_spreadsheet"]
3203
+ },
3204
+ // Diff
3205
+ {
3206
+ patterns: [/\bdiff\b/i, /\bcompare.*files?\b/i, /\bwhat.*changed\b/i],
3207
+ sets: ["ent_diff"]
3208
+ },
3209
+ // Tasks (Google)
3210
+ {
3211
+ patterns: [/\bgoogle\s*tasks?\b/i, /\btask\s*list\b/i, /\btodo\b/i],
3212
+ sets: ["gws_tasks"]
3213
+ },
3214
+ // Visual memory
3215
+ {
3216
+ patterns: [/\bscreenshot\b/i, /\bcapture\b/i, /\bvision\b/i, /\bwhat.*see\b/i, /\blook\s*at\b/i],
3217
+ sets: ["visual_memory"]
3218
+ },
3219
+ // AgenticMail (broader)
3220
+ {
3221
+ patterns: [/\bsend.*email\b/i, /\bwrite.*email\b/i, /\bdraft\b/i, /\binbox\b/i, /\bcheck.*mail\b/i],
3222
+ sets: ["agenticmail"]
3223
+ },
3224
+ // Remotion Video
3225
+ {
3226
+ patterns: [/\bremotion\b/i, /\bcreate.*video\b/i, /\brender.*video\b/i, /\bvideo\s*project\b/i, /\bmarketing\s*video\b/i, /\bsocial\s*reel\b/i, /\banimation\b/i, /\bmotion\s*graphics?\b/i],
3227
+ sets: ["remotion_video"]
3228
+ }
3229
+ ];
3230
+ function detectSignals(text) {
3231
+ const needed = /* @__PURE__ */ new Set();
3232
+ for (const rule of SIGNAL_RULES) {
3233
+ if (rule.patterns.some((p) => p.test(text))) {
3234
+ for (const s of rule.sets) needed.add(s);
3235
+ }
3236
+ }
3237
+ return [...needed];
3238
+ }
3239
+ function detectSessionContext(opts) {
3240
+ if (opts.explicitContext && opts.explicitContext in CONTEXT_PROMOTIONS) return opts.explicitContext;
3241
+ if (opts.sessionKind === "meeting") return "meeting";
3242
+ if (opts.sessionKind === "email") return "email";
3243
+ if (opts.sessionKind === "task") return "task";
3244
+ const sp = opts.systemPrompt || "";
3245
+ if (opts.isKeepAlive && (sp.includes("MeetingMonitor") || sp.includes("meeting_speak"))) {
3246
+ return "meeting";
3247
+ }
3248
+ if (sp.includes("[Email Handler]") || sp.includes("email triage")) return "email";
3249
+ return "chat";
3250
+ }
3251
+ function resolveSetsForContext(context, additionalSets) {
3252
+ const sets = /* @__PURE__ */ new Set();
3253
+ for (const [setName, tier] of Object.entries(TIER_MAP)) {
3254
+ if (tier === 1) sets.add(setName);
3255
+ }
3256
+ for (const s of CONTEXT_PROMOTIONS[context]) sets.add(s);
3257
+ if (additionalSets) {
3258
+ for (const s of additionalSets) sets.add(s);
3259
+ }
3260
+ return sets;
3261
+ }
3262
+ function filterToolsForContext(allTools, context, options) {
3263
+ const activeSets = resolveSetsForContext(context, options?.additionalSets);
3264
+ if (options?.userMessage) {
3265
+ const signaled = detectSignals(options.userMessage);
3266
+ for (const s of signaled) activeSets.add(s);
3267
+ }
3268
+ const sessionId = options?.sessionId;
3269
+ if (sessionId) {
3270
+ const state = sessionToolStates.get(sessionId);
3271
+ if (state) {
3272
+ for (const s of state.loadedSets) activeSets.add(s);
3273
+ }
3274
+ }
3275
+ const filtered = allTools.filter((tool) => {
3276
+ const set = TOOL_REGISTRY[tool.name];
3277
+ if (!set) return true;
3278
+ return activeSets.has(set);
3279
+ });
3280
+ filtered.push(createRequestToolsTool(allTools, activeSets, context));
3281
+ if (sessionId) {
3282
+ const existing = sessionToolStates.get(sessionId);
3283
+ if (existing) {
3284
+ existing.loadedSets = activeSets;
3285
+ existing.cachedTools = filtered;
3286
+ existing.allToolsRef = allTools;
3287
+ existing.context = context;
3288
+ } else {
3289
+ sessionToolStates.set(sessionId, {
3290
+ context,
3291
+ loadedSets: activeSets,
3292
+ cachedTools: filtered,
3293
+ allToolsRef: allTools,
3294
+ lastContextChange: Date.now(),
3295
+ loadHistory: [{ sets: [...activeSets], reason: `initial:${context}`, time: Date.now() }]
3296
+ });
3297
+ }
3298
+ }
3299
+ return filtered;
3300
+ }
3301
+ function transitionSessionContext(sessionId, newContext, allTools) {
3302
+ const state = sessionToolStates.get(sessionId);
3303
+ if (!state) return null;
3304
+ const oldContext = state.context;
3305
+ if (oldContext === newContext) return null;
3306
+ console.log(`[tool-resolver] Session ${sessionId} transitioning: ${oldContext} \u2192 ${newContext}`);
3307
+ const dynamicSets = /* @__PURE__ */ new Set();
3308
+ const baseSetsOld = resolveSetsForContext(oldContext);
3309
+ for (const s of state.loadedSets) {
3310
+ if (!baseSetsOld.has(s)) dynamicSets.add(s);
3311
+ }
3312
+ const newSets = resolveSetsForContext(newContext);
3313
+ for (const s of dynamicSets) newSets.add(s);
3314
+ const filtered = allTools.filter((tool) => {
3315
+ const set = TOOL_REGISTRY[tool.name];
3316
+ if (!set) return true;
3317
+ return newSets.has(set);
3318
+ });
3319
+ filtered.push(createRequestToolsTool(allTools, newSets, newContext));
3320
+ state.context = newContext;
3321
+ state.loadedSets = newSets;
3322
+ state.cachedTools = filtered;
3323
+ state.lastContextChange = Date.now();
3324
+ state.loadHistory.push({ sets: [...newSets], reason: `transition:${oldContext}\u2192${newContext}`, time: Date.now() });
3325
+ console.log(`[tool-resolver] Transitioned ${sessionId}: ${filtered.length} tools (was ${state.cachedTools?.length || "?"})`);
3326
+ return filtered;
3327
+ }
3328
+ var SET_DESCRIPTIONS = {
3329
+ core: "File I/O, search, bash (8 tools)",
3330
+ browser: "Web browser automation (1 tool)",
3331
+ system: "System capabilities check (1 tool)",
3332
+ memory: "Agent memory \u2014 store/search/reflect (4 tools)",
3333
+ visual_memory: "Vision capture, OCR, visual comparison (10 tools)",
3334
+ meeting_voice: "In-meeting voice + chat (4 tools)",
3335
+ meeting_lifecycle: "Join/schedule/manage meetings (8 tools)",
3336
+ gws_chat: "Google Chat messaging (14 tools)",
3337
+ smtp_email: "Email via SMTP/IMAP \u2014 send, read, search, manage (10 tools)",
3338
+ gws_gmail: "Gmail read/send/search (15 tools)",
3339
+ gws_calendar: "Google Calendar events (6 tools)",
3340
+ gws_drive: "Google Drive files (7 tools)",
3341
+ gws_docs: "Google Docs read/write (3 tools)",
3342
+ gws_sheets: "Google Sheets read/write (7 tools)",
3343
+ gws_contacts: "Google Contacts (5 tools)",
3344
+ gws_slides: "Google Slides presentations (12 tools)",
3345
+ gws_forms: "Google Forms surveys (8 tools)",
3346
+ gws_tasks: "Google Tasks (7 tools)",
3347
+ gws_maps: "Google Maps + Places (10 tools)",
3348
+ ent_database: "SQL database tools (6 tools)",
3349
+ ent_spreadsheet: "CSV/spreadsheet processing (8 tools)",
3350
+ ent_documents: "PDF/DOCX generation, OCR, invoices (8 tools)",
3351
+ ent_http: "HTTP requests, GraphQL (4 tools)",
3352
+ ent_security: "Security scanning \u2014 secrets, PII, deps (6 tools)",
3353
+ ent_code: "Code sandbox \u2014 JS, Python, shell (5 tools)",
3354
+ ent_diff: "Text/JSON/spreadsheet diffs (4 tools)",
3355
+ remotion_video: "Create videos programmatically \u2014 Remotion React framework (9 tools)",
3356
+ ent_knowledge: "Knowledge base + hub search (3 tools)",
3357
+ agenticmail: "Full email management (40 tools)",
3358
+ local_filesystem: "File read/write/edit/move/delete/search (7 tools)",
3359
+ local_shell: "Shell exec, PTY sessions, sudo, package install, dependency management (11 tools)",
3360
+ msg_whatsapp: "WhatsApp messaging, media, groups (16 tools)",
3361
+ msg_telegram: "Telegram messaging, media, and file download (5 tools)",
3362
+ management: "Team management \u2014 delegate tasks, escalate, org chart (10 tools)",
3363
+ mcp_bridge: "MCP integration adapters",
3364
+ filesystem: "File system tools (alias)",
3365
+ web: "Web tools (alias)"
3366
+ };
3367
+ function createRequestToolsTool(allTools, activeSets, _context) {
3368
+ const loadedSets = new Set(activeSets);
3369
+ const availableSetsInAllTools = /* @__PURE__ */ new Set();
3370
+ for (const tool of allTools) {
3371
+ const set = TOOL_REGISTRY[tool.name];
3372
+ if (set) availableSetsInAllTools.add(set);
3373
+ }
3374
+ const allSets = Object.keys(SET_DESCRIPTIONS).filter((s) => availableSetsInAllTools.has(s));
3375
+ const notLoaded = allSets.filter((s) => !loadedSets.has(s));
3376
+ const description = notLoaded.length > 0 ? `Load more tools into this session. Available: ${notLoaded.map((s) => `${s} (${SET_DESCRIPTIONS[s]})`).join("; ")}.` : "All tool sets are loaded.";
3377
+ return {
3378
+ name: "request_tools",
3379
+ label: "Request Tools",
3380
+ description,
3381
+ category: "utility",
3382
+ parameters: {
3383
+ type: "object",
3384
+ properties: {
3385
+ sets: {
3386
+ type: "array",
3387
+ items: { type: "string", enum: allSets },
3388
+ description: "Tool sets to load"
3389
+ }
3390
+ },
3391
+ required: ["sets"]
3392
+ },
3393
+ async execute(_id, params) {
3394
+ const requested = params.sets || [];
3395
+ const newSets = requested.filter((s) => !loadedSets.has(s) && SET_DESCRIPTIONS[s]);
3396
+ if (newSets.length === 0) {
3397
+ return {
3398
+ content: [{ type: "text", text: JSON.stringify({
3399
+ status: "no_change",
3400
+ loaded: [...loadedSets],
3401
+ message: requested.length === 0 ? "No sets specified. Available: " + notLoaded.join(", ") : "All requested sets already loaded."
3402
+ }) }]
3403
+ };
3404
+ }
3405
+ const newToolNames = /* @__PURE__ */ new Set();
3406
+ for (const [name, set] of Object.entries(TOOL_REGISTRY)) {
3407
+ if (newSets.includes(set)) newToolNames.add(name);
3408
+ }
3409
+ const newTools = allTools.filter((t) => newToolNames.has(t.name));
3410
+ for (const s of newSets) loadedSets.add(s);
3411
+ return {
3412
+ content: [{ type: "text", text: JSON.stringify({
3413
+ status: "loaded",
3414
+ newSets,
3415
+ newToolCount: newTools.length,
3416
+ newTools: newTools.map((t) => t.name),
3417
+ allLoaded: [...loadedSets],
3418
+ message: `Loaded ${newTools.length} tools from: ${newSets.join(", ")}. They are now available.`
3419
+ }) }],
3420
+ _dynamicTools: newTools
3421
+ };
3422
+ }
3423
+ };
3424
+ }
3425
+ async function createToolsForContext(options, context, opts) {
3426
+ const { createAllTools } = await import("./agent-tools-3BMHAUUM.js");
3427
+ if (context === "full") {
3428
+ return createAllTools(options);
3429
+ }
3430
+ const allTools = await createAllTools(options);
3431
+ return filterToolsForContext(allTools, context, {
3432
+ additionalSets: opts?.additionalSets,
3433
+ sessionId: opts?.sessionId,
3434
+ userMessage: opts?.userMessage
3435
+ });
3436
+ }
3437
+ async function getToolsForSession(sessionId, options, opts) {
3438
+ const state = sessionToolStates.get(sessionId);
3439
+ if (state?.cachedTools && state.allToolsRef) {
3440
+ if (opts?.userMessage) {
3441
+ const signaled = detectSignals(opts.userMessage);
3442
+ const newSignals = signaled.filter((s) => !state.loadedSets.has(s));
3443
+ if (newSignals.length > 0) {
3444
+ for (const s of newSignals) state.loadedSets.add(s);
3445
+ state.loadHistory.push({ sets: newSignals, reason: `signal:${newSignals.join(",")}`, time: Date.now() });
3446
+ console.log(`[tool-resolver] Auto-promoted sets for ${sessionId}: ${newSignals.join(", ")}`);
3447
+ const filtered = state.allToolsRef.filter((tool) => {
3448
+ const set = TOOL_REGISTRY[tool.name];
3449
+ if (!set) return true;
3450
+ return state.loadedSets.has(set);
3451
+ });
3452
+ filtered.push(createRequestToolsTool(state.allToolsRef, state.loadedSets, state.context));
3453
+ state.cachedTools = filtered;
3454
+ return filtered;
3455
+ }
3456
+ }
3457
+ if (opts?.context && opts.context !== state.context) {
3458
+ const transitioned = transitionSessionContext(sessionId, opts.context, state.allToolsRef);
3459
+ if (transitioned) return transitioned;
3460
+ }
3461
+ return state.cachedTools;
3462
+ }
3463
+ const context = opts?.context || state?.context || "chat";
3464
+ return createToolsForContext(options, context, {
3465
+ sessionId,
3466
+ userMessage: opts?.userMessage
3467
+ });
3468
+ }
3469
+ function getToolSetStats(tools) {
3470
+ const bySet = {};
3471
+ const unregistered = [];
3472
+ for (const tool of tools) {
3473
+ const set = TOOL_REGISTRY[tool.name];
3474
+ if (!set) {
3475
+ if (tool.name !== "request_tools") unregistered.push(tool.name);
3476
+ continue;
3477
+ }
3478
+ bySet[set] = (bySet[set] || 0) + 1;
3479
+ }
3480
+ return { total: tools.length, bySet, unregistered };
3481
+ }
3482
+
3483
+ // src/runtime/model-router.ts
3484
+ function parseModelString(modelStr) {
3485
+ if (!modelStr) return null;
3486
+ const parts = modelStr.split("/");
3487
+ if (parts.length !== 2 || !parts[0] || !parts[1]) return null;
3488
+ return { provider: parts[0], modelId: parts[1] };
3489
+ }
3490
+ function resolveModelForContext(agentConfig, context) {
3491
+ if (!agentConfig) return null;
3492
+ const routing = agentConfig.modelRouting;
3493
+ if (routing) {
3494
+ const contextModel = routing[context];
3495
+ if (contextModel) {
3496
+ const parsed = parseModelString(contextModel);
3497
+ if (parsed) return parsed;
3498
+ }
3499
+ }
3500
+ const voiceConfig = agentConfig.voiceConfig;
3501
+ if (voiceConfig) {
3502
+ if (context === "chat" && voiceConfig.chatModel) {
3503
+ return parseModelString(voiceConfig.chatModel);
3504
+ }
3505
+ if (context === "meeting" && voiceConfig.meetingModel) {
3506
+ return parseModelString(voiceConfig.meetingModel);
3507
+ }
3508
+ }
3509
+ return null;
3510
+ }
3511
+
3512
+ // src/runtime/gateway.ts
3513
+ import { Hono } from "hono";
3514
+ var activeStreams = /* @__PURE__ */ new Map();
3515
+ function emitSessionEvent(sessionId, event) {
3516
+ var listeners = activeStreams.get(sessionId);
3517
+ if (listeners) {
3518
+ for (var listener of listeners) {
3519
+ listener(event);
3520
+ }
3521
+ }
3522
+ }
3523
+ function createRuntimeGateway(config) {
3524
+ var app = new Hono();
3525
+ var runtime = config.runtime;
3526
+ app.get("/health", function(c) {
3527
+ return c.json({
3528
+ status: "ok",
3529
+ runtime: "active",
3530
+ sessions: runtime.getActiveSessionCount()
3531
+ });
3532
+ });
3533
+ app.post("/sessions", async function(c) {
3534
+ try {
3535
+ var body = await c.req.json();
3536
+ var agentId = body.agentId;
3537
+ var message = body.message;
3538
+ var orgId = body.orgId || "default";
3539
+ var modelOverride = body.model;
3540
+ var systemPrompt = body.systemPrompt;
3541
+ if (!agentId) {
3542
+ return c.json({ error: "agentId is required" }, 400);
3543
+ }
3544
+ if (!message) {
3545
+ return c.json({ error: "message is required" }, 400);
3546
+ }
3547
+ var session = await runtime.spawnSession({
3548
+ agentId,
3549
+ orgId,
3550
+ message,
3551
+ model: modelOverride,
3552
+ systemPrompt
3553
+ });
3554
+ return c.json({
3555
+ sessionId: session.id,
3556
+ agentId: session.agentId,
3557
+ status: session.status,
3558
+ createdAt: session.createdAt
3559
+ }, 201);
3560
+ } catch (err) {
3561
+ return c.json({ error: err.message }, 500);
3562
+ }
3563
+ });
3564
+ app.get("/sessions", async function(c) {
3565
+ try {
3566
+ var agentId = c.req.query("agentId");
3567
+ var status = c.req.query("status");
3568
+ var limit = parseInt(c.req.query("limit") || "50", 10);
3569
+ if (!agentId) {
3570
+ return c.json({ error: "agentId query parameter required" }, 400);
3571
+ }
3572
+ var sessions = await runtime.listSessions(agentId, { status, limit });
3573
+ return c.json({ sessions });
3574
+ } catch (err) {
3575
+ return c.json({ error: err.message }, 500);
3576
+ }
3577
+ });
3578
+ app.get("/sessions/:id", async function(c) {
3579
+ try {
3580
+ var sessionId = c.req.param("id");
3581
+ var session = await runtime.getSession(sessionId);
3582
+ if (!session) {
3583
+ return c.json({ error: "Session not found" }, 404);
3584
+ }
3585
+ return c.json(session);
3586
+ } catch (err) {
3587
+ return c.json({ error: err.message }, 500);
3588
+ }
3589
+ });
3590
+ app.delete("/sessions/:id", async function(c) {
3591
+ try {
3592
+ var sessionId = c.req.param("id");
3593
+ await runtime.terminateSession(sessionId);
3594
+ return c.json({ status: "terminated" });
3595
+ } catch (err) {
3596
+ return c.json({ error: err.message }, 500);
3597
+ }
3598
+ });
3599
+ app.post("/sessions/:id/message", async function(c) {
3600
+ try {
3601
+ var sessionId = c.req.param("id");
3602
+ var body = await c.req.json();
3603
+ var message = body.message;
3604
+ if (!message) {
3605
+ return c.json({ error: "message is required" }, 400);
3606
+ }
3607
+ await runtime.sendMessage(sessionId, message);
3608
+ return c.json({ status: "sent" });
3609
+ } catch (err) {
3610
+ return c.json({ error: err.message }, 500);
3611
+ }
3612
+ });
3613
+ app.get("/sessions/:id/stream", function(c) {
3614
+ var sessionId = c.req.param("id");
3615
+ return new Response(
3616
+ new ReadableStream({
3617
+ start(controller) {
3618
+ var encoder = new TextEncoder();
3619
+ function sendEvent(event) {
3620
+ try {
3621
+ var data = JSON.stringify(event);
3622
+ controller.enqueue(encoder.encode(`data: ${data}
3623
+
3624
+ `));
3625
+ if (event.type === "session_end" || event.type === "error") {
3626
+ cleanup();
3627
+ controller.close();
3628
+ }
3629
+ } catch {
3630
+ }
3631
+ }
3632
+ function cleanup() {
3633
+ var listeners = activeStreams.get(sessionId);
3634
+ if (listeners) {
3635
+ listeners.delete(sendEvent);
3636
+ if (listeners.size === 0) activeStreams.delete(sessionId);
3637
+ }
3638
+ }
3639
+ if (!activeStreams.has(sessionId)) {
3640
+ activeStreams.set(sessionId, /* @__PURE__ */ new Set());
3641
+ }
3642
+ activeStreams.get(sessionId).add(sendEvent);
3643
+ controller.enqueue(encoder.encode(": connected\n\n"));
3644
+ }
3645
+ }),
3646
+ {
3647
+ headers: {
3648
+ "Content-Type": "text/event-stream",
3649
+ "Cache-Control": "no-cache",
3650
+ "Connection": "keep-alive"
3651
+ }
3652
+ }
3653
+ );
3654
+ });
3655
+ app.post("/spawn", async function(c) {
3656
+ try {
3657
+ var body = await c.req.json();
3658
+ var parentSessionId = body.parentSessionId;
3659
+ var task = body.task;
3660
+ var agentId = body.agentId;
3661
+ var model = body.model;
3662
+ if (!parentSessionId || !task) {
3663
+ return c.json({ error: "parentSessionId and task are required" }, 400);
3664
+ }
3665
+ var result = await runtime.spawnSubAgent({
3666
+ parentSessionId,
3667
+ task,
3668
+ agentId,
3669
+ model
3670
+ });
3671
+ return c.json(result, 201);
3672
+ } catch (err) {
3673
+ return c.json({ error: err.message }, 500);
3674
+ }
3675
+ });
3676
+ app.post("/hooks/inbound", async function(c) {
3677
+ try {
3678
+ var body = await c.req.json();
3679
+ var result = await runtime.handleInboundEmail(body);
3680
+ return c.json(result);
3681
+ } catch (err) {
3682
+ return c.json({ error: err.message }, 500);
3683
+ }
3684
+ });
3685
+ return app;
3686
+ }
3687
+
3688
+ // src/runtime/subagent.ts
3689
+ var SubAgentManager = class {
3690
+ /** Active sub-agent records keyed by parent session ID */
3691
+ subAgents = /* @__PURE__ */ new Map();
3692
+ /** Max children per parent session */
3693
+ maxChildrenPerParent;
3694
+ /** Max spawn depth */
3695
+ maxSpawnDepth;
3696
+ constructor(opts) {
3697
+ this.maxChildrenPerParent = opts?.maxChildrenPerParent ?? 5;
3698
+ this.maxSpawnDepth = opts?.maxSpawnDepth ?? 2;
3699
+ }
3700
+ /**
3701
+ * Register a new sub-agent spawn.
3702
+ */
3703
+ register(info) {
3704
+ var existing = this.subAgents.get(info.parentSessionId) || [];
3705
+ existing.push(info);
3706
+ this.subAgents.set(info.parentSessionId, existing);
3707
+ }
3708
+ /**
3709
+ * Check if spawning is allowed for this parent session.
3710
+ */
3711
+ canSpawn(parentSessionId) {
3712
+ var children = this.subAgents.get(parentSessionId) || [];
3713
+ var activeCount = children.filter(function(c) {
3714
+ return c.status === "active";
3715
+ }).length;
3716
+ if (activeCount >= this.maxChildrenPerParent) {
3717
+ return {
3718
+ allowed: false,
3719
+ reason: `Max active sub-agents reached (${activeCount}/${this.maxChildrenPerParent})`
3720
+ };
3721
+ }
3722
+ return { allowed: true };
3723
+ }
3724
+ /**
3725
+ * Get depth of a session in the sub-agent tree.
3726
+ */
3727
+ getDepth(sessionId) {
3728
+ var depth = 0;
3729
+ for (var [parentId, children] of this.subAgents) {
3730
+ for (var child of children) {
3731
+ if (child.childSessionId === sessionId) {
3732
+ return 1 + this.getDepth(parentId);
3733
+ }
3734
+ }
3735
+ }
3736
+ return depth;
3737
+ }
3738
+ /**
3739
+ * Mark a sub-agent as completed.
3740
+ */
3741
+ complete(childSessionId, status) {
3742
+ for (var [, children] of this.subAgents) {
3743
+ for (var child of children) {
3744
+ if (child.childSessionId === childSessionId) {
3745
+ child.status = status;
3746
+ child.completedAt = Date.now();
3747
+ return;
3748
+ }
3749
+ }
3750
+ }
3751
+ }
3752
+ /**
3753
+ * List sub-agents for a parent session.
3754
+ */
3755
+ listForParent(parentSessionId) {
3756
+ return this.subAgents.get(parentSessionId) || [];
3757
+ }
3758
+ /**
3759
+ * Cancel all active sub-agents for a parent.
3760
+ */
3761
+ cancelAll(parentSessionId) {
3762
+ var children = this.subAgents.get(parentSessionId) || [];
3763
+ var cancelled = [];
3764
+ for (var child of children) {
3765
+ if (child.status === "active") {
3766
+ child.status = "cancelled";
3767
+ child.completedAt = Date.now();
3768
+ cancelled.push(child.childSessionId);
3769
+ }
3770
+ }
3771
+ return cancelled;
3772
+ }
3773
+ /**
3774
+ * Clean up all records for a parent session.
3775
+ */
3776
+ cleanup(parentSessionId) {
3777
+ this.subAgents.delete(parentSessionId);
3778
+ }
3779
+ /**
3780
+ * Get total active sub-agent count across all parents.
3781
+ */
3782
+ getActiveCount() {
3783
+ var count = 0;
3784
+ for (var [, children] of this.subAgents) {
3785
+ count += children.filter(function(c) {
3786
+ return c.status === "active";
3787
+ }).length;
3788
+ }
3789
+ return count;
3790
+ }
3791
+ };
3792
+
3793
+ // src/runtime/email-channel.ts
3794
+ var EmailChannel = class {
3795
+ config;
3796
+ constructor(config) {
3797
+ this.config = config;
3798
+ }
3799
+ /**
3800
+ * Handle an inbound email and trigger an agent session.
3801
+ */
3802
+ async handleInbound(email) {
3803
+ var agent = await this.config.resolveAgent(email.to);
3804
+ if (!agent) {
3805
+ throw new Error(`No agent found for email address: ${email.to}`);
3806
+ }
3807
+ var isNewSession = false;
3808
+ var session = await this.config.findActiveSession(agent.agentId, email.from);
3809
+ if (!session) {
3810
+ session = await this.config.createSession(agent.agentId, agent.orgId);
3811
+ isNewSession = true;
3812
+ }
3813
+ var messageContent = formatEmailAsMessage(email);
3814
+ await this.config.sendMessage(session.id, messageContent);
3815
+ return {
3816
+ sessionId: session.id,
3817
+ agentId: agent.agentId,
3818
+ isNewSession
3819
+ };
3820
+ }
3821
+ };
3822
+ function formatEmailAsMessage(email) {
3823
+ var parts = [];
3824
+ parts.push(`[Inbound Email]`);
3825
+ parts.push(`From: ${email.from}`);
3826
+ parts.push(`Subject: ${email.subject}`);
3827
+ if (email.inReplyTo) {
3828
+ parts.push(`In-Reply-To: ${email.inReplyTo}`);
3829
+ }
3830
+ parts.push("");
3831
+ parts.push(email.body);
3832
+ if (email.attachments && email.attachments.length > 0) {
3833
+ parts.push("");
3834
+ parts.push(`Attachments (${email.attachments.length}):`);
3835
+ for (var att of email.attachments) {
3836
+ parts.push(` - ${att.filename} (${att.contentType}, ${formatBytes(att.size)})`);
3837
+ }
3838
+ }
3839
+ return parts.join("\n");
3840
+ }
3841
+ function formatBytes(bytes) {
3842
+ if (bytes < 1024) return `${bytes}B`;
3843
+ if (bytes < 1024 * 1024) return `${(bytes / 1024).toFixed(1)}KB`;
3844
+ return `${(bytes / (1024 * 1024)).toFixed(1)}MB`;
3845
+ }
3846
+
3847
+ // src/runtime/followup.ts
3848
+ import { nanoid as nanoid2 } from "nanoid";
3849
+ var FollowUpScheduler = class {
3850
+ followUps = /* @__PURE__ */ new Map();
3851
+ timer = null;
3852
+ checkIntervalMs;
3853
+ onDue;
3854
+ db;
3855
+ constructor(opts) {
3856
+ this.onDue = opts.onDue;
3857
+ this.db = opts.engineDb || null;
3858
+ this.checkIntervalMs = opts.checkIntervalMs ?? 3e4;
3859
+ }
3860
+ /**
3861
+ * Schedule a new follow-up.
3862
+ */
3863
+ async schedule(opts) {
3864
+ var id = nanoid2(12);
3865
+ var now = Date.now();
3866
+ var followUp = {
3867
+ id,
3868
+ agentId: opts.agentId,
3869
+ sessionId: opts.sessionId,
3870
+ message: opts.message,
3871
+ executeAt: opts.executeAt.getTime(),
3872
+ status: "pending",
3873
+ createdAt: now
3874
+ };
3875
+ this.followUps.set(id, followUp);
3876
+ if (this.db) {
3877
+ try {
3878
+ await this.db.run(
3879
+ `INSERT INTO agent_followups (id, agent_id, session_id, message, execute_at, status, created_at)
3880
+ VALUES (?, ?, ?, ?, ?, 'pending', ?)`,
3881
+ [id, opts.agentId, opts.sessionId || null, opts.message, followUp.executeAt, now]
3882
+ );
3883
+ } catch (err) {
3884
+ console.warn("[followup] Failed to persist follow-up:", err.message);
3885
+ }
3886
+ }
3887
+ return id;
3888
+ }
3889
+ /**
3890
+ * Cancel a pending follow-up.
3891
+ */
3892
+ async cancel(followUpId) {
3893
+ var followUp = this.followUps.get(followUpId);
3894
+ if (!followUp || followUp.status !== "pending") return false;
3895
+ followUp.status = "cancelled";
3896
+ this.followUps.delete(followUpId);
3897
+ if (this.db) {
3898
+ try {
3899
+ await this.db.run(
3900
+ `UPDATE agent_followups SET status = 'cancelled' WHERE id = ?`,
3901
+ [followUpId]
3902
+ );
3903
+ } catch {
3904
+ }
3905
+ }
3906
+ return true;
3907
+ }
3908
+ /**
3909
+ * List pending follow-ups for an agent.
3910
+ */
3911
+ listPending(agentId) {
3912
+ var results = [];
3913
+ for (var [, fu] of this.followUps) {
3914
+ if (fu.agentId === agentId && fu.status === "pending") {
3915
+ results.push(fu);
3916
+ }
3917
+ }
3918
+ return results.sort(function(a, b) {
3919
+ return a.executeAt - b.executeAt;
3920
+ });
3921
+ }
3922
+ /**
3923
+ * Start the scheduler. Loads pending follow-ups from DB if available.
3924
+ */
3925
+ async start() {
3926
+ if (this.timer) return;
3927
+ if (this.db) {
3928
+ try {
3929
+ var rows = await this.db.query(
3930
+ `SELECT * FROM agent_followups WHERE status = 'pending' ORDER BY execute_at ASC`,
3931
+ []
3932
+ );
3933
+ for (var row of rows || []) {
3934
+ var r = row;
3935
+ var followUp = {
3936
+ id: r.id,
3937
+ agentId: r.agent_id,
3938
+ sessionId: r.session_id || void 0,
3939
+ message: r.message,
3940
+ executeAt: r.execute_at,
3941
+ status: "pending",
3942
+ createdAt: r.created_at
3943
+ };
3944
+ this.followUps.set(followUp.id, followUp);
3945
+ }
3946
+ if (this.followUps.size > 0) {
3947
+ console.log(`[followup] Loaded ${this.followUps.size} pending follow-ups from DB`);
3948
+ }
3949
+ } catch (err) {
3950
+ console.warn("[followup] Failed to load follow-ups from DB:", err.message);
3951
+ }
3952
+ }
3953
+ var self = this;
3954
+ this.timer = setInterval(async function() {
3955
+ await self.checkDueFollowUps();
3956
+ }, this.checkIntervalMs);
3957
+ this.timer.unref();
3958
+ }
3959
+ /**
3960
+ * Stop the scheduler.
3961
+ */
3962
+ stop() {
3963
+ if (this.timer) {
3964
+ clearInterval(this.timer);
3965
+ this.timer = null;
3966
+ }
3967
+ }
3968
+ /**
3969
+ * Check for and execute due follow-ups.
3970
+ */
3971
+ async checkDueFollowUps() {
3972
+ var now = Date.now();
3973
+ var due = [];
3974
+ for (var [_id, fu] of this.followUps) {
3975
+ if (fu.status === "pending" && fu.executeAt <= now) {
3976
+ due.push(fu);
3977
+ }
3978
+ }
3979
+ for (var followUp of due) {
3980
+ try {
3981
+ followUp.status = "executed";
3982
+ await this.onDue(followUp);
3983
+ this.followUps.delete(followUp.id);
3984
+ if (this.db) {
3985
+ try {
3986
+ await this.db.run(
3987
+ `UPDATE agent_followups SET status = 'executed' WHERE id = ?`,
3988
+ [followUp.id]
3989
+ );
3990
+ } catch {
3991
+ }
3992
+ }
3993
+ } catch (err) {
3994
+ console.warn(`[followup] Failed to execute follow-up ${followUp.id}: ${err}`);
3995
+ followUp.status = "pending";
3996
+ }
3997
+ }
3998
+ }
3999
+ /**
4000
+ * Get count of pending follow-ups.
4001
+ */
4002
+ getPendingCount() {
4003
+ var count = 0;
4004
+ for (var [, fu] of this.followUps) {
4005
+ if (fu.status === "pending") count++;
4006
+ }
4007
+ return count;
4008
+ }
4009
+ };
4010
+
4011
+ // src/runtime/index.ts
4012
+ import { homedir } from "os";
4013
+ import { join } from "path";
4014
+ import { mkdirSync } from "fs";
4015
+ var _remotionPrompt = buildRemotonPrompt();
4016
+ var DEFAULT_MODEL = {
4017
+ provider: "anthropic",
4018
+ modelId: "claude-sonnet-4-5-20250929"
4019
+ };
4020
+ var DEFAULT_HEARTBEAT_INTERVAL_MS = 3e4;
4021
+ var DEFAULT_STALE_SESSION_TIMEOUT_MS = 15 * 6e4;
4022
+ var DEFAULT_SSE_KEEPALIVE_MS = 15e3;
4023
+ var AgentRuntime = class {
4024
+ config;
4025
+ sessionManager = null;
4026
+ subAgentManager;
4027
+ followUpScheduler;
4028
+ emailChannel = null;
4029
+ gatewayApp = null;
4030
+ activeSessions = /* @__PURE__ */ new Map();
4031
+ sessionCompleteCallbacks = /* @__PURE__ */ new Map();
4032
+ /** Sessions that should NOT complete even when the LLM returns end_turn (e.g., meeting monitor active) */
4033
+ keepAliveSessions = /* @__PURE__ */ new Set();
4034
+ /** Sessions with an agent loop currently executing (LLM call in flight) */
4035
+ loopRunning = /* @__PURE__ */ new Set();
4036
+ /** Queued messages for sessions whose loop is currently running — prevents concurrent loops */
4037
+ pendingMessages = /* @__PURE__ */ new Map();
4038
+ heartbeatTimer = null;
4039
+ staleCheckTimer = null;
4040
+ sseKeepaliveTimer = null;
4041
+ started = false;
4042
+ constructor(config) {
4043
+ this.config = config;
4044
+ this.subAgentManager = new SubAgentManager();
4045
+ this.followUpScheduler = new FollowUpScheduler({
4046
+ engineDb: config.engineDb,
4047
+ onDue: async (followUp) => {
4048
+ if (followUp.sessionId) {
4049
+ await this.sendMessage(followUp.sessionId, `[Scheduled Reminder] ${followUp.message}`);
4050
+ }
4051
+ }
4052
+ });
4053
+ }
4054
+ /** Build tool options for a given agent, including OAuth email config if available */
4055
+ buildToolOptions(agentId, sessionId) {
4056
+ const self = this;
4057
+ const agentWorkspace = join(homedir(), ".agenticmail", "workspaces", agentId);
4058
+ try {
4059
+ mkdirSync(agentWorkspace, { recursive: true });
4060
+ } catch {
4061
+ }
4062
+ const base = {
4063
+ agentId,
4064
+ workspaceDir: agentWorkspace,
4065
+ agenticmailManager: this.config.agenticmailManager,
4066
+ agentMemoryManager: this.config.agentMemoryManager,
4067
+ engineDb: this.config.engineDb,
4068
+ knowledgeEngine: this.config.knowledgeEngine,
4069
+ orgId: this.config.orgId || process.env.ORG_ID || "default",
4070
+ runtimeRef: {
4071
+ sendMessage: (sid, message) => self.sendMessage(sid, message),
4072
+ getCurrentSessionId: () => sessionId,
4073
+ setKeepAlive: (sid, keepAlive) => self.setKeepAlive(sid, keepAlive),
4074
+ terminateSession: (sid) => self.terminateSession(sid)
4075
+ }
4076
+ };
4077
+ if (this.config.getEmailConfig) {
4078
+ const ec = this.config.getEmailConfig(agentId);
4079
+ if (ec?.oauthAccessToken || ec?.smtpHost) {
4080
+ base.emailConfig = ec;
4081
+ if (this.config.onTokenRefresh) {
4082
+ const onRefresh = this.config.onTokenRefresh;
4083
+ base.onTokenRefresh = (tokens) => onRefresh(agentId, tokens);
4084
+ }
4085
+ }
4086
+ }
4087
+ if (this.config.getAgentConfig) {
4088
+ const agentConfig = this.config.getAgentConfig(agentId);
4089
+ if (agentConfig?.enabledGoogleServices?.length) {
4090
+ base.enabledGoogleServices = agentConfig.enabledGoogleServices;
4091
+ } else if (agentConfig?.skills?.length) {
4092
+ const skillToService = {
4093
+ "gws-gmail": "gmail",
4094
+ "gws-calendar": "calendar",
4095
+ "gws-drive": "drive",
4096
+ "gws-tasks": "tasks",
4097
+ "gws-docs": "docs",
4098
+ "gws-sheets": "sheets",
4099
+ "gws-contacts": "contacts",
4100
+ "gws-chat": "chat",
4101
+ "gws-slides": "slides",
4102
+ "gws-forms": "forms",
4103
+ "gws-meet": "meetings"
4104
+ };
4105
+ const derived = agentConfig.skills.filter((s) => s.startsWith("gws-")).map((s) => skillToService[s]).filter(Boolean);
4106
+ if (derived.length) base.enabledGoogleServices = derived;
4107
+ }
4108
+ if (agentConfig?.voiceConfig) {
4109
+ base.voiceConfig = agentConfig.voiceConfig;
4110
+ console.log(`[runtime] Voice config loaded: ${JSON.stringify(agentConfig.voiceConfig)}`);
4111
+ } else {
4112
+ console.log(`[runtime] No voiceConfig in agent config (keys: ${Object.keys(agentConfig || {}).join(", ")})`);
4113
+ }
4114
+ if (agentConfig?.messagingChannels) {
4115
+ base.agentConfig = { messagingChannels: agentConfig.messagingChannels };
4116
+ }
4117
+ if (agentConfig?.toolSecurity?.security) {
4118
+ base.security = agentConfig.toolSecurity.security;
4119
+ } else if (self.orgToolSecurity) {
4120
+ base.security = self.orgToolSecurity;
4121
+ }
4122
+ }
4123
+ if (this.config.vault) {
4124
+ base.vault = this.config.vault;
4125
+ }
4126
+ if (this.config.permissionEngine) {
4127
+ base.permissionEngine = this.config.permissionEngine;
4128
+ }
4129
+ if (this.config.getIntegrationKey) {
4130
+ const getKey = this.config.getIntegrationKey;
4131
+ base.mapsApiKeyResolver = () => getKey("google-maps");
4132
+ base.elevenLabsKeyResolver = () => getKey("elevenlabs");
4133
+ }
4134
+ if (this.config.hierarchyManager) {
4135
+ base.hierarchyManager = this.config.hierarchyManager;
4136
+ }
4137
+ if (this.config.mcpProcessManager) {
4138
+ base.mcpProcessManager = this.config.mcpProcessManager;
4139
+ }
4140
+ if (this.config.databaseManager) {
4141
+ base.databaseManager = this.config.databaseManager;
4142
+ }
4143
+ return base;
4144
+ }
4145
+ /**
4146
+ * Start the runtime — initializes session manager, gateway, schedulers,
4147
+ * heartbeat, stale detection, and resumes active sessions.
4148
+ */
4149
+ async start() {
4150
+ if (this.started) return;
4151
+ try {
4152
+ if (this.config.adminDb) {
4153
+ var settings = await this.config.adminDb.getSettings();
4154
+ var pricingConfig = settings?.modelPricingConfig;
4155
+ if (pricingConfig && pricingConfig.customProviders) {
4156
+ this.customProviders = pricingConfig.customProviders;
4157
+ }
4158
+ var toolSecConfig = settings?.toolSecurityConfig;
4159
+ if (toolSecConfig?.security) {
4160
+ this.orgToolSecurity = toolSecConfig.security;
4161
+ }
4162
+ }
4163
+ } catch {
4164
+ }
4165
+ this.sessionManager = new SessionManager({ engineDb: this.config.engineDb });
4166
+ var self = this;
4167
+ this.emailChannel = new EmailChannel({
4168
+ async resolveAgent(email) {
4169
+ try {
4170
+ var rows = await self.config.engineDb.query(
4171
+ `SELECT id, org_id FROM managed_agents WHERE config LIKE ? AND state = 'running'`,
4172
+ [`%${email}%`]
4173
+ );
4174
+ if (rows && rows.length > 0) {
4175
+ var row = rows[0];
4176
+ return { agentId: row.id, orgId: row.org_id };
4177
+ }
4178
+ } catch {
4179
+ }
4180
+ return null;
4181
+ },
4182
+ async findActiveSession(agentId, _senderEmail) {
4183
+ var sessions = await self.sessionManager.listSessions(agentId, { status: "active", limit: 1 });
4184
+ return sessions.length > 0 ? await self.sessionManager.getSession(sessions[0].id) : null;
4185
+ },
4186
+ async createSession(agentId, orgId) {
4187
+ return self.sessionManager.createSession(agentId, orgId);
4188
+ },
4189
+ async sendMessage(sessionId, message) {
4190
+ await self.sendMessage(sessionId, message);
4191
+ }
4192
+ });
4193
+ if (this.config.gatewayEnabled !== false) {
4194
+ this.gatewayApp = createRuntimeGateway({ runtime: this });
4195
+ }
4196
+ await this.followUpScheduler.start();
4197
+ var heartbeatMs = this.config.heartbeatIntervalMs ?? DEFAULT_HEARTBEAT_INTERVAL_MS;
4198
+ this.heartbeatTimer = setInterval(function() {
4199
+ self.emitHeartbeats().catch(function() {
4200
+ });
4201
+ }, heartbeatMs);
4202
+ this.heartbeatTimer.unref();
4203
+ var staleTimeoutMs = this.config.staleSessionTimeoutMs ?? DEFAULT_STALE_SESSION_TIMEOUT_MS;
4204
+ this.staleCheckTimer = setInterval(function() {
4205
+ self.cleanupStaleSessions(staleTimeoutMs).catch(function() {
4206
+ });
4207
+ }, staleTimeoutMs);
4208
+ this.staleCheckTimer.unref();
4209
+ this.sseKeepaliveTimer = setInterval(function() {
4210
+ for (var [sessionId] of self.activeSessions) {
4211
+ emitSessionEvent(sessionId, {
4212
+ type: "heartbeat",
4213
+ timestamp: Date.now(),
4214
+ activeTurns: self.activeSessions.size
4215
+ });
4216
+ }
4217
+ }, DEFAULT_SSE_KEEPALIVE_MS);
4218
+ this.sseKeepaliveTimer.unref();
4219
+ this.started = true;
4220
+ console.log("[runtime] Agent runtime started");
4221
+ if (this.config.resumeOnStartup !== false) {
4222
+ await this.resumeActiveSessions();
4223
+ }
4224
+ }
4225
+ /**
4226
+ * Spawn a new agent session and begin processing.
4227
+ */
4228
+ async spawnSession(opts) {
4229
+ this.ensureStarted();
4230
+ var agentId = opts.agentId;
4231
+ var orgId = opts.orgId || "default";
4232
+ var model = opts.model || this.config.defaultModel || DEFAULT_MODEL;
4233
+ var session = await this.sessionManager.createSession(agentId, orgId, opts.parentSessionId);
4234
+ var memoryContext = "";
4235
+ if (this.config.agentMemoryManager) {
4236
+ try {
4237
+ memoryContext = await this.config.agentMemoryManager.generateMemoryContext(agentId);
4238
+ } catch {
4239
+ }
4240
+ }
4241
+ var hierarchyContext = "";
4242
+ if (this.config.hierarchyManager) {
4243
+ try {
4244
+ hierarchyContext = await this.config.hierarchyManager.buildManagerPrompt(agentId) || "";
4245
+ } catch {
4246
+ }
4247
+ }
4248
+ var _agentCfg = this.config.getAgentConfig ? this.config.getAgentConfig(agentId) : null;
4249
+ var _agentIdentity = _agentCfg?.identity || null;
4250
+ var _dbConnections = [];
4251
+ try {
4252
+ if (this.config.databaseManager) _dbConnections = this.config.databaseManager.getAgentConnectionSummary(agentId);
4253
+ } catch {
4254
+ }
4255
+ var systemPrompt = opts.systemPrompt || buildDefaultSystemPrompt(agentId, memoryContext, hierarchyContext, _agentIdentity, _dbConnections);
4256
+ var sessionContext = detectSessionContext({
4257
+ systemPrompt,
4258
+ sessionKind: opts.kind,
4259
+ explicitContext: opts.sessionContext
4260
+ });
4261
+ var toolOpts = this.buildToolOptions(agentId, session.id);
4262
+ var tools = opts.tools || await createToolsForContext(toolOpts, sessionContext, {
4263
+ additionalSets: opts.additionalSets,
4264
+ sessionId: session.id,
4265
+ userMessage: opts.message
4266
+ });
4267
+ var toolStats = getToolSetStats(tools);
4268
+ console.log(`[runtime] Session ${session.id} tools: ${toolStats.total} (context: ${sessionContext})${toolStats.unregistered.length ? `, unregistered: ${toolStats.unregistered.join(",")}` : ""}`);
4269
+ if (this._reportStatus) {
4270
+ this._reportStatus({
4271
+ status: "online",
4272
+ activeSessions: this.activeSessions.size,
4273
+ currentActivity: { type: sessionContext, detail: opts.message?.slice(0, 100), sessionId: session.id, startedAt: (/* @__PURE__ */ new Date()).toISOString() }
4274
+ });
4275
+ }
4276
+ var _contextModel = this.resolveModelForContext(agentId, sessionContext);
4277
+ if (_contextModel) {
4278
+ model = _contextModel;
4279
+ console.log(`[runtime] ${sessionContext} session \u2014 using model: ${_contextModel.provider}/${_contextModel.modelId}`);
4280
+ }
4281
+ var agentConfig = {
4282
+ agentId,
4283
+ orgId,
4284
+ model,
4285
+ systemPrompt,
4286
+ tools
4287
+ };
4288
+ var apiKey = await this.resolveApiKeyAsync(model.provider, agentId);
4289
+ if (!apiKey) {
4290
+ await this.sessionManager.updateSession(session.id, { status: "failed" });
4291
+ throw new Error(`No API key configured for provider: ${model.provider}`);
4292
+ }
4293
+ for (let ci = 0; ci < apiKey.length; ci++) {
4294
+ if (apiKey.charCodeAt(ci) > 127) {
4295
+ console.error(`[runtime] WARNING: API key for ${model.provider} contains non-ASCII at index ${ci}: charCode=${apiKey.charCodeAt(ci)}`);
4296
+ break;
4297
+ }
4298
+ }
4299
+ const initialContent = opts.messageContent || opts.message;
4300
+ this.runSessionLoop(session.id, agentConfig, [{ role: "user", content: initialContent }], apiKey);
4301
+ return session;
4302
+ }
4303
+ /**
4304
+ * Send a message to an active session.
4305
+ */
4306
+ async sendMessage(sessionId, message) {
4307
+ this.ensureStarted();
4308
+ var session = await this.sessionManager.getSession(sessionId);
4309
+ if (!session) throw new Error(`Session not found: ${sessionId}`);
4310
+ if (session.status !== "active") throw new Error(`Session is not active: ${session.status}`);
4311
+ await this.sessionManager.appendMessage(sessionId, { role: "user", content: message });
4312
+ if (this.loopRunning.has(sessionId)) {
4313
+ var queue = this.pendingMessages.get(sessionId);
4314
+ if (!queue) {
4315
+ queue = [];
4316
+ this.pendingMessages.set(sessionId, queue);
4317
+ }
4318
+ queue.push(message);
4319
+ console.log(`[runtime] Session ${sessionId} loop active \u2014 queued message (${queue.length} pending)`);
4320
+ return;
4321
+ }
4322
+ var updatedSession = await this.sessionManager.getSession(sessionId);
4323
+ var messages = updatedSession?.messages || [...session.messages, { role: "user", content: message }];
4324
+ var model = this.config.defaultModel || DEFAULT_MODEL;
4325
+ var _smCtx = this.keepAliveSessions.has(sessionId) ? "meeting" : "chat";
4326
+ var _smModel = this.resolveModelForContext(session.agentId, _smCtx);
4327
+ if (_smModel) {
4328
+ model = _smModel;
4329
+ }
4330
+ var apiKey = await this.resolveApiKeyAsync(model.provider, session.agentId);
4331
+ if (!apiKey) throw new Error(`No API key for provider: ${model.provider}`);
4332
+ var memoryContext = "";
4333
+ if (this.config.agentMemoryManager) {
4334
+ try {
4335
+ memoryContext = await this.config.agentMemoryManager.generateMemoryContext(session.agentId);
4336
+ } catch {
4337
+ }
4338
+ }
4339
+ var _hierarchyCtx = "";
4340
+ if (this.config.hierarchyManager) {
4341
+ try {
4342
+ _hierarchyCtx = await this.config.hierarchyManager.buildManagerPrompt(session.agentId) || "";
4343
+ } catch {
4344
+ }
4345
+ }
4346
+ var _aCfg2 = this.config.getAgentConfig ? this.config.getAgentConfig(session.agentId) : null;
4347
+ var _dbConns2 = [];
4348
+ try {
4349
+ if (this.config.databaseManager) _dbConns2 = this.config.databaseManager.getAgentConnectionSummary(session.agentId);
4350
+ } catch {
4351
+ }
4352
+ var _systemPrompt = buildDefaultSystemPrompt(session.agentId, memoryContext, _hierarchyCtx, _aCfg2?.identity, _dbConns2);
4353
+ var _sessionContext;
4354
+ if (!this.keepAliveSessions.has(sessionId)) {
4355
+ _sessionContext = detectSessionContext({
4356
+ systemPrompt: _systemPrompt,
4357
+ isKeepAlive: false
4358
+ });
4359
+ }
4360
+ var tools = await getToolsForSession(sessionId, this.buildToolOptions(session.agentId, sessionId), {
4361
+ context: _sessionContext,
4362
+ userMessage: message
4363
+ });
4364
+ var agentConfig = {
4365
+ agentId: session.agentId,
4366
+ orgId: session.orgId,
4367
+ model,
4368
+ systemPrompt: _systemPrompt,
4369
+ tools
4370
+ };
4371
+ this.runSessionLoop(sessionId, agentConfig, messages, apiKey);
4372
+ }
4373
+ /**
4374
+ * Register a callback for when a session completes (or fails).
4375
+ */
4376
+ onSessionComplete(sessionId, callback) {
4377
+ var existing = this.sessionCompleteCallbacks.get(sessionId);
4378
+ if (!existing) {
4379
+ existing = [];
4380
+ this.sessionCompleteCallbacks.set(sessionId, existing);
4381
+ }
4382
+ existing.push(callback);
4383
+ }
4384
+ /**
4385
+ * Terminate an active session.
4386
+ */
4387
+ async terminateSession(sessionId) {
4388
+ var controller = this.activeSessions.get(sessionId);
4389
+ if (controller) {
4390
+ controller.abort();
4391
+ this.activeSessions.delete(sessionId);
4392
+ }
4393
+ if (this.sessionManager) {
4394
+ await this.sessionManager.updateSession(sessionId, { status: "completed" });
4395
+ }
4396
+ if (this._reportStatus) {
4397
+ this._reportStatus({
4398
+ status: this.activeSessions.size > 0 ? "online" : "idle",
4399
+ activeSessions: this.activeSessions.size,
4400
+ currentActivity: null
4401
+ });
4402
+ }
4403
+ }
4404
+ /**
4405
+ * Get a session by ID.
4406
+ */
4407
+ async getSession(sessionId) {
4408
+ this.ensureStarted();
4409
+ return this.sessionManager.getSession(sessionId);
4410
+ }
4411
+ /**
4412
+ * List sessions for an agent.
4413
+ */
4414
+ async listSessions(agentId, opts) {
4415
+ this.ensureStarted();
4416
+ return this.sessionManager.listSessions(agentId, opts);
4417
+ }
4418
+ /**
4419
+ * Spawn a sub-agent.
4420
+ */
4421
+ async spawnSubAgent(opts) {
4422
+ this.ensureStarted();
4423
+ var check = this.subAgentManager.canSpawn(opts.parentSessionId);
4424
+ if (!check.allowed) {
4425
+ return { id: "", childSessionId: "", agentId: "", status: "error", error: check.reason };
4426
+ }
4427
+ var parentSession = await this.sessionManager.getSession(opts.parentSessionId);
4428
+ if (!parentSession) {
4429
+ return { id: "", childSessionId: "", agentId: "", status: "error", error: "Parent session not found" };
4430
+ }
4431
+ var agentId = opts.agentId || parentSession.agentId;
4432
+ var childSession = await this.spawnSession({
4433
+ agentId,
4434
+ orgId: parentSession.orgId,
4435
+ message: `[Sub-Agent Task] ${opts.task}`,
4436
+ model: opts.model,
4437
+ parentSessionId: opts.parentSessionId
4438
+ });
4439
+ var id = nanoid3(12);
4440
+ this.subAgentManager.register({
4441
+ id,
4442
+ parentSessionId: opts.parentSessionId,
4443
+ childSessionId: childSession.id,
4444
+ agentId,
4445
+ task: opts.task,
4446
+ status: "active",
4447
+ createdAt: Date.now()
4448
+ });
4449
+ return {
4450
+ id,
4451
+ childSessionId: childSession.id,
4452
+ agentId,
4453
+ status: "accepted"
4454
+ };
4455
+ }
4456
+ /**
4457
+ * Handle inbound email.
4458
+ */
4459
+ async handleInboundEmail(email) {
4460
+ this.ensureStarted();
4461
+ if (!this.emailChannel) throw new Error("Email channel not initialized");
4462
+ return this.emailChannel.handleInbound(email);
4463
+ }
4464
+ /**
4465
+ * Schedule a follow-up.
4466
+ */
4467
+ async scheduleFollowUp(opts) {
4468
+ return this.followUpScheduler.schedule(opts);
4469
+ }
4470
+ /**
4471
+ * Cancel a follow-up.
4472
+ */
4473
+ async cancelFollowUp(followUpId) {
4474
+ return this.followUpScheduler.cancel(followUpId);
4475
+ }
4476
+ /**
4477
+ * Get the Hono sub-app for mounting.
4478
+ */
4479
+ getApp() {
4480
+ return this.gatewayApp;
4481
+ }
4482
+ /**
4483
+ * Get the number of active sessions.
4484
+ */
4485
+ getActiveSessionCount() {
4486
+ return this.activeSessions.size;
4487
+ }
4488
+ /**
4489
+ * Mark a session as "keep alive" — prevents it from completing when the LLM returns end_turn.
4490
+ * Used by MeetingMonitor to keep meeting sessions alive for incoming caption/chat updates.
4491
+ * The session stays in 'active' status and waits for the next sendMessage() call.
4492
+ */
4493
+ setKeepAlive(sessionId, keepAlive) {
4494
+ if (keepAlive) {
4495
+ this.keepAliveSessions.add(sessionId);
4496
+ console.log(`[runtime] Session ${sessionId} marked as keep-alive`);
4497
+ } else {
4498
+ this.keepAliveSessions.delete(sessionId);
4499
+ console.log(`[runtime] Session ${sessionId} keep-alive removed`);
4500
+ }
4501
+ }
4502
+ isKeepAlive(sessionId) {
4503
+ return this.keepAliveSessions.has(sessionId);
4504
+ }
4505
+ /**
4506
+ * Stop the runtime.
4507
+ */
4508
+ async stop() {
4509
+ if (!this.started) return;
4510
+ for (var [_sessionId, controller] of this.activeSessions) {
4511
+ controller.abort();
4512
+ }
4513
+ this.activeSessions.clear();
4514
+ if (this.heartbeatTimer) {
4515
+ clearInterval(this.heartbeatTimer);
4516
+ this.heartbeatTimer = null;
4517
+ }
4518
+ if (this.staleCheckTimer) {
4519
+ clearInterval(this.staleCheckTimer);
4520
+ this.staleCheckTimer = null;
4521
+ }
4522
+ if (this.sseKeepaliveTimer) {
4523
+ clearInterval(this.sseKeepaliveTimer);
4524
+ this.sseKeepaliveTimer = null;
4525
+ }
4526
+ this.followUpScheduler.stop();
4527
+ this.started = false;
4528
+ console.log("[runtime] Agent runtime stopped");
4529
+ }
4530
+ // ─── Private: Session Loop ─────────────────────────
4531
+ /**
4532
+ * Run the agent loop for a session with all long-running features wired in:
4533
+ * incremental persistence, heartbeat, retry, budget checks.
4534
+ */
4535
+ runSessionLoop(sessionId, agentConfig, initialMessages, apiKey, isResume) {
4536
+ var self = this;
4537
+ this.loopRunning.add(sessionId);
4538
+ var _loopCtx = this.keepAliveSessions.has(sessionId) ? "meeting" : "chat";
4539
+ var _loopModel = this.resolveModelForContext(agentConfig.agentId, _loopCtx);
4540
+ var hooks = createRuntimeHooks({
4541
+ engineDb: this.config.engineDb,
4542
+ agentId: agentConfig.agentId,
4543
+ orgId: agentConfig.orgId
4544
+ });
4545
+ var abortController = new AbortController();
4546
+ this.activeSessions.set(sessionId, abortController);
4547
+ if (isResume) {
4548
+ emitSessionEvent(sessionId, { type: "session_resumed", sessionId, turnCount: 0 });
4549
+ }
4550
+ hooks.onSessionStart(sessionId, agentConfig.agentId, agentConfig.orgId).catch(function() {
4551
+ });
4552
+ (async function() {
4553
+ try {
4554
+ if (_loopModel && agentConfig.model.modelId !== _loopModel.modelId) {
4555
+ agentConfig = { ...agentConfig, model: _loopModel };
4556
+ apiKey = await self.resolveApiKeyAsync(_loopModel.provider, agentConfig.agentId) || apiKey;
4557
+ }
4558
+ var _fallbackModels = [];
4559
+ if (self.config.getAgentConfig) {
4560
+ var _ac = self.config.getAgentConfig(agentConfig.agentId);
4561
+ if (_ac?.fallbackModels) _fallbackModels = _ac.fallbackModels;
4562
+ else if (_ac?.modelFallback?.fallbacks) _fallbackModels = _ac.modelFallback.fallbacks;
4563
+ }
4564
+ var result = await runAgentLoop(agentConfig, initialMessages, hooks, {
4565
+ apiKey,
4566
+ signal: abortController.signal,
4567
+ sessionId,
4568
+ retryConfig: self.config.retry,
4569
+ runtime: self,
4570
+ fallbackModels: _fallbackModels,
4571
+ resolveApiKey: async function(provider) {
4572
+ return self.resolveApiKeyAsync(provider, agentConfig.agentId);
4573
+ },
4574
+ // Incremental persistence — save messages after every turn
4575
+ onCheckpoint: async function(data) {
4576
+ try {
4577
+ await self.sessionManager.replaceMessages(sessionId, data.messages);
4578
+ await self.sessionManager.touchSession(sessionId, {
4579
+ tokenCount: data.tokenCount,
4580
+ turnCount: data.turnCount
4581
+ });
4582
+ emitSessionEvent(sessionId, {
4583
+ type: "checkpoint",
4584
+ turnNumber: data.turnCount,
4585
+ tokenCount: data.tokenCount,
4586
+ messageCount: data.messages.length
4587
+ });
4588
+ } catch (err) {
4589
+ console.warn(`[runtime] Checkpoint save error for ${sessionId}: ${err.message}`);
4590
+ }
4591
+ },
4592
+ // Heartbeat — keep session alive during long operations
4593
+ onHeartbeat: async function(data) {
4594
+ try {
4595
+ await self.sessionManager.touchSession(sessionId, {
4596
+ tokenCount: data.tokenCount,
4597
+ turnCount: data.turnCount
4598
+ });
4599
+ } catch {
4600
+ }
4601
+ },
4602
+ onEvent: function(event) {
4603
+ emitSessionEvent(sessionId, event);
4604
+ }
4605
+ });
4606
+ await self.sessionManager.replaceMessages(sessionId, result.messages);
4607
+ if (self.keepAliveSessions.has(sessionId)) {
4608
+ await self.sessionManager.touchSession(sessionId, {
4609
+ tokenCount: result.tokenCount,
4610
+ turnCount: result.turnCount
4611
+ });
4612
+ self.loopRunning.delete(sessionId);
4613
+ var queued = self.pendingMessages.get(sessionId);
4614
+ if (queued && queued.length > 0) {
4615
+ self.pendingMessages.delete(sessionId);
4616
+ console.log(`[runtime] Session ${sessionId} keep-alive loop done \u2014 draining ${queued.length} queued message(s)`);
4617
+ for (var qm of queued) {
4618
+ await self.sessionManager.appendMessage(sessionId, { role: "user", content: qm });
4619
+ }
4620
+ var freshSession = await self.sessionManager.getSession(sessionId);
4621
+ var freshMessages = freshSession?.messages || [...result.messages, ...queued.map(function(q) {
4622
+ return { role: "user", content: q };
4623
+ })];
4624
+ var lastMsg = freshMessages[freshMessages.length - 1];
4625
+ if (lastMsg && lastMsg.role !== "user") {
4626
+ var nudge = { role: "user", content: "[System] You have new messages above. Please respond." };
4627
+ freshMessages.push(nudge);
4628
+ await self.sessionManager.appendMessage(sessionId, nudge);
4629
+ }
4630
+ self.runSessionLoop(sessionId, agentConfig, freshMessages, apiKey);
4631
+ } else {
4632
+ console.log(`[runtime] Session ${sessionId} finished LLM turn but is keep-alive \u2014 staying active for incoming messages`);
4633
+ }
4634
+ return;
4635
+ }
4636
+ await self.sessionManager.updateSession(sessionId, {
4637
+ status: result.status,
4638
+ tokenCount: result.tokenCount,
4639
+ turnCount: result.turnCount
4640
+ });
4641
+ var cancelledChildren = self.subAgentManager.cancelAll(sessionId);
4642
+ for (var childId of cancelledChildren) {
4643
+ await self.terminateSession(childId).catch(function() {
4644
+ });
4645
+ }
4646
+ await hooks.onSessionEnd(sessionId, agentConfig.agentId, agentConfig.orgId);
4647
+ var cbs = self.sessionCompleteCallbacks.get(sessionId);
4648
+ if (cbs) {
4649
+ for (var cb of cbs) {
4650
+ try {
4651
+ cb(result);
4652
+ } catch {
4653
+ }
4654
+ }
4655
+ self.sessionCompleteCallbacks.delete(sessionId);
4656
+ }
4657
+ } catch (err) {
4658
+ console.error(`[runtime] Session ${sessionId} error: ${err.message}`);
4659
+ self.loopRunning.delete(sessionId);
4660
+ self.pendingMessages.delete(sessionId);
4661
+ await self.sessionManager.updateSession(sessionId, { status: "failed" }).catch(function() {
4662
+ });
4663
+ emitSessionEvent(sessionId, { type: "error", message: err.message });
4664
+ var cbs2 = self.sessionCompleteCallbacks.get(sessionId);
4665
+ if (cbs2) {
4666
+ for (var cb2 of cbs2) {
4667
+ try {
4668
+ cb2({ status: "failed", error: err.message });
4669
+ } catch {
4670
+ }
4671
+ }
4672
+ self.sessionCompleteCallbacks.delete(sessionId);
4673
+ }
4674
+ } finally {
4675
+ if (!self.keepAliveSessions.has(sessionId)) {
4676
+ self.loopRunning.delete(sessionId);
4677
+ self.pendingMessages.delete(sessionId);
4678
+ self.activeSessions.delete(sessionId);
4679
+ clearSessionToolState(sessionId);
4680
+ }
4681
+ }
4682
+ })();
4683
+ }
4684
+ // ─── Private: Session Resume ───────────────────────
4685
+ /**
4686
+ * Resume active sessions from DB on startup.
4687
+ * Sessions that were in-progress when the process died get picked up again.
4688
+ */
4689
+ async resumeActiveSessions() {
4690
+ try {
4691
+ var activeSessions = await this.sessionManager.findActiveSessions();
4692
+ if (activeSessions.length === 0) return;
4693
+ console.log(`[runtime] Found ${activeSessions.length} active session(s) to resume`);
4694
+ for (var sessionMeta of activeSessions) {
4695
+ try {
4696
+ var session = await this.sessionManager.getSession(sessionMeta.id);
4697
+ if (!session || session.messages.length === 0) {
4698
+ await this.sessionManager.updateSession(sessionMeta.id, { status: "completed" });
4699
+ continue;
4700
+ }
4701
+ if (sessionMeta.status === "failed") {
4702
+ var lastUserIdx = -1;
4703
+ var lastAssistantIdx = -1;
4704
+ for (var mi = session.messages.length - 1; mi >= 0; mi--) {
4705
+ if (session.messages[mi].role === "user" && lastUserIdx < 0) lastUserIdx = mi;
4706
+ if (session.messages[mi].role === "assistant" && lastAssistantIdx < 0) lastAssistantIdx = mi;
4707
+ if (lastUserIdx >= 0 && lastAssistantIdx >= 0) break;
4708
+ }
4709
+ if (lastUserIdx < lastAssistantIdx || lastUserIdx < 0) {
4710
+ await this.sessionManager.updateSession(sessionMeta.id, { status: "completed" });
4711
+ continue;
4712
+ }
4713
+ console.log(`[runtime] Recovering failed session ${session.id} \u2014 agent never replied to user message`);
4714
+ }
4715
+ await this.sessionManager.updateSession(session.id, { status: "active" });
4716
+ var model = this.config.defaultModel || DEFAULT_MODEL;
4717
+ var apiKey = await this.resolveApiKeyAsync(model.provider, session.agentId);
4718
+ if (!apiKey) {
4719
+ console.warn(`[runtime] Cannot resume session ${session.id}: no API key for ${model.provider}`);
4720
+ await this.sessionManager.updateSession(session.id, { status: "failed" });
4721
+ continue;
4722
+ }
4723
+ var mc = "";
4724
+ if (this.config.agentMemoryManager) {
4725
+ try {
4726
+ mc = await this.config.agentMemoryManager.generateMemoryContext(session.agentId);
4727
+ } catch {
4728
+ }
4729
+ }
4730
+ var _hc = "";
4731
+ if (this.config.hierarchyManager) {
4732
+ try {
4733
+ _hc = await this.config.hierarchyManager.buildManagerPrompt(session.agentId) || "";
4734
+ } catch {
4735
+ }
4736
+ }
4737
+ var _rCfg = this.config.getAgentConfig ? this.config.getAgentConfig(session.agentId) : null;
4738
+ var _dbC3 = [];
4739
+ try {
4740
+ if (this.config.databaseManager) _dbC3 = this.config.databaseManager.getAgentConnectionSummary(session.agentId);
4741
+ } catch {
4742
+ }
4743
+ var _resumePrompt = buildDefaultSystemPrompt(session.agentId, mc, _hc, _rCfg?.identity, _dbC3);
4744
+ var _resumeCtx = detectSessionContext({
4745
+ systemPrompt: _resumePrompt,
4746
+ isKeepAlive: this.keepAliveSessions.has(session.id)
4747
+ });
4748
+ var tools = await createToolsForContext(this.buildToolOptions(session.agentId, session.id), _resumeCtx, {
4749
+ sessionId: session.id
4750
+ });
4751
+ var agentConfig = {
4752
+ agentId: session.agentId,
4753
+ orgId: session.orgId,
4754
+ model,
4755
+ systemPrompt: _resumePrompt,
4756
+ tools
4757
+ };
4758
+ var resumeMessages = [...session.messages];
4759
+ resumeMessages.push({
4760
+ role: "system",
4761
+ content: `[Runtime Notice] Session resumed after process restart. Continue where you left off. Current time: ${(/* @__PURE__ */ new Date()).toISOString()}`
4762
+ });
4763
+ this.runSessionLoop(session.id, agentConfig, resumeMessages, apiKey, true);
4764
+ console.log(`[runtime] Resumed session ${session.id} (agent: ${session.agentId}, turns: ${session.turnCount})`);
4765
+ } catch (err) {
4766
+ console.error(`[runtime] Failed to resume session ${sessionMeta.id}: ${err.message}`);
4767
+ await this.sessionManager.updateSession(sessionMeta.id, { status: "failed" }).catch(function() {
4768
+ });
4769
+ }
4770
+ }
4771
+ } catch (err) {
4772
+ console.warn(`[runtime] Session resume scan failed: ${err.message}`);
4773
+ }
4774
+ }
4775
+ // ─── Private: Heartbeat + Stale Detection ──────────
4776
+ /**
4777
+ * Emit heartbeats for all active sessions (touch DB updated_at).
4778
+ */
4779
+ async emitHeartbeats() {
4780
+ for (var [sessionId] of this.activeSessions) {
4781
+ try {
4782
+ await this.sessionManager.touchSession(sessionId);
4783
+ } catch {
4784
+ }
4785
+ }
4786
+ }
4787
+ /**
4788
+ * Find and mark sessions that have gone stale (no heartbeat within timeout).
4789
+ */
4790
+ async cleanupStaleSessions(timeoutMs) {
4791
+ try {
4792
+ var staleIds = await this.sessionManager.markStaleSessions(timeoutMs);
4793
+ for (var id of staleIds) {
4794
+ var controller = this.activeSessions.get(id);
4795
+ if (controller) {
4796
+ controller.abort();
4797
+ this.activeSessions.delete(id);
4798
+ }
4799
+ console.warn(`[runtime] Marked stale session: ${id}`);
4800
+ }
4801
+ } catch {
4802
+ }
4803
+ }
4804
+ // ─── Private: Helpers ──────────────────────────────
4805
+ ensureStarted() {
4806
+ if (!this.started) {
4807
+ throw new Error("Runtime not started. Call runtime.start() first.");
4808
+ }
4809
+ }
4810
+ customProviders = [];
4811
+ orgToolSecurity = null;
4812
+ _resolveApiKey(provider, agentId) {
4813
+ return resolveApiKeyForProvider(provider, this.config.apiKeys, this.customProviders);
4814
+ }
4815
+ /** Resolve API key with org-scoped fallback (async — checks org integrations first) */
4816
+ async resolveApiKeyAsync(provider, agentId) {
4817
+ if (agentId && this.config.resolveOrgApiKey) {
4818
+ try {
4819
+ const orgKey = await this.config.resolveOrgApiKey(agentId, provider);
4820
+ if (orgKey) return orgKey;
4821
+ } catch {
4822
+ }
4823
+ }
4824
+ return resolveApiKeyForProvider(provider, this.config.apiKeys, this.customProviders);
4825
+ }
4826
+ /** Resolve per-context model from agent's modelRouting config */
4827
+ resolveModelForContext(agentId, context) {
4828
+ const agentCfg = this.config.getAgentConfig?.(agentId);
4829
+ return resolveModelForContext(agentCfg, context);
4830
+ }
4831
+ /** Returns all available providers (built-in + custom). */
4832
+ getProviderRegistry() {
4833
+ return { builtIn: PROVIDER_REGISTRY, custom: this.customProviders };
4834
+ }
4835
+ };
4836
+ function createAgentRuntime(config) {
4837
+ return new AgentRuntime(config);
4838
+ }
4839
+ function buildDefaultSystemPrompt(agentId, memoryContext, hierarchyContext, agentIdentity, dbConnections) {
4840
+ const wsDir = join(homedir(), ".agenticmail", "workspaces", agentId);
4841
+ var base = `You are an AI agent managed by AgenticMail Enterprise (agent: ${agentId}).
4842
+
4843
+ You have access to a comprehensive set of tools for completing tasks. Use them effectively.
4844
+
4845
+ Guidelines:
4846
+ - Be helpful, accurate, and professional
4847
+ - Use tools when they help accomplish the task
4848
+ - Explain your reasoning when making decisions
4849
+ - If you encounter an error, try an alternative approach
4850
+ - Respect organization policies and permissions
4851
+ - Keep responses concise unless detail is requested
4852
+ - For long tasks, work systematically and report progress
4853
+
4854
+ ## Your Workspace
4855
+ Your dedicated workspace directory is: ${wsDir}
4856
+ ALWAYS save files, deliverables, rendered videos, images, and outputs here \u2014 NEVER in /tmp.
4857
+ /tmp files are cleaned up by the OS and will be lost. Your workspace persists across sessions.
4858
+ Create subdirectories as needed (e.g. videos/, images/, projects/, exports/).
4859
+ - ACTIVELY USE YOUR MEMORY: After corrections, lessons, or insights, call memory_reflect to record them
4860
+ - Before complex tasks, call memory_context to recall relevant knowledge
4861
+ - Your memory persists across conversations \u2014 it's how you grow as an expert
4862
+
4863
+ Database Tools:
4864
+ - ent_db_* tools: For LOCAL databases \u2014 SQLite files in the workspace. Read-only inspection.
4865
+ - db_* tools (db_query, db_list_connections, db_list_tables, db_describe_table): For EXTERNAL databases granted to you by your admin.
4866
+ IMPORTANT: When asked about any database by name, ALWAYS use db_* tools first. Start with db_list_connections.
4867
+ ${dbConnections && dbConnections.length > 0 ? `
4868
+ Your External Database Access:
4869
+ ${dbConnections.map((c) => " - " + c).join("\n")}
4870
+ Use db_query with the connection name to query these databases.` : ""}
4871
+
4872
+ Current time: ${(/* @__PURE__ */ new Date()).toISOString()}`;
4873
+ if (memoryContext) {
4874
+ base += "\n\n" + memoryContext;
4875
+ }
4876
+ if (hierarchyContext) {
4877
+ base += "\n\n" + hierarchyContext;
4878
+ }
4879
+ base += "\n" + _remotionPrompt;
4880
+ if (agentIdentity?.language && agentIdentity.language !== "en-us") {
4881
+ var langMap = {
4882
+ "en-us": "English (American)",
4883
+ "en-gb": "English (British)",
4884
+ "en-au": "English (Australian)",
4885
+ "en-ca": "English (Canadian)",
4886
+ "en-in": "English (Indian)",
4887
+ "en-za": "English (South African)",
4888
+ "en-ie": "English (Irish)",
4889
+ "en-ng": "English (Nigerian)",
4890
+ "es": "Spanish",
4891
+ "es-mx": "Mexican Spanish",
4892
+ "es-ar": "Argentine Spanish",
4893
+ "es-co": "Colombian Spanish",
4894
+ "es-latam": "Latin American Spanish",
4895
+ "pt": "Portuguese",
4896
+ "pt-br": "Brazilian Portuguese",
4897
+ "fr": "French",
4898
+ "fr-ca": "Canadian French",
4899
+ "fr-be": "Belgian French",
4900
+ "fr-ch": "Swiss French",
4901
+ "fr-af": "African French",
4902
+ "zh": "Simplified Chinese (Mandarin)",
4903
+ "zh-tw": "Traditional Chinese (Mandarin)",
4904
+ "zh-yue": "Cantonese",
4905
+ "ar": "Modern Standard Arabic",
4906
+ "ar-eg": "Egyptian Arabic",
4907
+ "ar-sa": "Saudi Arabic",
4908
+ "ar-ma": "Moroccan Arabic",
4909
+ "de": "German",
4910
+ "de-at": "Austrian German",
4911
+ "de-ch": "Swiss German",
4912
+ "it": "Italian",
4913
+ "nl": "Dutch",
4914
+ "ru": "Russian",
4915
+ "pl": "Polish",
4916
+ "uk": "Ukrainian",
4917
+ "ja": "Japanese",
4918
+ "ko": "Korean",
4919
+ "hi": "Hindi",
4920
+ "bn": "Bengali",
4921
+ "ur": "Urdu",
4922
+ "ta": "Tamil",
4923
+ "te": "Telugu",
4924
+ "mr": "Marathi",
4925
+ "th": "Thai",
4926
+ "vi": "Vietnamese",
4927
+ "id": "Indonesian",
4928
+ "ms": "Malay",
4929
+ "tl": "Filipino (Tagalog)",
4930
+ "yo": "Yoruba",
4931
+ "ig": "Igbo",
4932
+ "ha": "Hausa",
4933
+ "sw": "Swahili",
4934
+ "am": "Amharic",
4935
+ "tr": "Turkish",
4936
+ "sv": "Swedish",
4937
+ "no": "Norwegian",
4938
+ "da": "Danish",
4939
+ "fi": "Finnish",
4940
+ "el": "Greek",
4941
+ "he": "Hebrew",
4942
+ "fa": "Persian (Farsi)",
4943
+ "ro": "Romanian",
4944
+ "hu": "Hungarian",
4945
+ "cs": "Czech",
4946
+ "sk": "Slovak",
4947
+ "bg": "Bulgarian",
4948
+ "hr": "Croatian",
4949
+ "sr": "Serbian",
4950
+ "ca": "Catalan",
4951
+ "gl": "Galician",
4952
+ "eu": "Basque",
4953
+ "af": "Afrikaans"
4954
+ };
4955
+ var langName = langMap[agentIdentity.language] || agentIdentity.language;
4956
+ base += `
4957
+
4958
+ ## Language Requirement
4959
+ **CRITICAL: You MUST respond in ${langName} at all times.**
4960
+ - ALL your responses, messages, emails, and communications must be written in ${langName}.
4961
+ - When users write to you in any language, understand their message but ALWAYS reply in ${langName}.
4962
+ - Tool calls, code, and technical identifiers remain in their original language (English/code), but all human-facing text must be in ${langName}.
4963
+ - This is a hard requirement set by your administrator. Do not switch languages even if asked.`;
4964
+ }
4965
+ return base;
4966
+ }
4967
+
4968
+ export {
4969
+ SessionManager,
4970
+ createRuntimeHooks,
4971
+ createNoopHooks,
4972
+ estimateTokens,
4973
+ estimateMessageTokens,
4974
+ callLLM,
4975
+ toolsToDefinitions,
4976
+ ToolRegistry,
4977
+ executeTool,
4978
+ runAgentLoop,
4979
+ SubAgentManager,
4980
+ EmailChannel,
4981
+ FollowUpScheduler,
4982
+ AgentRuntime,
4983
+ createAgentRuntime
4984
+ };