llmist 5.1.0 → 6.1.0

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.
@@ -690,23 +690,26 @@ Produces: { "items": ["first", "second"] }`);
690
690
  * Record a gadget execution result in the message history.
691
691
  * Creates an assistant message with the gadget invocation and a user message with the result.
692
692
  *
693
+ * The invocationId is shown to the LLM so it can reference previous calls when building dependencies.
694
+ *
693
695
  * @param gadget - Name of the gadget that was executed
694
696
  * @param parameters - Parameters that were passed to the gadget
695
697
  * @param result - Text result from the gadget execution
698
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
696
699
  * @param media - Optional media outputs from the gadget
697
700
  * @param mediaIds - Optional IDs for the media outputs
698
701
  */
699
- addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
702
+ addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds) {
700
703
  const paramStr = this.formatBlockParameters(parameters, "");
701
704
  this.messages.push({
702
705
  role: "assistant",
703
- content: `${this.startPrefix}${gadget}
706
+ content: `${this.startPrefix}${gadget}:${invocationId}
704
707
  ${paramStr}
705
708
  ${this.endPrefix}`
706
709
  });
707
710
  if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
708
711
  const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
709
- const textWithIds = `Result: ${result}
712
+ const textWithIds = `Result (${invocationId}): ${result}
710
713
  ${idRefs}`;
711
714
  const parts = [text(textWithIds)];
712
715
  for (const item of media) {
@@ -720,7 +723,7 @@ ${idRefs}`;
720
723
  } else {
721
724
  this.messages.push({
722
725
  role: "user",
723
- content: `Result: ${result}`
726
+ content: `Result (${invocationId}): ${result}`
724
727
  });
725
728
  }
726
729
  return this;
@@ -1072,6 +1075,611 @@ var init_registry = __esm({
1072
1075
  }
1073
1076
  });
1074
1077
 
1078
+ // src/core/execution-tree.ts
1079
+ var ExecutionTree;
1080
+ var init_execution_tree = __esm({
1081
+ "src/core/execution-tree.ts"() {
1082
+ "use strict";
1083
+ ExecutionTree = class {
1084
+ nodes = /* @__PURE__ */ new Map();
1085
+ rootIds = [];
1086
+ eventListeners = /* @__PURE__ */ new Map();
1087
+ eventIdCounter = 0;
1088
+ invocationIdToNodeId = /* @__PURE__ */ new Map();
1089
+ // For async event streaming
1090
+ eventQueue = [];
1091
+ eventWaiters = [];
1092
+ isCompleted = false;
1093
+ /**
1094
+ * Base depth for all nodes in this tree.
1095
+ * Used when this tree is a subagent's view into a parent tree.
1096
+ */
1097
+ baseDepth;
1098
+ /**
1099
+ * Parent node ID for subagent trees.
1100
+ * All root nodes in this tree will have this as their parentId.
1101
+ */
1102
+ parentNodeId;
1103
+ constructor(options) {
1104
+ this.baseDepth = options?.baseDepth ?? 0;
1105
+ this.parentNodeId = options?.parentNodeId ?? null;
1106
+ }
1107
+ // ===========================================================================
1108
+ // Node ID Generation
1109
+ // ===========================================================================
1110
+ generateLLMCallId(iteration, parentId) {
1111
+ if (parentId) {
1112
+ return `llm_${parentId}_${iteration}`;
1113
+ }
1114
+ return `llm_${iteration}`;
1115
+ }
1116
+ gadgetIdCounter = 0;
1117
+ generateGadgetId(invocationId) {
1118
+ return `gadget_${invocationId}_${++this.gadgetIdCounter}`;
1119
+ }
1120
+ // ===========================================================================
1121
+ // Event Emission
1122
+ // ===========================================================================
1123
+ emit(event) {
1124
+ const listeners = this.eventListeners.get(event.type);
1125
+ if (listeners) {
1126
+ for (const listener of listeners) {
1127
+ try {
1128
+ listener(event);
1129
+ } catch (error) {
1130
+ console.error(`Error in event listener for ${event.type}:`, error);
1131
+ }
1132
+ }
1133
+ }
1134
+ const allListeners = this.eventListeners.get("*");
1135
+ if (allListeners) {
1136
+ for (const listener of allListeners) {
1137
+ try {
1138
+ listener(event);
1139
+ } catch (error) {
1140
+ console.error("Error in wildcard event listener:", error);
1141
+ }
1142
+ }
1143
+ }
1144
+ if (this.eventWaiters.length > 0) {
1145
+ const waiter = this.eventWaiters.shift();
1146
+ if (waiter) waiter(event);
1147
+ } else {
1148
+ this.eventQueue.push(event);
1149
+ }
1150
+ }
1151
+ createBaseEventProps(node) {
1152
+ return {
1153
+ eventId: ++this.eventIdCounter,
1154
+ timestamp: Date.now(),
1155
+ nodeId: node.id,
1156
+ parentId: node.parentId,
1157
+ depth: node.depth,
1158
+ path: node.path
1159
+ };
1160
+ }
1161
+ // ===========================================================================
1162
+ // Node Creation
1163
+ // ===========================================================================
1164
+ /**
1165
+ * Add a new LLM call node to the tree.
1166
+ */
1167
+ addLLMCall(params) {
1168
+ const parentId = params.parentId ?? this.parentNodeId;
1169
+ const parent = parentId ? this.nodes.get(parentId) : null;
1170
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
1171
+ const path = parent ? [...parent.path] : [];
1172
+ const id = this.generateLLMCallId(params.iteration, parentId);
1173
+ path.push(id);
1174
+ const node = {
1175
+ id,
1176
+ type: "llm_call",
1177
+ parentId,
1178
+ depth,
1179
+ path,
1180
+ createdAt: Date.now(),
1181
+ completedAt: null,
1182
+ iteration: params.iteration,
1183
+ model: params.model,
1184
+ request: params.request,
1185
+ response: "",
1186
+ children: []
1187
+ };
1188
+ this.nodes.set(id, node);
1189
+ if (!parentId) {
1190
+ this.rootIds.push(id);
1191
+ } else if (parent) {
1192
+ parent.children.push(id);
1193
+ }
1194
+ this.emit({
1195
+ type: "llm_call_start",
1196
+ ...this.createBaseEventProps(node),
1197
+ iteration: node.iteration,
1198
+ model: node.model,
1199
+ request: node.request
1200
+ });
1201
+ return node;
1202
+ }
1203
+ /**
1204
+ * Add text to an LLM call's response (for streaming).
1205
+ */
1206
+ appendLLMResponse(nodeId, chunk) {
1207
+ const node = this.nodes.get(nodeId);
1208
+ if (!node || node.type !== "llm_call") {
1209
+ throw new Error(`LLM call node not found: ${nodeId}`);
1210
+ }
1211
+ node.response += chunk;
1212
+ this.emit({
1213
+ type: "llm_call_stream",
1214
+ ...this.createBaseEventProps(node),
1215
+ chunk
1216
+ });
1217
+ }
1218
+ /**
1219
+ * Complete an LLM call node.
1220
+ */
1221
+ completeLLMCall(nodeId, params) {
1222
+ const node = this.nodes.get(nodeId);
1223
+ if (!node || node.type !== "llm_call") {
1224
+ throw new Error(`LLM call node not found: ${nodeId}`);
1225
+ }
1226
+ const llmNode = node;
1227
+ llmNode.completedAt = Date.now();
1228
+ if (params.response !== void 0) llmNode.response = params.response;
1229
+ if (params.usage) llmNode.usage = params.usage;
1230
+ if (params.finishReason !== void 0) llmNode.finishReason = params.finishReason;
1231
+ if (params.cost !== void 0) llmNode.cost = params.cost;
1232
+ this.emit({
1233
+ type: "llm_call_complete",
1234
+ ...this.createBaseEventProps(node),
1235
+ response: llmNode.response,
1236
+ usage: llmNode.usage,
1237
+ finishReason: llmNode.finishReason,
1238
+ cost: llmNode.cost
1239
+ });
1240
+ }
1241
+ /**
1242
+ * Mark an LLM call as failed.
1243
+ */
1244
+ failLLMCall(nodeId, error, recovered) {
1245
+ const node = this.nodes.get(nodeId);
1246
+ if (!node || node.type !== "llm_call") {
1247
+ throw new Error(`LLM call node not found: ${nodeId}`);
1248
+ }
1249
+ const llmNode = node;
1250
+ llmNode.completedAt = Date.now();
1251
+ this.emit({
1252
+ type: "llm_call_error",
1253
+ ...this.createBaseEventProps(node),
1254
+ error,
1255
+ recovered
1256
+ });
1257
+ }
1258
+ /**
1259
+ * Add a new gadget node to the tree.
1260
+ */
1261
+ addGadget(params) {
1262
+ const parentId = params.parentId ?? this.getCurrentLLMCallId() ?? this.parentNodeId;
1263
+ const parent = parentId ? this.nodes.get(parentId) : null;
1264
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
1265
+ const path = parent ? [...parent.path] : [];
1266
+ const id = this.generateGadgetId(params.invocationId);
1267
+ path.push(id);
1268
+ const node = {
1269
+ id,
1270
+ type: "gadget",
1271
+ parentId,
1272
+ depth,
1273
+ path,
1274
+ createdAt: Date.now(),
1275
+ completedAt: null,
1276
+ invocationId: params.invocationId,
1277
+ name: params.name,
1278
+ parameters: params.parameters,
1279
+ dependencies: params.dependencies ?? [],
1280
+ state: "pending",
1281
+ children: [],
1282
+ isSubagent: false
1283
+ };
1284
+ this.nodes.set(id, node);
1285
+ this.invocationIdToNodeId.set(params.invocationId, id);
1286
+ if (parent) {
1287
+ parent.children.push(id);
1288
+ }
1289
+ this.emit({
1290
+ type: "gadget_call",
1291
+ ...this.createBaseEventProps(node),
1292
+ invocationId: node.invocationId,
1293
+ name: node.name,
1294
+ parameters: node.parameters,
1295
+ dependencies: node.dependencies
1296
+ });
1297
+ return node;
1298
+ }
1299
+ /**
1300
+ * Mark a gadget as started (running).
1301
+ */
1302
+ startGadget(nodeId) {
1303
+ const node = this.nodes.get(nodeId);
1304
+ if (!node || node.type !== "gadget") {
1305
+ throw new Error(`Gadget node not found: ${nodeId}`);
1306
+ }
1307
+ const gadgetNode = node;
1308
+ gadgetNode.state = "running";
1309
+ this.emit({
1310
+ type: "gadget_start",
1311
+ ...this.createBaseEventProps(node),
1312
+ invocationId: gadgetNode.invocationId,
1313
+ name: gadgetNode.name
1314
+ });
1315
+ }
1316
+ /**
1317
+ * Complete a gadget node successfully.
1318
+ */
1319
+ completeGadget(nodeId, params) {
1320
+ const node = this.nodes.get(nodeId);
1321
+ if (!node || node.type !== "gadget") {
1322
+ throw new Error(`Gadget node not found: ${nodeId}`);
1323
+ }
1324
+ const gadgetNode = node;
1325
+ gadgetNode.completedAt = Date.now();
1326
+ gadgetNode.state = params.error ? "failed" : "completed";
1327
+ if (params.result !== void 0) gadgetNode.result = params.result;
1328
+ if (params.error) gadgetNode.error = params.error;
1329
+ if (params.executionTimeMs !== void 0) gadgetNode.executionTimeMs = params.executionTimeMs;
1330
+ if (params.cost !== void 0) gadgetNode.cost = params.cost;
1331
+ if (params.media) gadgetNode.media = params.media;
1332
+ gadgetNode.isSubagent = gadgetNode.children.some((childId) => {
1333
+ const child = this.nodes.get(childId);
1334
+ return child?.type === "llm_call";
1335
+ });
1336
+ if (params.error) {
1337
+ this.emit({
1338
+ type: "gadget_error",
1339
+ ...this.createBaseEventProps(node),
1340
+ invocationId: gadgetNode.invocationId,
1341
+ name: gadgetNode.name,
1342
+ error: params.error,
1343
+ executionTimeMs: params.executionTimeMs ?? 0
1344
+ });
1345
+ } else {
1346
+ this.emit({
1347
+ type: "gadget_complete",
1348
+ ...this.createBaseEventProps(node),
1349
+ invocationId: gadgetNode.invocationId,
1350
+ name: gadgetNode.name,
1351
+ result: params.result ?? "",
1352
+ executionTimeMs: params.executionTimeMs ?? 0,
1353
+ cost: params.cost,
1354
+ media: params.media
1355
+ });
1356
+ }
1357
+ }
1358
+ /**
1359
+ * Mark a gadget as skipped due to dependency failure.
1360
+ */
1361
+ skipGadget(nodeId, failedDependency, failedDependencyError, reason) {
1362
+ const node = this.nodes.get(nodeId);
1363
+ if (!node || node.type !== "gadget") {
1364
+ throw new Error(`Gadget node not found: ${nodeId}`);
1365
+ }
1366
+ const gadgetNode = node;
1367
+ gadgetNode.completedAt = Date.now();
1368
+ gadgetNode.state = "skipped";
1369
+ gadgetNode.failedDependency = failedDependency;
1370
+ gadgetNode.error = failedDependencyError;
1371
+ const error = reason === "controller_skip" ? "Skipped by controller" : `Dependency ${failedDependency} failed: ${failedDependencyError}`;
1372
+ this.emit({
1373
+ type: "gadget_skipped",
1374
+ ...this.createBaseEventProps(node),
1375
+ invocationId: gadgetNode.invocationId,
1376
+ name: gadgetNode.name,
1377
+ reason,
1378
+ error,
1379
+ failedDependency,
1380
+ failedDependencyError
1381
+ });
1382
+ }
1383
+ // ===========================================================================
1384
+ // Text Events (pure notifications, not tree nodes)
1385
+ // ===========================================================================
1386
+ /**
1387
+ * Emit a text event (notification only, not stored in tree).
1388
+ */
1389
+ emitText(content, llmCallNodeId) {
1390
+ const node = this.nodes.get(llmCallNodeId);
1391
+ if (!node) {
1392
+ throw new Error(`Node not found: ${llmCallNodeId}`);
1393
+ }
1394
+ this.emit({
1395
+ type: "text",
1396
+ ...this.createBaseEventProps(node),
1397
+ content
1398
+ });
1399
+ }
1400
+ // ===========================================================================
1401
+ // Query Methods
1402
+ // ===========================================================================
1403
+ /**
1404
+ * Get a node by ID.
1405
+ */
1406
+ getNode(id) {
1407
+ return this.nodes.get(id);
1408
+ }
1409
+ /**
1410
+ * Get a gadget node by invocation ID.
1411
+ */
1412
+ getNodeByInvocationId(invocationId) {
1413
+ const nodeId = this.invocationIdToNodeId.get(invocationId);
1414
+ if (!nodeId) return void 0;
1415
+ const node = this.nodes.get(nodeId);
1416
+ return node?.type === "gadget" ? node : void 0;
1417
+ }
1418
+ /**
1419
+ * Get all root nodes (depth 0 for this tree).
1420
+ */
1421
+ getRoots() {
1422
+ return this.rootIds.map((id) => this.nodes.get(id)).filter((node) => node !== void 0);
1423
+ }
1424
+ /**
1425
+ * Get children of a node.
1426
+ */
1427
+ getChildren(id) {
1428
+ const node = this.nodes.get(id);
1429
+ if (!node) return [];
1430
+ return node.children.map((childId) => this.nodes.get(childId)).filter((child) => child !== void 0);
1431
+ }
1432
+ /**
1433
+ * Get ancestors of a node (from root to parent).
1434
+ */
1435
+ getAncestors(id) {
1436
+ const node = this.nodes.get(id);
1437
+ if (!node) return [];
1438
+ const ancestors = [];
1439
+ let currentId = node.parentId;
1440
+ while (currentId) {
1441
+ const ancestor = this.nodes.get(currentId);
1442
+ if (ancestor) {
1443
+ ancestors.unshift(ancestor);
1444
+ currentId = ancestor.parentId;
1445
+ } else {
1446
+ break;
1447
+ }
1448
+ }
1449
+ return ancestors;
1450
+ }
1451
+ /**
1452
+ * Get all descendants of a node.
1453
+ */
1454
+ getDescendants(id, type) {
1455
+ const node = this.nodes.get(id);
1456
+ if (!node) return [];
1457
+ const descendants = [];
1458
+ const stack = [...node.children];
1459
+ while (stack.length > 0) {
1460
+ const childId = stack.pop();
1461
+ const child = this.nodes.get(childId);
1462
+ if (child) {
1463
+ if (!type || child.type === type) {
1464
+ descendants.push(child);
1465
+ }
1466
+ stack.push(...child.children);
1467
+ }
1468
+ }
1469
+ return descendants;
1470
+ }
1471
+ /**
1472
+ * Get the current (most recent incomplete) LLM call node.
1473
+ */
1474
+ getCurrentLLMCallId() {
1475
+ for (let i = this.rootIds.length - 1; i >= 0; i--) {
1476
+ const node = this.nodes.get(this.rootIds[i]);
1477
+ if (node?.type === "llm_call" && !node.completedAt) {
1478
+ return node.id;
1479
+ }
1480
+ }
1481
+ return void 0;
1482
+ }
1483
+ // ===========================================================================
1484
+ // Aggregation Methods (for subagent support)
1485
+ // ===========================================================================
1486
+ /**
1487
+ * Get total cost for entire tree.
1488
+ */
1489
+ getTotalCost() {
1490
+ let total = 0;
1491
+ for (const node of this.nodes.values()) {
1492
+ if (node.type === "llm_call" && node.cost) {
1493
+ total += node.cost;
1494
+ } else if (node.type === "gadget" && node.cost) {
1495
+ total += node.cost;
1496
+ }
1497
+ }
1498
+ return total;
1499
+ }
1500
+ /**
1501
+ * Get total cost for a subtree (node and all descendants).
1502
+ */
1503
+ getSubtreeCost(nodeId) {
1504
+ const node = this.nodes.get(nodeId);
1505
+ if (!node) return 0;
1506
+ let total = 0;
1507
+ if (node.type === "llm_call" && node.cost) {
1508
+ total += node.cost;
1509
+ } else if (node.type === "gadget" && node.cost) {
1510
+ total += node.cost;
1511
+ }
1512
+ for (const descendant of this.getDescendants(nodeId)) {
1513
+ if (descendant.type === "llm_call" && descendant.cost) {
1514
+ total += descendant.cost;
1515
+ } else if (descendant.type === "gadget" && descendant.cost) {
1516
+ total += descendant.cost;
1517
+ }
1518
+ }
1519
+ return total;
1520
+ }
1521
+ /**
1522
+ * Get token usage for entire tree.
1523
+ */
1524
+ getTotalTokens() {
1525
+ let input = 0;
1526
+ let output = 0;
1527
+ let cached = 0;
1528
+ for (const node of this.nodes.values()) {
1529
+ if (node.type === "llm_call") {
1530
+ const llmNode = node;
1531
+ if (llmNode.usage) {
1532
+ input += llmNode.usage.inputTokens;
1533
+ output += llmNode.usage.outputTokens;
1534
+ cached += llmNode.usage.cachedInputTokens ?? 0;
1535
+ }
1536
+ }
1537
+ }
1538
+ return { input, output, cached };
1539
+ }
1540
+ /**
1541
+ * Get token usage for a subtree.
1542
+ */
1543
+ getSubtreeTokens(nodeId) {
1544
+ const node = this.nodes.get(nodeId);
1545
+ if (!node) return { input: 0, output: 0, cached: 0 };
1546
+ let input = 0;
1547
+ let output = 0;
1548
+ let cached = 0;
1549
+ const nodesToProcess = [node, ...this.getDescendants(nodeId)];
1550
+ for (const n of nodesToProcess) {
1551
+ if (n.type === "llm_call") {
1552
+ const llmNode = n;
1553
+ if (llmNode.usage) {
1554
+ input += llmNode.usage.inputTokens;
1555
+ output += llmNode.usage.outputTokens;
1556
+ cached += llmNode.usage.cachedInputTokens ?? 0;
1557
+ }
1558
+ }
1559
+ }
1560
+ return { input, output, cached };
1561
+ }
1562
+ /**
1563
+ * Collect all media from a subtree.
1564
+ */
1565
+ getSubtreeMedia(nodeId) {
1566
+ const node = this.nodes.get(nodeId);
1567
+ if (!node) return [];
1568
+ const media = [];
1569
+ const nodesToProcess = node.type === "gadget" ? [node] : [];
1570
+ nodesToProcess.push(...this.getDescendants(nodeId, "gadget"));
1571
+ for (const n of nodesToProcess) {
1572
+ if (n.type === "gadget") {
1573
+ const gadgetNode = n;
1574
+ if (gadgetNode.media) {
1575
+ media.push(...gadgetNode.media);
1576
+ }
1577
+ }
1578
+ }
1579
+ return media;
1580
+ }
1581
+ /**
1582
+ * Check if a subtree is complete (all nodes finished).
1583
+ */
1584
+ isSubtreeComplete(nodeId) {
1585
+ const node = this.nodes.get(nodeId);
1586
+ if (!node) return true;
1587
+ if (!node.completedAt) return false;
1588
+ for (const descendant of this.getDescendants(nodeId)) {
1589
+ if (!descendant.completedAt) return false;
1590
+ }
1591
+ return true;
1592
+ }
1593
+ /**
1594
+ * Get node counts.
1595
+ */
1596
+ getNodeCount() {
1597
+ let llmCalls = 0;
1598
+ let gadgets = 0;
1599
+ for (const node of this.nodes.values()) {
1600
+ if (node.type === "llm_call") llmCalls++;
1601
+ else if (node.type === "gadget") gadgets++;
1602
+ }
1603
+ return { llmCalls, gadgets };
1604
+ }
1605
+ // ===========================================================================
1606
+ // Event Subscription
1607
+ // ===========================================================================
1608
+ /**
1609
+ * Subscribe to events of a specific type.
1610
+ * Returns unsubscribe function.
1611
+ *
1612
+ * @param type - Event type to subscribe to (use "*" for all events)
1613
+ * @param listener - Callback function that receives matching events
1614
+ * @returns Unsubscribe function
1615
+ *
1616
+ * @example
1617
+ * ```typescript
1618
+ * const unsubscribe = tree.on("gadget_complete", (event) => {
1619
+ * if (event.type === "gadget_complete") {
1620
+ * console.log(`Gadget ${event.name} completed`);
1621
+ * }
1622
+ * });
1623
+ * ```
1624
+ */
1625
+ on(type, listener) {
1626
+ if (!this.eventListeners.has(type)) {
1627
+ this.eventListeners.set(type, /* @__PURE__ */ new Set());
1628
+ }
1629
+ const listeners = this.eventListeners.get(type);
1630
+ listeners.add(listener);
1631
+ return () => {
1632
+ listeners.delete(listener);
1633
+ };
1634
+ }
1635
+ /**
1636
+ * Subscribe to all events.
1637
+ */
1638
+ onAll(listener) {
1639
+ return this.on("*", listener);
1640
+ }
1641
+ /**
1642
+ * Get async iterable of all events.
1643
+ * Events are yielded as they occur.
1644
+ */
1645
+ async *events() {
1646
+ while (!this.isCompleted) {
1647
+ while (this.eventQueue.length > 0) {
1648
+ yield this.eventQueue.shift();
1649
+ }
1650
+ if (this.isCompleted) break;
1651
+ const event = await new Promise((resolve) => {
1652
+ if (this.eventQueue.length > 0) {
1653
+ resolve(this.eventQueue.shift());
1654
+ } else {
1655
+ this.eventWaiters.push(resolve);
1656
+ }
1657
+ });
1658
+ yield event;
1659
+ }
1660
+ while (this.eventQueue.length > 0) {
1661
+ yield this.eventQueue.shift();
1662
+ }
1663
+ }
1664
+ /**
1665
+ * Mark the tree as complete (no more events will be emitted).
1666
+ */
1667
+ complete() {
1668
+ this.isCompleted = true;
1669
+ for (const waiter of this.eventWaiters) {
1670
+ }
1671
+ this.eventWaiters = [];
1672
+ }
1673
+ /**
1674
+ * Check if the tree is complete.
1675
+ */
1676
+ isComplete() {
1677
+ return this.isCompleted;
1678
+ }
1679
+ };
1680
+ }
1681
+ });
1682
+
1075
1683
  // src/gadgets/media-store.ts
1076
1684
  import { randomBytes } from "node:crypto";
1077
1685
  import { mkdir, rm, writeFile } from "node:fs/promises";
@@ -2367,8 +2975,8 @@ var init_conversation_manager = __esm({
2367
2975
  addAssistantMessage(content) {
2368
2976
  this.historyBuilder.addAssistant(content);
2369
2977
  }
2370
- addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2371
- this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2978
+ addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds) {
2979
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds);
2372
2980
  }
2373
2981
  getMessages() {
2374
2982
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -3549,7 +4157,7 @@ var init_executor = __esm({
3549
4157
  init_exceptions();
3550
4158
  init_parser();
3551
4159
  GadgetExecutor = class {
3552
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent) {
4160
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
3553
4161
  this.registry = registry;
3554
4162
  this.requestHumanInput = requestHumanInput;
3555
4163
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -3558,6 +4166,9 @@ var init_executor = __esm({
3558
4166
  this.agentConfig = agentConfig;
3559
4167
  this.subagentConfig = subagentConfig;
3560
4168
  this.onSubagentEvent = onSubagentEvent;
4169
+ this.tree = tree;
4170
+ this.parentNodeId = parentNodeId;
4171
+ this.baseDepth = baseDepth;
3561
4172
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3562
4173
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3563
4174
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3568,15 +4179,21 @@ var init_executor = __esm({
3568
4179
  /**
3569
4180
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
3570
4181
  * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
4182
+ * Returns both the promise and a cancel function to clear the timeout when no longer needed.
3571
4183
  */
3572
4184
  createTimeoutPromise(gadgetName, timeoutMs, abortController) {
3573
- return new Promise((_, reject) => {
3574
- setTimeout(() => {
4185
+ let timeoutId;
4186
+ const promise = new Promise((_, reject) => {
4187
+ timeoutId = setTimeout(() => {
3575
4188
  const timeoutError = new TimeoutException(gadgetName, timeoutMs);
3576
4189
  abortController.abort(timeoutError.message);
3577
4190
  reject(timeoutError);
3578
4191
  }, timeoutMs);
3579
4192
  });
4193
+ return {
4194
+ promise,
4195
+ cancel: () => clearTimeout(timeoutId)
4196
+ };
3580
4197
  }
3581
4198
  /**
3582
4199
  * Unify gadget execute result to consistent internal format.
@@ -3698,6 +4315,8 @@ var init_executor = __esm({
3698
4315
  });
3699
4316
  }
3700
4317
  };
4318
+ const gadgetNodeId = this.tree?.getNodeByInvocationId(call.invocationId)?.id;
4319
+ const gadgetDepth = gadgetNodeId ? this.tree?.getNode(gadgetNodeId)?.depth ?? this.baseDepth : this.baseDepth;
3701
4320
  const ctx = {
3702
4321
  reportCost,
3703
4322
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
@@ -3705,7 +4324,11 @@ var init_executor = __esm({
3705
4324
  agentConfig: this.agentConfig,
3706
4325
  subagentConfig: this.subagentConfig,
3707
4326
  invocationId: call.invocationId,
3708
- onSubagentEvent: this.onSubagentEvent
4327
+ onSubagentEvent: this.onSubagentEvent,
4328
+ // Tree context for subagent support - use gadget's own node ID
4329
+ tree: this.tree,
4330
+ nodeId: gadgetNodeId,
4331
+ depth: gadgetDepth
3709
4332
  };
3710
4333
  let rawResult;
3711
4334
  if (timeoutMs && timeoutMs > 0) {
@@ -3713,10 +4336,15 @@ var init_executor = __esm({
3713
4336
  gadgetName: call.gadgetName,
3714
4337
  timeoutMs
3715
4338
  });
3716
- rawResult = await Promise.race([
3717
- Promise.resolve(gadget.execute(validatedParameters, ctx)),
3718
- this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
3719
- ]);
4339
+ const timeout = this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController);
4340
+ try {
4341
+ rawResult = await Promise.race([
4342
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
4343
+ timeout.promise
4344
+ ]);
4345
+ } finally {
4346
+ timeout.cancel();
4347
+ }
3720
4348
  } else {
3721
4349
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3722
4350
  }
@@ -3911,10 +4539,11 @@ var init_stream_processor = __esm({
3911
4539
  logger;
3912
4540
  parser;
3913
4541
  executor;
3914
- stopOnGadgetError;
3915
- canRecoverFromGadgetError;
4542
+ // Execution Tree context
4543
+ tree;
4544
+ parentNodeId;
4545
+ baseDepth;
3916
4546
  responseText = "";
3917
- executionHalted = false;
3918
4547
  observerFailureCount = 0;
3919
4548
  // Dependency tracking for gadget execution DAG
3920
4549
  /** Gadgets waiting for their dependencies to complete */
@@ -3923,18 +4552,30 @@ var init_stream_processor = __esm({
3923
4552
  completedResults = /* @__PURE__ */ new Map();
3924
4553
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
3925
4554
  failedInvocations = /* @__PURE__ */ new Set();
4555
+ /** Promises for independent gadgets currently executing (fire-and-forget) */
4556
+ inFlightExecutions = /* @__PURE__ */ new Map();
4557
+ /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4558
+ completedResultsQueue = [];
3926
4559
  constructor(options) {
3927
4560
  this.iteration = options.iteration;
3928
4561
  this.registry = options.registry;
3929
4562
  this.hooks = options.hooks ?? {};
3930
4563
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3931
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3932
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4564
+ this.tree = options.tree;
4565
+ this.parentNodeId = options.parentNodeId ?? null;
4566
+ this.baseDepth = options.baseDepth ?? 0;
3933
4567
  this.parser = new GadgetCallParser({
3934
4568
  startPrefix: options.gadgetStartPrefix,
3935
4569
  endPrefix: options.gadgetEndPrefix,
3936
4570
  argPrefix: options.gadgetArgPrefix
3937
4571
  });
4572
+ const wrappedOnSubagentEvent = options.onSubagentEvent ? (event) => {
4573
+ this.completedResultsQueue.push({
4574
+ type: "subagent_event",
4575
+ subagentEvent: event
4576
+ });
4577
+ options.onSubagentEvent?.(event);
4578
+ } : void 0;
3938
4579
  this.executor = new GadgetExecutor(
3939
4580
  options.registry,
3940
4581
  options.requestHumanInput,
@@ -3945,7 +4586,11 @@ var init_stream_processor = __esm({
3945
4586
  options.mediaStore,
3946
4587
  options.agentConfig,
3947
4588
  options.subagentConfig,
3948
- options.onSubagentEvent
4589
+ wrappedOnSubagentEvent,
4590
+ // Tree context for gadget execution
4591
+ options.tree,
4592
+ options.parentNodeId,
4593
+ options.baseDepth
3949
4594
  );
3950
4595
  }
3951
4596
  /**
@@ -3996,7 +4641,7 @@ var init_stream_processor = __esm({
3996
4641
  usage,
3997
4642
  logger: this.logger
3998
4643
  };
3999
- await this.hooks.observers.onStreamChunk(context);
4644
+ await this.hooks.observers?.onStreamChunk?.(context);
4000
4645
  });
4001
4646
  await this.runObserversInParallel(chunkObservers);
4002
4647
  }
@@ -4014,24 +4659,7 @@ var init_stream_processor = __esm({
4014
4659
  }
4015
4660
  }
4016
4661
  }
4017
- if (this.executionHalted) {
4018
- this.logger.info("Breaking from LLM stream due to gadget error");
4019
- break;
4020
- }
4021
- }
4022
- if (!this.executionHalted) {
4023
- for (const event of this.parser.finalize()) {
4024
- for await (const processedEvent of this.processEventGenerator(event)) {
4025
- yield processedEvent;
4026
- if (processedEvent.type === "gadget_result") {
4027
- didExecuteGadgets = true;
4028
- if (processedEvent.result.breaksLoop) {
4029
- shouldBreakLoop = true;
4030
- }
4031
- }
4032
- }
4033
- }
4034
- for await (const evt of this.processPendingGadgetsGenerator()) {
4662
+ for (const evt of this.drainCompletedResults()) {
4035
4663
  yield evt;
4036
4664
  if (evt.type === "gadget_result") {
4037
4665
  didExecuteGadgets = true;
@@ -4041,6 +4669,44 @@ var init_stream_processor = __esm({
4041
4669
  }
4042
4670
  }
4043
4671
  }
4672
+ for (const event of this.parser.finalize()) {
4673
+ for await (const processedEvent of this.processEventGenerator(event)) {
4674
+ yield processedEvent;
4675
+ if (processedEvent.type === "gadget_result") {
4676
+ didExecuteGadgets = true;
4677
+ if (processedEvent.result.breaksLoop) {
4678
+ shouldBreakLoop = true;
4679
+ }
4680
+ }
4681
+ }
4682
+ }
4683
+ for await (const evt of this.waitForInFlightExecutions()) {
4684
+ yield evt;
4685
+ if (evt.type === "gadget_result") {
4686
+ didExecuteGadgets = true;
4687
+ if (evt.result.breaksLoop) {
4688
+ shouldBreakLoop = true;
4689
+ }
4690
+ }
4691
+ }
4692
+ for (const evt of this.drainCompletedResults()) {
4693
+ yield evt;
4694
+ if (evt.type === "gadget_result") {
4695
+ didExecuteGadgets = true;
4696
+ if (evt.result.breaksLoop) {
4697
+ shouldBreakLoop = true;
4698
+ }
4699
+ }
4700
+ }
4701
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4702
+ yield evt;
4703
+ if (evt.type === "gadget_result") {
4704
+ didExecuteGadgets = true;
4705
+ if (evt.result.breaksLoop) {
4706
+ shouldBreakLoop = true;
4707
+ }
4708
+ }
4709
+ }
4044
4710
  let finalMessage = this.responseText;
4045
4711
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4046
4712
  const context = {
@@ -4061,21 +4727,8 @@ var init_stream_processor = __esm({
4061
4727
  };
4062
4728
  yield completionEvent;
4063
4729
  }
4064
- /**
4065
- * Process a single parsed event (text or gadget call).
4066
- * @deprecated Use processEventGenerator for real-time streaming
4067
- */
4068
- async processEvent(event) {
4069
- if (event.type === "text") {
4070
- return this.processTextEvent(event);
4071
- } else if (event.type === "gadget_call") {
4072
- return this.processGadgetCall(event.call);
4073
- }
4074
- return [event];
4075
- }
4076
4730
  /**
4077
4731
  * Process a single parsed event, yielding events in real-time.
4078
- * Generator version of processEvent for streaming support.
4079
4732
  */
4080
4733
  async *processEventGenerator(event) {
4081
4734
  if (event.type === "text") {
@@ -4117,12 +4770,6 @@ var init_stream_processor = __esm({
4117
4770
  * After each execution, pending gadgets are checked to see if they can now run.
4118
4771
  */
4119
4772
  async processGadgetCall(call) {
4120
- if (this.executionHalted) {
4121
- this.logger.debug("Skipping gadget execution due to previous error", {
4122
- gadgetName: call.gadgetName
4123
- });
4124
- return [];
4125
- }
4126
4773
  const events = [];
4127
4774
  events.push({ type: "gadget_call", call });
4128
4775
  if (call.dependencies.length > 0) {
@@ -4173,13 +4820,16 @@ var init_stream_processor = __esm({
4173
4820
  * when parsed (before execution), enabling real-time UI feedback.
4174
4821
  */
4175
4822
  async *processGadgetCallGenerator(call) {
4176
- if (this.executionHalted) {
4177
- this.logger.debug("Skipping gadget execution due to previous error", {
4178
- gadgetName: call.gadgetName
4823
+ yield { type: "gadget_call", call };
4824
+ if (this.tree) {
4825
+ this.tree.addGadget({
4826
+ invocationId: call.invocationId,
4827
+ name: call.gadgetName,
4828
+ parameters: call.parameters ?? {},
4829
+ dependencies: call.dependencies,
4830
+ parentId: this.parentNodeId
4179
4831
  });
4180
- return;
4181
4832
  }
4182
- yield { type: "gadget_call", call };
4183
4833
  if (call.dependencies.length > 0) {
4184
4834
  if (call.dependencies.includes(call.invocationId)) {
4185
4835
  this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
@@ -4216,13 +4866,16 @@ var init_stream_processor = __esm({
4216
4866
  this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4217
4867
  return;
4218
4868
  }
4869
+ for await (const evt of this.executeGadgetGenerator(call)) {
4870
+ yield evt;
4871
+ }
4872
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4873
+ yield evt;
4874
+ }
4875
+ return;
4219
4876
  }
4220
- for await (const evt of this.executeGadgetGenerator(call)) {
4221
- yield evt;
4222
- }
4223
- for await (const evt of this.processPendingGadgetsGenerator()) {
4224
- yield evt;
4225
- }
4877
+ const executionPromise = this.executeGadgetAndCollect(call);
4878
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4226
4879
  }
4227
4880
  /**
4228
4881
  * Execute a gadget through the full hook lifecycle.
@@ -4237,15 +4890,6 @@ var init_stream_processor = __esm({
4237
4890
  error: call.parseError,
4238
4891
  rawParameters: call.parametersRaw
4239
4892
  });
4240
- const shouldContinue = await this.checkCanRecoverFromError(
4241
- call.parseError,
4242
- call.gadgetName,
4243
- "parse",
4244
- call.parameters
4245
- );
4246
- if (!shouldContinue) {
4247
- this.executionHalted = true;
4248
- }
4249
4893
  }
4250
4894
  let parameters = call.parameters ?? {};
4251
4895
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4288,7 +4932,7 @@ var init_stream_processor = __esm({
4288
4932
  parameters,
4289
4933
  logger: this.logger
4290
4934
  };
4291
- await this.hooks.observers.onGadgetExecutionStart(context);
4935
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4292
4936
  });
4293
4937
  }
4294
4938
  await this.runObserversInParallel(startObservers);
@@ -4357,7 +5001,7 @@ var init_stream_processor = __esm({
4357
5001
  cost: result.cost,
4358
5002
  logger: this.logger
4359
5003
  };
4360
- await this.hooks.observers.onGadgetExecutionComplete(context);
5004
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4361
5005
  });
4362
5006
  }
4363
5007
  await this.runObserversInParallel(completeObservers);
@@ -4366,18 +5010,6 @@ var init_stream_processor = __esm({
4366
5010
  this.failedInvocations.add(result.invocationId);
4367
5011
  }
4368
5012
  events.push({ type: "gadget_result", result });
4369
- if (result.error) {
4370
- const errorType = this.determineErrorType(call, result);
4371
- const shouldContinue = await this.checkCanRecoverFromError(
4372
- result.error,
4373
- result.gadgetName,
4374
- errorType,
4375
- result.parameters
4376
- );
4377
- if (!shouldContinue) {
4378
- this.executionHalted = true;
4379
- }
4380
- }
4381
5013
  return events;
4382
5014
  }
4383
5015
  /**
@@ -4391,15 +5023,6 @@ var init_stream_processor = __esm({
4391
5023
  error: call.parseError,
4392
5024
  rawParameters: call.parametersRaw
4393
5025
  });
4394
- const shouldContinue = await this.checkCanRecoverFromError(
4395
- call.parseError,
4396
- call.gadgetName,
4397
- "parse",
4398
- call.parameters
4399
- );
4400
- if (!shouldContinue) {
4401
- this.executionHalted = true;
4402
- }
4403
5026
  }
4404
5027
  let parameters = call.parameters ?? {};
4405
5028
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4442,10 +5065,16 @@ var init_stream_processor = __esm({
4442
5065
  parameters,
4443
5066
  logger: this.logger
4444
5067
  };
4445
- await this.hooks.observers.onGadgetExecutionStart(context);
5068
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4446
5069
  });
4447
5070
  }
4448
5071
  await this.runObserversInParallel(startObservers);
5072
+ if (this.tree) {
5073
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5074
+ if (gadgetNode) {
5075
+ this.tree.startGadget(gadgetNode.id);
5076
+ }
5077
+ }
4449
5078
  let result;
4450
5079
  if (shouldSkip) {
4451
5080
  result = {
@@ -4511,27 +5140,84 @@ var init_stream_processor = __esm({
4511
5140
  cost: result.cost,
4512
5141
  logger: this.logger
4513
5142
  };
4514
- await this.hooks.observers.onGadgetExecutionComplete(context);
5143
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4515
5144
  });
4516
5145
  }
4517
5146
  await this.runObserversInParallel(completeObservers);
5147
+ if (this.tree) {
5148
+ const gadgetNode = this.tree.getNodeByInvocationId(result.invocationId);
5149
+ if (gadgetNode) {
5150
+ if (result.error) {
5151
+ this.tree.completeGadget(gadgetNode.id, {
5152
+ error: result.error,
5153
+ executionTimeMs: result.executionTimeMs,
5154
+ cost: result.cost
5155
+ });
5156
+ } else {
5157
+ this.tree.completeGadget(gadgetNode.id, {
5158
+ result: result.result,
5159
+ executionTimeMs: result.executionTimeMs,
5160
+ cost: result.cost,
5161
+ media: result.media
5162
+ });
5163
+ }
5164
+ }
5165
+ }
4518
5166
  this.completedResults.set(result.invocationId, result);
4519
5167
  if (result.error) {
4520
5168
  this.failedInvocations.add(result.invocationId);
4521
5169
  }
4522
5170
  yield { type: "gadget_result", result };
4523
- if (result.error) {
4524
- const errorType = this.determineErrorType(call, result);
4525
- const shouldContinue = await this.checkCanRecoverFromError(
4526
- result.error,
4527
- result.gadgetName,
4528
- errorType,
4529
- result.parameters
4530
- );
4531
- if (!shouldContinue) {
4532
- this.executionHalted = true;
5171
+ }
5172
+ /**
5173
+ * Execute a gadget and push events to the completed results queue (non-blocking).
5174
+ * Used for fire-and-forget parallel execution of independent gadgets.
5175
+ * Results are pushed to completedResultsQueue for real-time streaming to the caller.
5176
+ */
5177
+ async executeGadgetAndCollect(call) {
5178
+ for await (const evt of this.executeGadgetGenerator(call)) {
5179
+ this.completedResultsQueue.push(evt);
5180
+ }
5181
+ }
5182
+ /**
5183
+ * Drain all completed results from the queue.
5184
+ * Used to yield results as they complete during stream processing.
5185
+ * @returns Generator that yields all events currently in the queue
5186
+ */
5187
+ *drainCompletedResults() {
5188
+ while (this.completedResultsQueue.length > 0) {
5189
+ yield this.completedResultsQueue.shift();
5190
+ }
5191
+ }
5192
+ /**
5193
+ * Wait for all in-flight gadget executions to complete, yielding events in real-time.
5194
+ * Called at stream end to ensure all parallel executions finish.
5195
+ * Results and subagent events are pushed to completedResultsQueue during execution.
5196
+ * This generator yields queued events while polling, enabling real-time display
5197
+ * of subagent activity (LLM calls, nested gadgets) during long-running gadgets.
5198
+ * Clears the inFlightExecutions map after all gadgets complete.
5199
+ */
5200
+ async *waitForInFlightExecutions() {
5201
+ if (this.inFlightExecutions.size === 0) {
5202
+ return;
5203
+ }
5204
+ this.logger.debug("Waiting for in-flight gadget executions", {
5205
+ count: this.inFlightExecutions.size,
5206
+ invocationIds: Array.from(this.inFlightExecutions.keys())
5207
+ });
5208
+ const allDone = Promise.all(this.inFlightExecutions.values()).then(() => "done");
5209
+ const POLL_INTERVAL_MS = 100;
5210
+ while (true) {
5211
+ const result = await Promise.race([
5212
+ allDone,
5213
+ new Promise((resolve) => setTimeout(() => resolve("poll"), POLL_INTERVAL_MS))
5214
+ ]);
5215
+ yield* this.drainCompletedResults();
5216
+ if (result === "done") {
5217
+ break;
4533
5218
  }
4534
5219
  }
5220
+ this.inFlightExecutions.clear();
4535
5221
  }
4536
5222
  /**
4537
5223
  * Handle a gadget that cannot execute because a dependency failed.
@@ -4556,6 +5242,12 @@ var init_stream_processor = __esm({
4556
5242
  }
4557
5243
  if (action.action === "skip") {
4558
5244
  this.failedInvocations.add(call.invocationId);
5245
+ if (this.tree) {
5246
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5247
+ if (gadgetNode) {
5248
+ this.tree.skipGadget(gadgetNode.id, failedDep, depError, "dependency_failed");
5249
+ }
5250
+ }
4559
5251
  const skipEvent = {
4560
5252
  type: "gadget_skipped",
4561
5253
  gadgetName: call.gadgetName,
@@ -4575,7 +5267,7 @@ var init_stream_processor = __esm({
4575
5267
  failedDependencyError: depError,
4576
5268
  logger: this.logger
4577
5269
  };
4578
- await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(observeContext));
5270
+ await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
4579
5271
  }
4580
5272
  this.logger.info("Gadget skipped due to failed dependency", {
4581
5273
  gadgetName: call.gadgetName,
@@ -4807,48 +5499,6 @@ var init_stream_processor = __esm({
4807
5499
  observers.map((observer) => this.safeObserve(observer))
4808
5500
  );
4809
5501
  }
4810
- /**
4811
- * Check if execution can recover from an error.
4812
- *
4813
- * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4814
- *
4815
- * Logic:
4816
- * - If custom canRecoverFromGadgetError is provided, use it
4817
- * - Otherwise, use stopOnGadgetError config:
4818
- * - stopOnGadgetError=true → return false (stop execution)
4819
- * - stopOnGadgetError=false → return true (continue execution)
4820
- */
4821
- async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4822
- if (this.canRecoverFromGadgetError) {
4823
- return await this.canRecoverFromGadgetError({
4824
- error,
4825
- gadgetName,
4826
- errorType,
4827
- parameters
4828
- });
4829
- }
4830
- const shouldContinue = !this.stopOnGadgetError;
4831
- this.logger.debug("Checking if should continue after error", {
4832
- error,
4833
- gadgetName,
4834
- errorType,
4835
- stopOnGadgetError: this.stopOnGadgetError,
4836
- shouldContinue
4837
- });
4838
- return shouldContinue;
4839
- }
4840
- /**
4841
- * Determine the type of error from a gadget execution.
4842
- */
4843
- determineErrorType(call, result) {
4844
- if (call.parseError) {
4845
- return "parse";
4846
- }
4847
- if (result.error?.includes("Invalid parameters:")) {
4848
- return "validation";
4849
- }
4850
- return "execution";
4851
- }
4852
5502
  };
4853
5503
  }
4854
5504
  });
@@ -4859,6 +5509,7 @@ var init_agent = __esm({
4859
5509
  "src/agent/agent.ts"() {
4860
5510
  "use strict";
4861
5511
  init_constants();
5512
+ init_execution_tree();
4862
5513
  init_messages();
4863
5514
  init_model_shortcuts();
4864
5515
  init_media_store();
@@ -4886,8 +5537,6 @@ var init_agent = __esm({
4886
5537
  requestHumanInput;
4887
5538
  textOnlyHandler;
4888
5539
  textWithGadgetsHandler;
4889
- stopOnGadgetError;
4890
- canRecoverFromGadgetError;
4891
5540
  defaultGadgetTimeoutMs;
4892
5541
  defaultMaxTokens;
4893
5542
  hasUserPrompt;
@@ -4910,6 +5559,12 @@ var init_agent = __esm({
4910
5559
  pendingSubagentEvents = [];
4911
5560
  // Combined callback that queues events AND calls user callback
4912
5561
  onSubagentEvent;
5562
+ // Counter for generating synthetic invocation IDs for wrapped text content
5563
+ syntheticInvocationCounter = 0;
5564
+ // Execution Tree - first-class model for nested subagent support
5565
+ tree;
5566
+ parentNodeId;
5567
+ baseDepth;
4913
5568
  /**
4914
5569
  * Creates a new Agent instance.
4915
5570
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4932,8 +5587,6 @@ var init_agent = __esm({
4932
5587
  this.requestHumanInput = options.requestHumanInput;
4933
5588
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4934
5589
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4935
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4936
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4937
5590
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4938
5591
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4939
5592
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4987,6 +5640,9 @@ var init_agent = __esm({
4987
5640
  temperature: this.temperature
4988
5641
  };
4989
5642
  this.subagentConfig = options.subagentConfig;
5643
+ this.tree = options.parentTree ?? new ExecutionTree();
5644
+ this.parentNodeId = options.parentNodeId ?? null;
5645
+ this.baseDepth = options.baseDepth ?? 0;
4990
5646
  this.userSubagentEventCallback = options.onSubagentEvent;
4991
5647
  this.onSubagentEvent = (event) => {
4992
5648
  this.pendingSubagentEvents.push(event);
@@ -5051,7 +5707,9 @@ var init_agent = __esm({
5051
5707
  *flushPendingSubagentEvents() {
5052
5708
  while (this.pendingSubagentEvents.length > 0) {
5053
5709
  const event = this.pendingSubagentEvents.shift();
5054
- yield { type: "subagent_event", subagentEvent: event };
5710
+ if (event) {
5711
+ yield { type: "subagent_event", subagentEvent: event };
5712
+ }
5055
5713
  }
5056
5714
  }
5057
5715
  /**
@@ -5105,6 +5763,48 @@ var init_agent = __esm({
5105
5763
  getMediaStore() {
5106
5764
  return this.mediaStore;
5107
5765
  }
5766
+ /**
5767
+ * Get the execution tree for this agent.
5768
+ *
5769
+ * The execution tree provides a first-class model of all LLM calls and gadget executions,
5770
+ * including nested subagent activity. Use this to:
5771
+ * - Query execution state: `tree.getNode(id)`
5772
+ * - Get total cost: `tree.getTotalCost()`
5773
+ * - Get subtree cost/media/tokens: `tree.getSubtreeCost(nodeId)`
5774
+ * - Subscribe to events: `tree.on("llm_call_complete", handler)`
5775
+ * - Stream all events: `for await (const event of tree.events())`
5776
+ *
5777
+ * For subagents (created with `withParentContext`), the tree is shared with the parent,
5778
+ * enabling unified tracking and real-time visibility across all nesting levels.
5779
+ *
5780
+ * @returns The ExecutionTree instance
5781
+ *
5782
+ * @example
5783
+ * ```typescript
5784
+ * const agent = LLMist.createAgent()
5785
+ * .withModel("sonnet")
5786
+ * .withGadgets(BrowseWeb)
5787
+ * .ask("Research topic X");
5788
+ *
5789
+ * for await (const event of agent.run()) {
5790
+ * // Process events...
5791
+ * }
5792
+ *
5793
+ * // After execution, query the tree
5794
+ * const tree = agent.getTree();
5795
+ * console.log(`Total cost: $${tree.getTotalCost().toFixed(4)}`);
5796
+ *
5797
+ * // Inspect all LLM calls
5798
+ * for (const node of tree.getAllNodes()) {
5799
+ * if (node.type === "llm_call") {
5800
+ * console.log(`LLM #${node.iteration}: ${node.model}`);
5801
+ * }
5802
+ * }
5803
+ * ```
5804
+ */
5805
+ getTree() {
5806
+ return this.tree;
5807
+ }
5108
5808
  /**
5109
5809
  * Manually trigger context compaction.
5110
5810
  *
@@ -5206,6 +5906,7 @@ var init_agent = __esm({
5206
5906
  await this.hooks.observers.onCompaction({
5207
5907
  iteration: currentIteration,
5208
5908
  event: compactionEvent,
5909
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5209
5910
  stats: this.compactionManager.getStats(),
5210
5911
  logger: this.logger
5211
5912
  });
@@ -5267,6 +5968,13 @@ var init_agent = __esm({
5267
5968
  messageCount: llmOptions.messages.length,
5268
5969
  messages: llmOptions.messages
5269
5970
  });
5971
+ const llmNode = this.tree.addLLMCall({
5972
+ iteration: currentIteration,
5973
+ model: llmOptions.model,
5974
+ parentId: this.parentNodeId,
5975
+ request: llmOptions.messages
5976
+ });
5977
+ const currentLLMNodeId = llmNode.id;
5270
5978
  const stream2 = this.client.stream(llmOptions);
5271
5979
  const processor = new StreamProcessor({
5272
5980
  iteration: currentIteration,
@@ -5277,14 +5985,17 @@ var init_agent = __esm({
5277
5985
  hooks: this.hooks,
5278
5986
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
5279
5987
  requestHumanInput: this.requestHumanInput,
5280
- stopOnGadgetError: this.stopOnGadgetError,
5281
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5282
5988
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5283
5989
  client: this.client,
5284
5990
  mediaStore: this.mediaStore,
5285
5991
  agentConfig: this.agentContextConfig,
5286
5992
  subagentConfig: this.subagentConfig,
5287
- onSubagentEvent: this.onSubagentEvent
5993
+ onSubagentEvent: this.onSubagentEvent,
5994
+ // Tree context for execution tracking
5995
+ tree: this.tree,
5996
+ parentNodeId: currentLLMNodeId,
5997
+ // Gadgets are children of this LLM call
5998
+ baseDepth: this.baseDepth
5288
5999
  });
5289
6000
  let streamMetadata = null;
5290
6001
  let gadgetCallCount = 0;
@@ -5330,6 +6041,11 @@ var init_agent = __esm({
5330
6041
  await this.hooks.observers.onLLMCallComplete(context);
5331
6042
  }
5332
6043
  });
6044
+ this.tree.completeLLMCall(currentLLMNodeId, {
6045
+ response: result.rawResponse,
6046
+ usage: result.usage,
6047
+ finishReason: result.finishReason
6048
+ });
5333
6049
  let finalMessage = result.finalMessage;
5334
6050
  if (this.hooks.controllers?.afterLLMCall) {
5335
6051
  const context = {
@@ -5364,10 +6080,12 @@ var init_agent = __esm({
5364
6080
  const textContent = textOutputs.join("");
5365
6081
  if (textContent.trim()) {
5366
6082
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6083
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
5367
6084
  this.conversation.addGadgetCallResult(
5368
6085
  gadgetName,
5369
6086
  parameterMapping(textContent),
5370
- resultMapping ? resultMapping(textContent) : textContent
6087
+ resultMapping ? resultMapping(textContent) : textContent,
6088
+ syntheticId
5371
6089
  );
5372
6090
  }
5373
6091
  }
@@ -5378,6 +6096,7 @@ var init_agent = __esm({
5378
6096
  gadgetResult.gadgetName,
5379
6097
  gadgetResult.parameters,
5380
6098
  gadgetResult.error ?? gadgetResult.result ?? "",
6099
+ gadgetResult.invocationId,
5381
6100
  gadgetResult.media,
5382
6101
  gadgetResult.mediaIds
5383
6102
  );
@@ -5385,10 +6104,12 @@ var init_agent = __esm({
5385
6104
  }
5386
6105
  } else {
5387
6106
  if (finalMessage.trim()) {
6107
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
5388
6108
  this.conversation.addGadgetCallResult(
5389
6109
  "TellUser",
5390
6110
  { message: finalMessage, done: false, type: "info" },
5391
- `\u2139\uFE0F ${finalMessage}`
6111
+ `\u2139\uFE0F ${finalMessage}`,
6112
+ syntheticId
5392
6113
  );
5393
6114
  }
5394
6115
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
@@ -5599,8 +6320,6 @@ var init_builder = __esm({
5599
6320
  gadgetArgPrefix;
5600
6321
  textOnlyHandler;
5601
6322
  textWithGadgetsHandler;
5602
- stopOnGadgetError;
5603
- canRecoverFromGadgetError;
5604
6323
  defaultGadgetTimeoutMs;
5605
6324
  gadgetOutputLimit;
5606
6325
  gadgetOutputLimitPercent;
@@ -5609,6 +6328,8 @@ var init_builder = __esm({
5609
6328
  trailingMessage;
5610
6329
  subagentConfig;
5611
6330
  subagentEventCallback;
6331
+ // Tree context for subagent support - enables shared tree model
6332
+ // When a gadget calls withParentContext(ctx), it shares the parent's tree
5612
6333
  parentContext;
5613
6334
  constructor(client) {
5614
6335
  this.client = client;
@@ -5891,62 +6612,6 @@ var init_builder = __esm({
5891
6612
  this.textWithGadgetsHandler = handler;
5892
6613
  return this;
5893
6614
  }
5894
- /**
5895
- * Set whether to stop gadget execution on first error.
5896
- *
5897
- * When true (default), if a gadget fails:
5898
- * - Subsequent gadgets in the same response are skipped
5899
- * - LLM stream is cancelled to save costs
5900
- * - Agent loop continues with error in context
5901
- *
5902
- * When false:
5903
- * - All gadgets in the response still execute
5904
- * - LLM stream continues to completion
5905
- *
5906
- * @param stop - Whether to stop on gadget error
5907
- * @returns This builder for chaining
5908
- *
5909
- * @example
5910
- * ```typescript
5911
- * .withStopOnGadgetError(false)
5912
- * ```
5913
- */
5914
- withStopOnGadgetError(stop) {
5915
- this.stopOnGadgetError = stop;
5916
- return this;
5917
- }
5918
- /**
5919
- * Set custom error handling logic.
5920
- *
5921
- * Provides fine-grained control over whether to continue after different types of errors.
5922
- * Overrides `stopOnGadgetError` when provided.
5923
- *
5924
- * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5925
- * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5926
- * but maps to the `canRecoverFromGadgetError` property internally.
5927
- *
5928
- * @param handler - Function that decides whether to continue after an error.
5929
- * Return `true` to continue execution, `false` to stop.
5930
- * @returns This builder for chaining
5931
- *
5932
- * @example
5933
- * ```typescript
5934
- * .withErrorHandler((context) => {
5935
- * // Stop on parse errors, continue on validation/execution errors
5936
- * if (context.errorType === "parse") {
5937
- * return false;
5938
- * }
5939
- * if (context.error.includes("CRITICAL")) {
5940
- * return false;
5941
- * }
5942
- * return true;
5943
- * })
5944
- * ```
5945
- */
5946
- withErrorHandler(handler) {
5947
- this.canRecoverFromGadgetError = handler;
5948
- return this;
5949
- }
5950
6615
  /**
5951
6616
  * Set default timeout for gadget execution.
5952
6617
  *
@@ -6141,6 +6806,15 @@ var init_builder = __esm({
6141
6806
  * The method extracts `invocationId` and `onSubagentEvent` from the execution
6142
6807
  * context and sets up automatic forwarding via hooks and event wrapping.
6143
6808
  *
6809
+ * **NEW: Shared Tree Model** - When the parent provides an ExecutionTree via context,
6810
+ * the subagent shares that tree instead of creating its own. This enables:
6811
+ * - Unified cost tracking across all nesting levels
6812
+ * - Automatic media aggregation via `tree.getSubtreeMedia(nodeId)`
6813
+ * - Real-time visibility of nested execution in the parent
6814
+ *
6815
+ * **Signal Forwarding** - When parent context includes a signal, it's automatically
6816
+ * forwarded to the subagent for proper cancellation propagation.
6817
+ *
6144
6818
  * @param ctx - ExecutionContext passed to the gadget's execute() method
6145
6819
  * @param depth - Nesting depth (default: 1 for direct child)
6146
6820
  * @returns This builder for chaining
@@ -6161,17 +6835,25 @@ var init_builder = __esm({
6161
6835
  * result = event.content;
6162
6836
  * }
6163
6837
  * }
6838
+ *
6839
+ * // After subagent completes, costs are automatically aggregated
6840
+ * // No manual tracking needed - use tree methods:
6841
+ * const totalCost = ctx.tree?.getSubtreeCost(ctx.nodeId!);
6842
+ * const allMedia = ctx.tree?.getSubtreeMedia(ctx.nodeId!);
6164
6843
  * }
6165
6844
  * ```
6166
6845
  */
6167
6846
  withParentContext(ctx, depth = 1) {
6168
- if (ctx.onSubagentEvent && ctx.invocationId) {
6847
+ if (ctx.tree) {
6169
6848
  this.parentContext = {
6170
- invocationId: ctx.invocationId,
6171
- onSubagentEvent: ctx.onSubagentEvent,
6849
+ tree: ctx.tree,
6850
+ nodeId: ctx.nodeId,
6172
6851
  depth
6173
6852
  };
6174
6853
  }
6854
+ if (ctx.signal && !this.signal) {
6855
+ this.signal = ctx.signal;
6856
+ }
6175
6857
  return this;
6176
6858
  }
6177
6859
  /**
@@ -6204,11 +6886,13 @@ var init_builder = __esm({
6204
6886
  *
6205
6887
  * This is useful for in-context learning - showing the LLM what "past self"
6206
6888
  * did correctly so it mimics the pattern. The call is formatted with proper
6207
- * markers and parameter format.
6889
+ * markers and parameter format, including the invocation ID so the LLM can
6890
+ * reference previous calls when building dependencies.
6208
6891
  *
6209
6892
  * @param gadgetName - Name of the gadget
6210
6893
  * @param parameters - Parameters passed to the gadget
6211
6894
  * @param result - Result returned by the gadget
6895
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
6212
6896
  * @returns This builder for chaining
6213
6897
  *
6214
6898
  * @example
@@ -6220,124 +6904,36 @@ var init_builder = __esm({
6220
6904
  * done: false,
6221
6905
  * type: 'info'
6222
6906
  * },
6223
- * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands'
6907
+ * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands',
6908
+ * 'gc_1'
6224
6909
  * )
6225
6910
  * ```
6226
6911
  */
6227
- withSyntheticGadgetCall(gadgetName, parameters, result) {
6912
+ withSyntheticGadgetCall(gadgetName, parameters, result, invocationId) {
6228
6913
  const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
6229
6914
  const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
6230
6915
  const paramStr = this.formatBlockParameters(parameters, "");
6231
6916
  this.initialMessages.push({
6232
6917
  role: "assistant",
6233
- content: `${startPrefix}${gadgetName}
6918
+ content: `${startPrefix}${gadgetName}:${invocationId}
6234
6919
  ${paramStr}
6235
6920
  ${endPrefix}`
6236
6921
  });
6237
6922
  this.initialMessages.push({
6238
6923
  role: "user",
6239
- content: `Result: ${result}`
6924
+ content: `Result (${invocationId}): ${result}`
6240
6925
  });
6241
6926
  return this;
6242
6927
  }
6243
6928
  /**
6244
- * Compose the final hooks, including:
6245
- * - Trailing message injection (if configured)
6246
- * - Subagent event forwarding for LLM calls (if parentContext is set)
6929
+ * Compose the final hooks, including trailing message injection if configured.
6930
+ *
6931
+ * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
6932
+ * When a subagent uses withParentContext(ctx), it shares the parent's tree,
6933
+ * and all events are automatically visible to tree subscribers (like the TUI).
6247
6934
  */
6248
6935
  composeHooks() {
6249
- let hooks = this.hooks;
6250
- if (this.parentContext) {
6251
- const { invocationId, onSubagentEvent, depth } = this.parentContext;
6252
- const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6253
- const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6254
- const existingOnGadgetExecutionStart = hooks?.observers?.onGadgetExecutionStart;
6255
- const existingOnGadgetExecutionComplete = hooks?.observers?.onGadgetExecutionComplete;
6256
- hooks = {
6257
- ...hooks,
6258
- observers: {
6259
- ...hooks?.observers,
6260
- onLLMCallStart: async (context) => {
6261
- let inputTokens;
6262
- try {
6263
- if (this.client) {
6264
- inputTokens = await this.client.countTokens(
6265
- context.options.model,
6266
- context.options.messages
6267
- );
6268
- }
6269
- } catch {
6270
- }
6271
- onSubagentEvent({
6272
- type: "llm_call_start",
6273
- gadgetInvocationId: invocationId,
6274
- depth,
6275
- event: {
6276
- iteration: context.iteration,
6277
- model: context.options.model,
6278
- inputTokens
6279
- }
6280
- });
6281
- if (existingOnLLMCallStart) {
6282
- await existingOnLLMCallStart(context);
6283
- }
6284
- },
6285
- onLLMCallComplete: async (context) => {
6286
- onSubagentEvent({
6287
- type: "llm_call_end",
6288
- gadgetInvocationId: invocationId,
6289
- depth,
6290
- event: {
6291
- iteration: context.iteration,
6292
- model: context.options.model,
6293
- // Backward compat fields
6294
- inputTokens: context.usage?.inputTokens,
6295
- outputTokens: context.usage?.outputTokens,
6296
- finishReason: context.finishReason ?? void 0,
6297
- // Full usage object with cache details (for first-class display)
6298
- usage: context.usage
6299
- // Cost will be calculated by parent if it has model registry
6300
- }
6301
- });
6302
- if (existingOnLLMCallComplete) {
6303
- await existingOnLLMCallComplete(context);
6304
- }
6305
- },
6306
- onGadgetExecutionStart: async (context) => {
6307
- onSubagentEvent({
6308
- type: "gadget_call",
6309
- gadgetInvocationId: invocationId,
6310
- depth,
6311
- event: {
6312
- call: {
6313
- invocationId: context.invocationId,
6314
- gadgetName: context.gadgetName,
6315
- parameters: context.parameters
6316
- }
6317
- }
6318
- });
6319
- if (existingOnGadgetExecutionStart) {
6320
- await existingOnGadgetExecutionStart(context);
6321
- }
6322
- },
6323
- onGadgetExecutionComplete: async (context) => {
6324
- onSubagentEvent({
6325
- type: "gadget_result",
6326
- gadgetInvocationId: invocationId,
6327
- depth,
6328
- event: {
6329
- result: {
6330
- invocationId: context.invocationId
6331
- }
6332
- }
6333
- });
6334
- if (existingOnGadgetExecutionComplete) {
6335
- await existingOnGadgetExecutionComplete(context);
6336
- }
6337
- }
6338
- }
6339
- };
6340
- }
6936
+ const hooks = this.hooks;
6341
6937
  if (!this.trailingMessage) {
6342
6938
  return hooks;
6343
6939
  }
@@ -6420,19 +7016,6 @@ ${endPrefix}`
6420
7016
  this.client = new LLMistClass();
6421
7017
  }
6422
7018
  const registry = GadgetRegistry.from(this.gadgets);
6423
- let onSubagentEvent = this.subagentEventCallback;
6424
- if (this.parentContext) {
6425
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6426
- const existingCallback = this.subagentEventCallback;
6427
- onSubagentEvent = (event) => {
6428
- parentCallback({
6429
- ...event,
6430
- gadgetInvocationId: invocationId,
6431
- depth: event.depth + depth
6432
- });
6433
- existingCallback?.(event);
6434
- };
6435
- }
6436
7019
  return {
6437
7020
  client: this.client,
6438
7021
  model: this.model ?? "openai:gpt-5-nano",
@@ -6451,15 +7034,17 @@ ${endPrefix}`
6451
7034
  gadgetArgPrefix: this.gadgetArgPrefix,
6452
7035
  textOnlyHandler: this.textOnlyHandler,
6453
7036
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6454
- stopOnGadgetError: this.stopOnGadgetError,
6455
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6456
7037
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6457
7038
  gadgetOutputLimit: this.gadgetOutputLimit,
6458
7039
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6459
7040
  compactionConfig: this.compactionConfig,
6460
7041
  signal: this.signal,
6461
7042
  subagentConfig: this.subagentConfig,
6462
- onSubagentEvent
7043
+ onSubagentEvent: this.subagentEventCallback,
7044
+ // Tree context for shared tree model (subagents share parent's tree)
7045
+ parentTree: this.parentContext?.tree,
7046
+ parentNodeId: this.parentContext?.nodeId,
7047
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6463
7048
  };
6464
7049
  }
6465
7050
  ask(userPrompt) {
@@ -6616,19 +7201,6 @@ ${endPrefix}`
6616
7201
  this.client = new LLMistClass();
6617
7202
  }
6618
7203
  const registry = GadgetRegistry.from(this.gadgets);
6619
- let onSubagentEvent = this.subagentEventCallback;
6620
- if (this.parentContext) {
6621
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6622
- const existingCallback = this.subagentEventCallback;
6623
- onSubagentEvent = (event) => {
6624
- parentCallback({
6625
- ...event,
6626
- gadgetInvocationId: invocationId,
6627
- depth: event.depth + depth
6628
- });
6629
- existingCallback?.(event);
6630
- };
6631
- }
6632
7204
  const options = {
6633
7205
  client: this.client,
6634
7206
  model: this.model ?? "openai:gpt-5-nano",
@@ -6647,15 +7219,17 @@ ${endPrefix}`
6647
7219
  gadgetArgPrefix: this.gadgetArgPrefix,
6648
7220
  textOnlyHandler: this.textOnlyHandler,
6649
7221
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6650
- stopOnGadgetError: this.stopOnGadgetError,
6651
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6652
7222
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6653
7223
  gadgetOutputLimit: this.gadgetOutputLimit,
6654
7224
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6655
7225
  compactionConfig: this.compactionConfig,
6656
7226
  signal: this.signal,
6657
7227
  subagentConfig: this.subagentConfig,
6658
- onSubagentEvent
7228
+ onSubagentEvent: this.subagentEventCallback,
7229
+ // Tree context for shared tree model (subagents share parent's tree)
7230
+ parentTree: this.parentContext?.tree,
7231
+ parentNodeId: this.parentContext?.nodeId,
7232
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6659
7233
  };
6660
7234
  return new Agent(AGENT_INTERNAL_KEY, options);
6661
7235
  }
@@ -11243,16 +11817,16 @@ var MockConversationManager = class {
11243
11817
  this.history.push(msg);
11244
11818
  this.addedMessages.push(msg);
11245
11819
  }
11246
- addGadgetCallResult(gadgetName, parameters, result) {
11820
+ addGadgetCallResult(gadgetName, parameters, result, invocationId) {
11247
11821
  const assistantMsg = {
11248
11822
  role: "assistant",
11249
- content: `!!!GADGET_START:${gadgetName}
11823
+ content: `!!!GADGET_START:${gadgetName}:${invocationId}
11250
11824
  ${JSON.stringify(parameters)}
11251
11825
  !!!GADGET_END`
11252
11826
  };
11253
11827
  const resultMsg = {
11254
11828
  role: "user",
11255
- content: `Result: ${result}`
11829
+ content: `Result (${invocationId}): ${result}`
11256
11830
  };
11257
11831
  this.history.push(assistantMsg);
11258
11832
  this.history.push(resultMsg);
@@ -11600,6 +12174,8 @@ export {
11600
12174
  init_schema_validator,
11601
12175
  GadgetRegistry,
11602
12176
  init_registry,
12177
+ ExecutionTree,
12178
+ init_execution_tree,
11603
12179
  DEFAULT_HINTS,
11604
12180
  DEFAULT_PROMPTS,
11605
12181
  resolvePromptTemplate,
@@ -11718,4 +12294,4 @@ export {
11718
12294
  createEmptyStream,
11719
12295
  createErrorStream
11720
12296
  };
11721
- //# sourceMappingURL=chunk-YJKUWFIC.js.map
12297
+ //# sourceMappingURL=chunk-VAJLPRJ6.js.map