@bubblelab/bubble-core 0.1.197 → 0.1.199
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bubble-bundle.d.ts +6 -4
- package/dist/bubbles/service-bubble/ai-agent.d.ts +7 -0
- package/dist/bubbles/service-bubble/ai-agent.d.ts.map +1 -1
- package/dist/bubbles/service-bubble/ai-agent.js +248 -18
- package/dist/bubbles/service-bubble/ai-agent.js.map +1 -1
- package/dist/bubbles/tool-bubble/code-edit-tool.d.ts.map +1 -1
- package/dist/bubbles/tool-bubble/code-edit-tool.js +17 -6
- package/dist/bubbles/tool-bubble/code-edit-tool.js.map +1 -1
- package/dist/bubbles.json +1 -1
- package/package.json +2 -2
|
@@ -701,7 +701,14 @@ export declare class AIAgentBubble extends ServiceBubble<AIAgentParamsParsed, AI
|
|
|
701
701
|
private shouldStopAfterTools;
|
|
702
702
|
private shouldContinueToAgent;
|
|
703
703
|
private rescueAttempts;
|
|
704
|
+
/** Current graph messages — kept in sync by executeToolsWithHooks so that
|
|
705
|
+
* the use-capability tool can snapshot master state before delegation. */
|
|
706
|
+
private _currentGraphMessages;
|
|
707
|
+
/** Emit a trace event via executionMeta._onTrace (if wired by the host). */
|
|
708
|
+
private _trace;
|
|
704
709
|
private static readonly MAX_RESCUE_ATTEMPTS;
|
|
710
|
+
/** Max characters for a single tool result before truncation (~50k chars ≈ ~12k tokens). */
|
|
711
|
+
private static readonly MAX_TOOL_RESULT_CHARS;
|
|
705
712
|
constructor(params?: AIAgentParams, context?: BubbleContext, instanceId?: string);
|
|
706
713
|
testCredential(): Promise<boolean>;
|
|
707
714
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ai-agent.d.ts","sourceRoot":"","sources":["../../../src/bubbles/service-bubble/ai-agent.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"ai-agent.d.ts","sourceRoot":"","sources":["../../../src/bubbles/service-bubble/ai-agent.ts"],"names":[],"mappings":"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"}
|
|
@@ -309,7 +309,19 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
309
309
|
shouldStopAfterTools = false;
|
|
310
310
|
shouldContinueToAgent = false;
|
|
311
311
|
rescueAttempts = 0;
|
|
312
|
+
/** Current graph messages — kept in sync by executeToolsWithHooks so that
|
|
313
|
+
* the use-capability tool can snapshot master state before delegation. */
|
|
314
|
+
_currentGraphMessages = [];
|
|
315
|
+
/** Emit a trace event via executionMeta._onTrace (if wired by the host). */
|
|
316
|
+
_trace(source, message, data) {
|
|
317
|
+
const onTrace = this.context?.executionMeta?._onTrace;
|
|
318
|
+
if (typeof onTrace === 'function') {
|
|
319
|
+
onTrace(source, message, data);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
312
322
|
static MAX_RESCUE_ATTEMPTS = 1;
|
|
323
|
+
/** Max characters for a single tool result before truncation (~50k chars ≈ ~12k tokens). */
|
|
324
|
+
static MAX_TOOL_RESULT_CHARS = 50_000;
|
|
313
325
|
constructor(params = {
|
|
314
326
|
message: 'Hello, how are you?',
|
|
315
327
|
systemPrompt: 'You are a helpful AI assistant',
|
|
@@ -404,24 +416,40 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
404
416
|
await applyCapabilityPreprocessing(this.params, this.context, this.resolveCapabilityCredentials.bind(this));
|
|
405
417
|
// Extract execution metadata (used for conversation history + agent memory)
|
|
406
418
|
const execMeta = this.context?.executionMeta;
|
|
419
|
+
// Memory injection — tools, prompt, and reflection are built externally (Pro)
|
|
420
|
+
// and passed via executionMeta. This keeps all memory logic out of OSS.
|
|
421
|
+
const isCapabilityAgent = this.params.name?.startsWith('Capability Agent:');
|
|
407
422
|
// Inject Slack channel context into system prompt
|
|
408
|
-
|
|
409
|
-
if (
|
|
410
|
-
|
|
423
|
+
// Skip for capability agents (e.g. memory) — they only need their own prompt
|
|
424
|
+
if (!isCapabilityAgent) {
|
|
425
|
+
const slackChannel = execMeta?._slackChannel;
|
|
426
|
+
if (slackChannel) {
|
|
427
|
+
this.params.systemPrompt = `${this.params.systemPrompt}\n**Current Slack channel:** ${slackChannel}`;
|
|
428
|
+
}
|
|
429
|
+
// Inject bot identity and mention format context
|
|
430
|
+
const botDisplayName = execMeta?._selfBotDisplayName;
|
|
431
|
+
const selfBotUserId = execMeta?._selfBotUserId;
|
|
432
|
+
if (botDisplayName) {
|
|
433
|
+
let botContext = `**Your Slack identity:** ${botDisplayName}`;
|
|
434
|
+
if (selfBotUserId) {
|
|
435
|
+
botContext += ` (user ID: ${selfBotUserId})`;
|
|
436
|
+
}
|
|
437
|
+
botContext += `\nIn Slack messages, \`<@userId>\` is a mention — when you see \`<@${selfBotUserId ?? 'your_id'}>\`, that's someone addressing you.`;
|
|
438
|
+
botContext += `\nConversation messages are prefixed with \`[Name (userId)]\` — this tells you who sent each message. Use these names when addressing users.`;
|
|
439
|
+
this.params.systemPrompt = `${this.params.systemPrompt}\n${botContext}`;
|
|
440
|
+
}
|
|
411
441
|
}
|
|
412
442
|
// Auto-inject trigger conversation history if no explicit conversationHistory was provided
|
|
413
443
|
// This enables Slack thread context to automatically flow into AI agents
|
|
444
|
+
// Skip for capability agents (e.g. memory) — they only need their own prompt
|
|
414
445
|
// Check both legacy path (context.triggerConversationHistory) and new path (executionMeta)
|
|
415
|
-
if (!this.params.conversationHistory?.length) {
|
|
446
|
+
if (!isCapabilityAgent && !this.params.conversationHistory?.length) {
|
|
416
447
|
const convHistory = execMeta?.triggerConversationHistory ??
|
|
417
448
|
this.context?.triggerConversationHistory;
|
|
418
449
|
if (convHistory?.length) {
|
|
419
450
|
this.params.conversationHistory = convHistory;
|
|
420
451
|
}
|
|
421
452
|
}
|
|
422
|
-
// Memory injection — tools, prompt, and reflection are built externally (Pro)
|
|
423
|
-
// and passed via executionMeta. This keeps all memory logic out of OSS.
|
|
424
|
-
const isCapabilityAgent = this.params.name?.startsWith('Capability Agent:');
|
|
425
453
|
if (!isCapabilityAgent && this.params.memoryEnabled) {
|
|
426
454
|
const memoryTools = execMeta?.memoryTools;
|
|
427
455
|
const memorySystemPrompt = execMeta?.memorySystemPrompt;
|
|
@@ -1040,6 +1068,22 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1040
1068
|
const capDef = getCapability(capabilityId);
|
|
1041
1069
|
if (!capConfig || !capDef)
|
|
1042
1070
|
return { error: `Capability "${capabilityId}" not found` };
|
|
1071
|
+
// Snapshot master agent state before delegation so that the
|
|
1072
|
+
// subagent's beforeToolCall hook can save both states if an
|
|
1073
|
+
// approval interrupt is triggered (fixes multi-cap state leak).
|
|
1074
|
+
const execMeta = this.context?.executionMeta;
|
|
1075
|
+
if (execMeta && this._currentGraphMessages.length > 0) {
|
|
1076
|
+
const { mapChatMessagesToStoredMessages } = await import('@langchain/core/messages');
|
|
1077
|
+
execMeta._masterAgentSnapshot = {
|
|
1078
|
+
messages: mapChatMessagesToStoredMessages(this._currentGraphMessages),
|
|
1079
|
+
capabilityId,
|
|
1080
|
+
capabilityTask: task,
|
|
1081
|
+
};
|
|
1082
|
+
this._trace('use-capability', `snapshotted master state before delegating`, {
|
|
1083
|
+
masterMsgCount: this._currentGraphMessages.length,
|
|
1084
|
+
capabilityId,
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1043
1087
|
const subAgent = new AIAgentBubble({
|
|
1044
1088
|
message: task,
|
|
1045
1089
|
systemPrompt: '', // capability's systemPrompt fills this via beforeAction
|
|
@@ -1049,6 +1093,16 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1049
1093
|
credentials: this.params.credentials,
|
|
1050
1094
|
}, this.context, `capability-${capabilityId}`);
|
|
1051
1095
|
const result = await subAgent.action();
|
|
1096
|
+
// Clean up snapshot after delegation completes
|
|
1097
|
+
if (execMeta) {
|
|
1098
|
+
delete execMeta._masterAgentSnapshot;
|
|
1099
|
+
}
|
|
1100
|
+
this._trace('use-capability', `subAgent returned`, {
|
|
1101
|
+
capabilityId,
|
|
1102
|
+
success: result.success,
|
|
1103
|
+
pendingApproval: !!execMeta?._pendingApproval,
|
|
1104
|
+
shouldStopAfterTools: this.shouldStopAfterTools,
|
|
1105
|
+
});
|
|
1052
1106
|
if (!result.success) {
|
|
1053
1107
|
return { success: false, error: result.error };
|
|
1054
1108
|
}
|
|
@@ -1252,8 +1306,15 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1252
1306
|
const toolMessages = [];
|
|
1253
1307
|
let currentMessages = [...messages];
|
|
1254
1308
|
let hooksModifiedMessages = false;
|
|
1309
|
+
// Keep master snapshot in sync so use-capability can capture state
|
|
1310
|
+
this._currentGraphMessages = currentMessages;
|
|
1255
1311
|
// Reset stop flag at the start of tool execution
|
|
1256
1312
|
this.shouldStopAfterTools = false;
|
|
1313
|
+
this._trace('executeToolsWithHooks', `ENTRY`, {
|
|
1314
|
+
toolCallCount: toolCalls.length,
|
|
1315
|
+
toolCalls: toolCalls.map(tc => `${tc.name}(${tc.id?.slice(-8)})`).join(', '),
|
|
1316
|
+
totalMsgs: messages.length,
|
|
1317
|
+
});
|
|
1257
1318
|
// Execute each tool call
|
|
1258
1319
|
for (const toolCall of toolCalls) {
|
|
1259
1320
|
const tool = tools.find((t) => t.name === toolCall.name);
|
|
@@ -1346,11 +1407,17 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1346
1407
|
}
|
|
1347
1408
|
// Execute the tool
|
|
1348
1409
|
const toolOutput = await tool.invoke(toolCall.args);
|
|
1349
|
-
// Create tool message
|
|
1410
|
+
// Create tool message — cap result size to avoid blowing up LLM context
|
|
1411
|
+
let toolContent = typeof toolOutput === 'string'
|
|
1412
|
+
? toolOutput
|
|
1413
|
+
: JSON.stringify(toolOutput);
|
|
1414
|
+
if (toolContent.length > AIAgentBubble.MAX_TOOL_RESULT_CHARS) {
|
|
1415
|
+
toolContent =
|
|
1416
|
+
toolContent.slice(0, AIAgentBubble.MAX_TOOL_RESULT_CHARS) +
|
|
1417
|
+
`\n\n[... truncated — result was ${toolContent.length} chars, limit is ${AIAgentBubble.MAX_TOOL_RESULT_CHARS}]`;
|
|
1418
|
+
}
|
|
1350
1419
|
const toolMessage = new ToolMessage({
|
|
1351
|
-
content:
|
|
1352
|
-
? toolOutput
|
|
1353
|
-
: JSON.stringify(toolOutput),
|
|
1420
|
+
content: toolContent,
|
|
1354
1421
|
tool_call_id: toolCall.id,
|
|
1355
1422
|
});
|
|
1356
1423
|
toolMessages.push(toolMessage);
|
|
@@ -1431,6 +1498,17 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1431
1498
|
async createAgentGraph(llm, tools, systemPrompt) {
|
|
1432
1499
|
// Define the agent node
|
|
1433
1500
|
const agentNode = async ({ messages }) => {
|
|
1501
|
+
this._trace('agentNode', `LLM CALL`, {
|
|
1502
|
+
msgCount: messages.length,
|
|
1503
|
+
lastMsgType: messages[messages.length - 1]?._getType(),
|
|
1504
|
+
lastMsgPreview: (() => {
|
|
1505
|
+
const last = messages[messages.length - 1];
|
|
1506
|
+
if (!last)
|
|
1507
|
+
return '';
|
|
1508
|
+
const content = typeof last.content === 'string' ? last.content : JSON.stringify(last.content);
|
|
1509
|
+
return content.slice(0, 120);
|
|
1510
|
+
})(),
|
|
1511
|
+
});
|
|
1434
1512
|
// systemPrompt is already enhanced by beforeAction() if expectedOutputSchema was provided
|
|
1435
1513
|
// Use cache_control for Anthropic models to cache the system prompt across iterations
|
|
1436
1514
|
const isAnthropic = llm instanceof ChatAnthropic;
|
|
@@ -1632,25 +1710,36 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1632
1710
|
const lastAIMessage = aiMessages[aiMessages.length - 1];
|
|
1633
1711
|
// Check if the last AI message has tool calls
|
|
1634
1712
|
if (lastAIMessage?.tool_calls && lastAIMessage.tool_calls.length > 0) {
|
|
1713
|
+
this._trace('afterLLMCheck', `→ tools`, {
|
|
1714
|
+
toolCalls: lastAIMessage.tool_calls.map(tc => `${tc.name}(${tc.id?.slice(-8)})`).join(', '),
|
|
1715
|
+
});
|
|
1635
1716
|
return 'tools';
|
|
1636
1717
|
}
|
|
1718
|
+
this._trace('afterLLMCheck', `→ __end__ (no tool calls)`);
|
|
1637
1719
|
return '__end__';
|
|
1638
1720
|
};
|
|
1639
1721
|
// Define conditional edge after tools to check if we should stop
|
|
1640
1722
|
const shouldContinueAfterTools = () => {
|
|
1723
|
+
const execMeta = this.context?.executionMeta;
|
|
1724
|
+
this._trace('shouldContinueAfterTools', `CHECK`, {
|
|
1725
|
+
shouldStop: this.shouldStopAfterTools,
|
|
1726
|
+
pendingApproval: !!execMeta?._pendingApproval,
|
|
1727
|
+
});
|
|
1641
1728
|
// Check if the afterToolCall hook requested stopping
|
|
1642
1729
|
if (this.shouldStopAfterTools) {
|
|
1730
|
+
this._trace('shouldContinueAfterTools', `→ __end__ (shouldStopAfterTools)`);
|
|
1643
1731
|
return '__end__';
|
|
1644
1732
|
}
|
|
1645
1733
|
// Check for pending approval signal from sub-agent (shared via executionMeta).
|
|
1646
1734
|
// In multi-capability mode the master and sub-agent share the same BubbleContext,
|
|
1647
1735
|
// so a sub-agent setting _pendingApproval is visible here.
|
|
1648
|
-
const execMeta = this.context?.executionMeta;
|
|
1649
1736
|
if (execMeta?._pendingApproval) {
|
|
1737
|
+
this._trace('shouldContinueAfterTools', `→ __end__ (pendingApproval)`);
|
|
1650
1738
|
this.shouldStopAfterTools = true;
|
|
1651
1739
|
return '__end__';
|
|
1652
1740
|
}
|
|
1653
1741
|
// Otherwise continue back to agent
|
|
1742
|
+
this._trace('shouldContinueAfterTools', `→ agent (continue)`);
|
|
1654
1743
|
return 'agent';
|
|
1655
1744
|
};
|
|
1656
1745
|
// Build the graph
|
|
@@ -1685,10 +1774,128 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1685
1774
|
try {
|
|
1686
1775
|
// Build messages array starting with conversation history (for KV cache optimization)
|
|
1687
1776
|
const initialMessages = [];
|
|
1777
|
+
let enrichedMessage;
|
|
1688
1778
|
// Resume from saved agent state (lossless — preserves tool_calls, etc.)
|
|
1689
1779
|
const resumeExecMeta = this.context?.executionMeta;
|
|
1780
|
+
const resumeStateV2 = resumeExecMeta?._resumeAgentStateV2;
|
|
1690
1781
|
const resumeState = resumeExecMeta?._resumeAgentState;
|
|
1691
|
-
|
|
1782
|
+
// Clear stale _pendingApproval ONLY when actually resuming, so that
|
|
1783
|
+
// non-resume executeAgent calls (e.g. personality reflection) don't
|
|
1784
|
+
// wipe the flag before postFlowAction reads it.
|
|
1785
|
+
if (resumeExecMeta && (resumeStateV2 || resumeState)) {
|
|
1786
|
+
delete resumeExecMeta._pendingApproval;
|
|
1787
|
+
}
|
|
1788
|
+
if (resumeStateV2 && resumeStateV2.__version === 2) {
|
|
1789
|
+
// V2: Multi-cap scoped resume — master and subagent states are separate.
|
|
1790
|
+
// Restore the master's messages, find the pending use-capability call,
|
|
1791
|
+
// inject the subagent's state so it resumes via the V1 path, then
|
|
1792
|
+
// execute the use-capability tool directly.
|
|
1793
|
+
this._trace('v2-resume', `START`, {
|
|
1794
|
+
masterMsgs: resumeStateV2.masterState.length,
|
|
1795
|
+
subagentMsgs: resumeStateV2.subagentState.length,
|
|
1796
|
+
capabilityId: resumeStateV2.capabilityId,
|
|
1797
|
+
task: resumeStateV2.capabilityTask?.slice(0, 80),
|
|
1798
|
+
});
|
|
1799
|
+
const { mapStoredMessagesToChatMessages } = await import('@langchain/core/messages');
|
|
1800
|
+
const masterRestored = mapStoredMessagesToChatMessages(resumeStateV2.masterState);
|
|
1801
|
+
// Collect existing tool results
|
|
1802
|
+
const existingToolResultIds = new Set();
|
|
1803
|
+
for (const msg of masterRestored) {
|
|
1804
|
+
if (msg._getType() === 'tool') {
|
|
1805
|
+
const tm = msg;
|
|
1806
|
+
if (tm.tool_call_id)
|
|
1807
|
+
existingToolResultIds.add(tm.tool_call_id);
|
|
1808
|
+
}
|
|
1809
|
+
}
|
|
1810
|
+
// Build tool lookup for direct execution
|
|
1811
|
+
const toolsByName = new Map();
|
|
1812
|
+
if (tools) {
|
|
1813
|
+
for (const t of tools)
|
|
1814
|
+
toolsByName.set(t.name, t);
|
|
1815
|
+
}
|
|
1816
|
+
// Inject the subagent's state so the use-capability tool's subagent
|
|
1817
|
+
// picks it up via the V1 resume path in its own executeAgent call.
|
|
1818
|
+
// IMPORTANT: Delete _resumeAgentStateV2 BEFORE tool.invoke() so the
|
|
1819
|
+
// subagent doesn't re-enter the V2 path (it shares executionMeta).
|
|
1820
|
+
if (resumeExecMeta) {
|
|
1821
|
+
resumeExecMeta._resumeAgentState = resumeStateV2.subagentState;
|
|
1822
|
+
delete resumeExecMeta._resumeAgentStateV2;
|
|
1823
|
+
}
|
|
1824
|
+
// Repair master messages: find the pending use-capability tool call
|
|
1825
|
+
// and execute it (which re-creates the subagent that resumes via V1).
|
|
1826
|
+
const repairedMessages = [];
|
|
1827
|
+
for (let i = 0; i < masterRestored.length; i++) {
|
|
1828
|
+
repairedMessages.push(masterRestored[i]);
|
|
1829
|
+
const msg = masterRestored[i];
|
|
1830
|
+
if (msg._getType() !== 'ai')
|
|
1831
|
+
continue;
|
|
1832
|
+
const aiMsg = msg;
|
|
1833
|
+
const pendingCalls = aiMsg.tool_calls?.filter((tc) => tc.id && !existingToolResultIds.has(tc.id));
|
|
1834
|
+
if (!pendingCalls?.length)
|
|
1835
|
+
continue;
|
|
1836
|
+
for (const tc of pendingCalls) {
|
|
1837
|
+
if (toolsByName.has(tc.name)) {
|
|
1838
|
+
try {
|
|
1839
|
+
this._trace('v2-resume', `executing tool "${tc.name}"`, {
|
|
1840
|
+
callId: tc.id,
|
|
1841
|
+
args: JSON.stringify(tc.args).slice(0, 200),
|
|
1842
|
+
});
|
|
1843
|
+
const tool = toolsByName.get(tc.name);
|
|
1844
|
+
// Sync _currentGraphMessages so use-capability can snapshot
|
|
1845
|
+
// master state for the subagent's beforeToolCall hook.
|
|
1846
|
+
// Without this, _masterAgentSnapshot is empty during V2 resume
|
|
1847
|
+
// and subsequent approvals fall to the V1 path, corrupting state.
|
|
1848
|
+
this._currentGraphMessages = [...repairedMessages];
|
|
1849
|
+
const result = await tool.invoke(tc.args);
|
|
1850
|
+
let content = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1851
|
+
if (content.length > AIAgentBubble.MAX_TOOL_RESULT_CHARS) {
|
|
1852
|
+
content =
|
|
1853
|
+
content.slice(0, AIAgentBubble.MAX_TOOL_RESULT_CHARS) +
|
|
1854
|
+
`\n\n[... truncated — result was ${content.length} chars, limit is ${AIAgentBubble.MAX_TOOL_RESULT_CHARS}]`;
|
|
1855
|
+
}
|
|
1856
|
+
repairedMessages.push(new ToolMessage({ content, tool_call_id: tc.id }));
|
|
1857
|
+
}
|
|
1858
|
+
catch (err) {
|
|
1859
|
+
console.warn(`[AIAgent] V2 resume: tool execution failed for "${tc.name}":`, err);
|
|
1860
|
+
repairedMessages.push(new ToolMessage({
|
|
1861
|
+
content: `Tool execution failed: ${err instanceof Error ? err.message : String(err)}`,
|
|
1862
|
+
tool_call_id: tc.id,
|
|
1863
|
+
}));
|
|
1864
|
+
}
|
|
1865
|
+
}
|
|
1866
|
+
else {
|
|
1867
|
+
repairedMessages.push(new ToolMessage({
|
|
1868
|
+
content: 'This action has been approved by the user. You may now execute this tool.',
|
|
1869
|
+
tool_call_id: tc.id,
|
|
1870
|
+
}));
|
|
1871
|
+
}
|
|
1872
|
+
existingToolResultIds.add(tc.id);
|
|
1873
|
+
}
|
|
1874
|
+
}
|
|
1875
|
+
// Clean up remaining resume state and pre-approval flag so that
|
|
1876
|
+
// subsequent subagents (created by normal master loop) don't
|
|
1877
|
+
// accidentally pick up stale state.
|
|
1878
|
+
// Note: _resumeAgentStateV2 already deleted above (before tool.invoke).
|
|
1879
|
+
if (resumeExecMeta) {
|
|
1880
|
+
delete resumeExecMeta._resumeAgentState;
|
|
1881
|
+
delete resumeExecMeta._approvedAction;
|
|
1882
|
+
}
|
|
1883
|
+
this._trace('v2-resume', `DONE`, {
|
|
1884
|
+
repairedMessages: repairedMessages.length,
|
|
1885
|
+
types: repairedMessages.map((m) => m._getType()).join(','),
|
|
1886
|
+
pendingApproval: !!resumeExecMeta?._pendingApproval,
|
|
1887
|
+
shouldStopAfterTools: this.shouldStopAfterTools,
|
|
1888
|
+
});
|
|
1889
|
+
// If a new approval was triggered during V2 resume (subagent set _pendingApproval),
|
|
1890
|
+
// we should stop the master loop immediately to avoid re-executing.
|
|
1891
|
+
if (resumeExecMeta?._pendingApproval) {
|
|
1892
|
+
this._trace('v2-resume', `_pendingApproval detected — will stop master loop`);
|
|
1893
|
+
this.shouldStopAfterTools = true;
|
|
1894
|
+
}
|
|
1895
|
+
initialMessages.push(...repairedMessages);
|
|
1896
|
+
}
|
|
1897
|
+
else if (resumeState && Array.isArray(resumeState) && resumeState.length > 0) {
|
|
1898
|
+
this._trace('v1-resume', `restoring ${resumeState.length} messages (single-cap/subagent path)`);
|
|
1692
1899
|
const { mapStoredMessagesToChatMessages } = await import('@langchain/core/messages');
|
|
1693
1900
|
const restored = mapStoredMessagesToChatMessages(resumeState);
|
|
1694
1901
|
// Collect existing tool_call_ids that already have ToolMessage responses
|
|
@@ -1740,7 +1947,12 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1740
1947
|
console.log(`[AIAgent] Resume: executing approved tool "${tc.name}" directly`);
|
|
1741
1948
|
const tool = toolsByName.get(tc.name);
|
|
1742
1949
|
const result = await tool.invoke(tc.args);
|
|
1743
|
-
|
|
1950
|
+
let content = typeof result === 'string' ? result : JSON.stringify(result);
|
|
1951
|
+
if (content.length > AIAgentBubble.MAX_TOOL_RESULT_CHARS) {
|
|
1952
|
+
content =
|
|
1953
|
+
content.slice(0, AIAgentBubble.MAX_TOOL_RESULT_CHARS) +
|
|
1954
|
+
`\n\n[... truncated — result was ${content.length} chars, limit is ${AIAgentBubble.MAX_TOOL_RESULT_CHARS}]`;
|
|
1955
|
+
}
|
|
1744
1956
|
repairedMessages.push(new ToolMessage({ content, tool_call_id: tc.id }));
|
|
1745
1957
|
}
|
|
1746
1958
|
catch (err) {
|
|
@@ -1772,7 +1984,8 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1772
1984
|
else if (conversationHistory && conversationHistory.length > 0) {
|
|
1773
1985
|
// Normal path: lossy ConversationMessage[] → BaseMessage[]
|
|
1774
1986
|
// This enables KV cache optimization by keeping previous turns as separate messages
|
|
1775
|
-
// Pop the trigger (last entry) —
|
|
1987
|
+
// Pop the trigger (last entry) — its enriched content replaces the raw message below
|
|
1988
|
+
const lastHistoryMsg = conversationHistory[conversationHistory.length - 1];
|
|
1776
1989
|
for (const historyMsg of conversationHistory.slice(0, -1)) {
|
|
1777
1990
|
switch (historyMsg.role) {
|
|
1778
1991
|
case 'user':
|
|
@@ -1793,13 +2006,20 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1793
2006
|
break;
|
|
1794
2007
|
}
|
|
1795
2008
|
}
|
|
2009
|
+
// Use the enriched content from the last conversation history entry
|
|
2010
|
+
// (includes user name, timezone) instead of the raw trigger message
|
|
2011
|
+
if (lastHistoryMsg.role === 'user') {
|
|
2012
|
+
enrichedMessage = lastHistoryMsg.content;
|
|
2013
|
+
}
|
|
1796
2014
|
}
|
|
1797
2015
|
// Create the current human message with text and optional images
|
|
2016
|
+
// Prefer enriched message (with user name/timezone) over raw trigger text
|
|
2017
|
+
const triggerMessage = enrichedMessage ?? message;
|
|
1798
2018
|
let humanMessage;
|
|
1799
2019
|
if (images && images.length > 0) {
|
|
1800
2020
|
console.log('[AIAgent] Creating multimodal message with', images.length, 'images');
|
|
1801
2021
|
// Create multimodal content array
|
|
1802
|
-
const content = [{ type: 'text', text:
|
|
2022
|
+
const content = [{ type: 'text', text: triggerMessage }];
|
|
1803
2023
|
// Add images to content
|
|
1804
2024
|
for (const image of images) {
|
|
1805
2025
|
let imageUrl;
|
|
@@ -1843,16 +2063,26 @@ export class AIAgentBubble extends ServiceBubble {
|
|
|
1843
2063
|
}
|
|
1844
2064
|
else {
|
|
1845
2065
|
// Text-only message
|
|
1846
|
-
humanMessage = new HumanMessage(
|
|
2066
|
+
humanMessage = new HumanMessage(triggerMessage);
|
|
1847
2067
|
}
|
|
1848
2068
|
// In the resume flow the trigger message is already part of the saved
|
|
1849
2069
|
// state (it was the HumanMessage that started the original execution).
|
|
1850
2070
|
// Re-appending it causes the LLM to see the same request twice, which
|
|
1851
2071
|
// triggers a duplicate tool call. Skip it when resuming.
|
|
1852
|
-
if (!resumeState) {
|
|
2072
|
+
if (!resumeState && !resumeStateV2) {
|
|
1853
2073
|
initialMessages.push(humanMessage);
|
|
1854
2074
|
}
|
|
2075
|
+
this._trace('agent-loop', `STARTING graph.invoke`, {
|
|
2076
|
+
initialMsgCount: initialMessages.length,
|
|
2077
|
+
initialMsgTypes: initialMessages.map((m) => m._getType()).join(','),
|
|
2078
|
+
shouldStopAfterTools: this.shouldStopAfterTools,
|
|
2079
|
+
maxIterations,
|
|
2080
|
+
});
|
|
1855
2081
|
const result = await graph.invoke({ messages: initialMessages }, { recursionLimit: maxIterations });
|
|
2082
|
+
this._trace('agent-loop', `graph.invoke COMPLETED`, {
|
|
2083
|
+
totalMessages: result.messages.length,
|
|
2084
|
+
pendingApproval: !!this.context?.executionMeta?._pendingApproval,
|
|
2085
|
+
});
|
|
1856
2086
|
console.log('[AIAgent] Graph execution completed');
|
|
1857
2087
|
console.log('[AIAgent] Total messages:', result.messages.length);
|
|
1858
2088
|
iterations = result.messages.length;
|