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.
@@ -671,23 +671,26 @@ Produces: { "items": ["first", "second"] }`);
671
671
  * Record a gadget execution result in the message history.
672
672
  * Creates an assistant message with the gadget invocation and a user message with the result.
673
673
  *
674
+ * The invocationId is shown to the LLM so it can reference previous calls when building dependencies.
675
+ *
674
676
  * @param gadget - Name of the gadget that was executed
675
677
  * @param parameters - Parameters that were passed to the gadget
676
678
  * @param result - Text result from the gadget execution
679
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
677
680
  * @param media - Optional media outputs from the gadget
678
681
  * @param mediaIds - Optional IDs for the media outputs
679
682
  */
680
- addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
683
+ addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds) {
681
684
  const paramStr = this.formatBlockParameters(parameters, "");
682
685
  this.messages.push({
683
686
  role: "assistant",
684
- content: `${this.startPrefix}${gadget}
687
+ content: `${this.startPrefix}${gadget}:${invocationId}
685
688
  ${paramStr}
686
689
  ${this.endPrefix}`
687
690
  });
688
691
  if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
689
692
  const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
690
- const textWithIds = `Result: ${result}
693
+ const textWithIds = `Result (${invocationId}): ${result}
691
694
  ${idRefs}`;
692
695
  const parts = [text(textWithIds)];
693
696
  for (const item of media) {
@@ -701,7 +704,7 @@ ${idRefs}`;
701
704
  } else {
702
705
  this.messages.push({
703
706
  role: "user",
704
- content: `Result: ${result}`
707
+ content: `Result (${invocationId}): ${result}`
705
708
  });
706
709
  }
707
710
  return this;
@@ -1037,6 +1040,611 @@ var init_registry = __esm({
1037
1040
  }
1038
1041
  });
1039
1042
 
1043
+ // src/core/execution-tree.ts
1044
+ var ExecutionTree;
1045
+ var init_execution_tree = __esm({
1046
+ "src/core/execution-tree.ts"() {
1047
+ "use strict";
1048
+ ExecutionTree = class {
1049
+ nodes = /* @__PURE__ */ new Map();
1050
+ rootIds = [];
1051
+ eventListeners = /* @__PURE__ */ new Map();
1052
+ eventIdCounter = 0;
1053
+ invocationIdToNodeId = /* @__PURE__ */ new Map();
1054
+ // For async event streaming
1055
+ eventQueue = [];
1056
+ eventWaiters = [];
1057
+ isCompleted = false;
1058
+ /**
1059
+ * Base depth for all nodes in this tree.
1060
+ * Used when this tree is a subagent's view into a parent tree.
1061
+ */
1062
+ baseDepth;
1063
+ /**
1064
+ * Parent node ID for subagent trees.
1065
+ * All root nodes in this tree will have this as their parentId.
1066
+ */
1067
+ parentNodeId;
1068
+ constructor(options) {
1069
+ this.baseDepth = options?.baseDepth ?? 0;
1070
+ this.parentNodeId = options?.parentNodeId ?? null;
1071
+ }
1072
+ // ===========================================================================
1073
+ // Node ID Generation
1074
+ // ===========================================================================
1075
+ generateLLMCallId(iteration, parentId) {
1076
+ if (parentId) {
1077
+ return `llm_${parentId}_${iteration}`;
1078
+ }
1079
+ return `llm_${iteration}`;
1080
+ }
1081
+ gadgetIdCounter = 0;
1082
+ generateGadgetId(invocationId) {
1083
+ return `gadget_${invocationId}_${++this.gadgetIdCounter}`;
1084
+ }
1085
+ // ===========================================================================
1086
+ // Event Emission
1087
+ // ===========================================================================
1088
+ emit(event) {
1089
+ const listeners = this.eventListeners.get(event.type);
1090
+ if (listeners) {
1091
+ for (const listener of listeners) {
1092
+ try {
1093
+ listener(event);
1094
+ } catch (error) {
1095
+ console.error(`Error in event listener for ${event.type}:`, error);
1096
+ }
1097
+ }
1098
+ }
1099
+ const allListeners = this.eventListeners.get("*");
1100
+ if (allListeners) {
1101
+ for (const listener of allListeners) {
1102
+ try {
1103
+ listener(event);
1104
+ } catch (error) {
1105
+ console.error("Error in wildcard event listener:", error);
1106
+ }
1107
+ }
1108
+ }
1109
+ if (this.eventWaiters.length > 0) {
1110
+ const waiter = this.eventWaiters.shift();
1111
+ if (waiter) waiter(event);
1112
+ } else {
1113
+ this.eventQueue.push(event);
1114
+ }
1115
+ }
1116
+ createBaseEventProps(node) {
1117
+ return {
1118
+ eventId: ++this.eventIdCounter,
1119
+ timestamp: Date.now(),
1120
+ nodeId: node.id,
1121
+ parentId: node.parentId,
1122
+ depth: node.depth,
1123
+ path: node.path
1124
+ };
1125
+ }
1126
+ // ===========================================================================
1127
+ // Node Creation
1128
+ // ===========================================================================
1129
+ /**
1130
+ * Add a new LLM call node to the tree.
1131
+ */
1132
+ addLLMCall(params) {
1133
+ const parentId = params.parentId ?? this.parentNodeId;
1134
+ const parent = parentId ? this.nodes.get(parentId) : null;
1135
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
1136
+ const path = parent ? [...parent.path] : [];
1137
+ const id = this.generateLLMCallId(params.iteration, parentId);
1138
+ path.push(id);
1139
+ const node = {
1140
+ id,
1141
+ type: "llm_call",
1142
+ parentId,
1143
+ depth,
1144
+ path,
1145
+ createdAt: Date.now(),
1146
+ completedAt: null,
1147
+ iteration: params.iteration,
1148
+ model: params.model,
1149
+ request: params.request,
1150
+ response: "",
1151
+ children: []
1152
+ };
1153
+ this.nodes.set(id, node);
1154
+ if (!parentId) {
1155
+ this.rootIds.push(id);
1156
+ } else if (parent) {
1157
+ parent.children.push(id);
1158
+ }
1159
+ this.emit({
1160
+ type: "llm_call_start",
1161
+ ...this.createBaseEventProps(node),
1162
+ iteration: node.iteration,
1163
+ model: node.model,
1164
+ request: node.request
1165
+ });
1166
+ return node;
1167
+ }
1168
+ /**
1169
+ * Add text to an LLM call's response (for streaming).
1170
+ */
1171
+ appendLLMResponse(nodeId, chunk) {
1172
+ const node = this.nodes.get(nodeId);
1173
+ if (!node || node.type !== "llm_call") {
1174
+ throw new Error(`LLM call node not found: ${nodeId}`);
1175
+ }
1176
+ node.response += chunk;
1177
+ this.emit({
1178
+ type: "llm_call_stream",
1179
+ ...this.createBaseEventProps(node),
1180
+ chunk
1181
+ });
1182
+ }
1183
+ /**
1184
+ * Complete an LLM call node.
1185
+ */
1186
+ completeLLMCall(nodeId, params) {
1187
+ const node = this.nodes.get(nodeId);
1188
+ if (!node || node.type !== "llm_call") {
1189
+ throw new Error(`LLM call node not found: ${nodeId}`);
1190
+ }
1191
+ const llmNode = node;
1192
+ llmNode.completedAt = Date.now();
1193
+ if (params.response !== void 0) llmNode.response = params.response;
1194
+ if (params.usage) llmNode.usage = params.usage;
1195
+ if (params.finishReason !== void 0) llmNode.finishReason = params.finishReason;
1196
+ if (params.cost !== void 0) llmNode.cost = params.cost;
1197
+ this.emit({
1198
+ type: "llm_call_complete",
1199
+ ...this.createBaseEventProps(node),
1200
+ response: llmNode.response,
1201
+ usage: llmNode.usage,
1202
+ finishReason: llmNode.finishReason,
1203
+ cost: llmNode.cost
1204
+ });
1205
+ }
1206
+ /**
1207
+ * Mark an LLM call as failed.
1208
+ */
1209
+ failLLMCall(nodeId, error, recovered) {
1210
+ const node = this.nodes.get(nodeId);
1211
+ if (!node || node.type !== "llm_call") {
1212
+ throw new Error(`LLM call node not found: ${nodeId}`);
1213
+ }
1214
+ const llmNode = node;
1215
+ llmNode.completedAt = Date.now();
1216
+ this.emit({
1217
+ type: "llm_call_error",
1218
+ ...this.createBaseEventProps(node),
1219
+ error,
1220
+ recovered
1221
+ });
1222
+ }
1223
+ /**
1224
+ * Add a new gadget node to the tree.
1225
+ */
1226
+ addGadget(params) {
1227
+ const parentId = params.parentId ?? this.getCurrentLLMCallId() ?? this.parentNodeId;
1228
+ const parent = parentId ? this.nodes.get(parentId) : null;
1229
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
1230
+ const path = parent ? [...parent.path] : [];
1231
+ const id = this.generateGadgetId(params.invocationId);
1232
+ path.push(id);
1233
+ const node = {
1234
+ id,
1235
+ type: "gadget",
1236
+ parentId,
1237
+ depth,
1238
+ path,
1239
+ createdAt: Date.now(),
1240
+ completedAt: null,
1241
+ invocationId: params.invocationId,
1242
+ name: params.name,
1243
+ parameters: params.parameters,
1244
+ dependencies: params.dependencies ?? [],
1245
+ state: "pending",
1246
+ children: [],
1247
+ isSubagent: false
1248
+ };
1249
+ this.nodes.set(id, node);
1250
+ this.invocationIdToNodeId.set(params.invocationId, id);
1251
+ if (parent) {
1252
+ parent.children.push(id);
1253
+ }
1254
+ this.emit({
1255
+ type: "gadget_call",
1256
+ ...this.createBaseEventProps(node),
1257
+ invocationId: node.invocationId,
1258
+ name: node.name,
1259
+ parameters: node.parameters,
1260
+ dependencies: node.dependencies
1261
+ });
1262
+ return node;
1263
+ }
1264
+ /**
1265
+ * Mark a gadget as started (running).
1266
+ */
1267
+ startGadget(nodeId) {
1268
+ const node = this.nodes.get(nodeId);
1269
+ if (!node || node.type !== "gadget") {
1270
+ throw new Error(`Gadget node not found: ${nodeId}`);
1271
+ }
1272
+ const gadgetNode = node;
1273
+ gadgetNode.state = "running";
1274
+ this.emit({
1275
+ type: "gadget_start",
1276
+ ...this.createBaseEventProps(node),
1277
+ invocationId: gadgetNode.invocationId,
1278
+ name: gadgetNode.name
1279
+ });
1280
+ }
1281
+ /**
1282
+ * Complete a gadget node successfully.
1283
+ */
1284
+ completeGadget(nodeId, params) {
1285
+ const node = this.nodes.get(nodeId);
1286
+ if (!node || node.type !== "gadget") {
1287
+ throw new Error(`Gadget node not found: ${nodeId}`);
1288
+ }
1289
+ const gadgetNode = node;
1290
+ gadgetNode.completedAt = Date.now();
1291
+ gadgetNode.state = params.error ? "failed" : "completed";
1292
+ if (params.result !== void 0) gadgetNode.result = params.result;
1293
+ if (params.error) gadgetNode.error = params.error;
1294
+ if (params.executionTimeMs !== void 0) gadgetNode.executionTimeMs = params.executionTimeMs;
1295
+ if (params.cost !== void 0) gadgetNode.cost = params.cost;
1296
+ if (params.media) gadgetNode.media = params.media;
1297
+ gadgetNode.isSubagent = gadgetNode.children.some((childId) => {
1298
+ const child = this.nodes.get(childId);
1299
+ return child?.type === "llm_call";
1300
+ });
1301
+ if (params.error) {
1302
+ this.emit({
1303
+ type: "gadget_error",
1304
+ ...this.createBaseEventProps(node),
1305
+ invocationId: gadgetNode.invocationId,
1306
+ name: gadgetNode.name,
1307
+ error: params.error,
1308
+ executionTimeMs: params.executionTimeMs ?? 0
1309
+ });
1310
+ } else {
1311
+ this.emit({
1312
+ type: "gadget_complete",
1313
+ ...this.createBaseEventProps(node),
1314
+ invocationId: gadgetNode.invocationId,
1315
+ name: gadgetNode.name,
1316
+ result: params.result ?? "",
1317
+ executionTimeMs: params.executionTimeMs ?? 0,
1318
+ cost: params.cost,
1319
+ media: params.media
1320
+ });
1321
+ }
1322
+ }
1323
+ /**
1324
+ * Mark a gadget as skipped due to dependency failure.
1325
+ */
1326
+ skipGadget(nodeId, failedDependency, failedDependencyError, reason) {
1327
+ const node = this.nodes.get(nodeId);
1328
+ if (!node || node.type !== "gadget") {
1329
+ throw new Error(`Gadget node not found: ${nodeId}`);
1330
+ }
1331
+ const gadgetNode = node;
1332
+ gadgetNode.completedAt = Date.now();
1333
+ gadgetNode.state = "skipped";
1334
+ gadgetNode.failedDependency = failedDependency;
1335
+ gadgetNode.error = failedDependencyError;
1336
+ const error = reason === "controller_skip" ? "Skipped by controller" : `Dependency ${failedDependency} failed: ${failedDependencyError}`;
1337
+ this.emit({
1338
+ type: "gadget_skipped",
1339
+ ...this.createBaseEventProps(node),
1340
+ invocationId: gadgetNode.invocationId,
1341
+ name: gadgetNode.name,
1342
+ reason,
1343
+ error,
1344
+ failedDependency,
1345
+ failedDependencyError
1346
+ });
1347
+ }
1348
+ // ===========================================================================
1349
+ // Text Events (pure notifications, not tree nodes)
1350
+ // ===========================================================================
1351
+ /**
1352
+ * Emit a text event (notification only, not stored in tree).
1353
+ */
1354
+ emitText(content, llmCallNodeId) {
1355
+ const node = this.nodes.get(llmCallNodeId);
1356
+ if (!node) {
1357
+ throw new Error(`Node not found: ${llmCallNodeId}`);
1358
+ }
1359
+ this.emit({
1360
+ type: "text",
1361
+ ...this.createBaseEventProps(node),
1362
+ content
1363
+ });
1364
+ }
1365
+ // ===========================================================================
1366
+ // Query Methods
1367
+ // ===========================================================================
1368
+ /**
1369
+ * Get a node by ID.
1370
+ */
1371
+ getNode(id) {
1372
+ return this.nodes.get(id);
1373
+ }
1374
+ /**
1375
+ * Get a gadget node by invocation ID.
1376
+ */
1377
+ getNodeByInvocationId(invocationId) {
1378
+ const nodeId = this.invocationIdToNodeId.get(invocationId);
1379
+ if (!nodeId) return void 0;
1380
+ const node = this.nodes.get(nodeId);
1381
+ return node?.type === "gadget" ? node : void 0;
1382
+ }
1383
+ /**
1384
+ * Get all root nodes (depth 0 for this tree).
1385
+ */
1386
+ getRoots() {
1387
+ return this.rootIds.map((id) => this.nodes.get(id)).filter((node) => node !== void 0);
1388
+ }
1389
+ /**
1390
+ * Get children of a node.
1391
+ */
1392
+ getChildren(id) {
1393
+ const node = this.nodes.get(id);
1394
+ if (!node) return [];
1395
+ return node.children.map((childId) => this.nodes.get(childId)).filter((child) => child !== void 0);
1396
+ }
1397
+ /**
1398
+ * Get ancestors of a node (from root to parent).
1399
+ */
1400
+ getAncestors(id) {
1401
+ const node = this.nodes.get(id);
1402
+ if (!node) return [];
1403
+ const ancestors = [];
1404
+ let currentId = node.parentId;
1405
+ while (currentId) {
1406
+ const ancestor = this.nodes.get(currentId);
1407
+ if (ancestor) {
1408
+ ancestors.unshift(ancestor);
1409
+ currentId = ancestor.parentId;
1410
+ } else {
1411
+ break;
1412
+ }
1413
+ }
1414
+ return ancestors;
1415
+ }
1416
+ /**
1417
+ * Get all descendants of a node.
1418
+ */
1419
+ getDescendants(id, type) {
1420
+ const node = this.nodes.get(id);
1421
+ if (!node) return [];
1422
+ const descendants = [];
1423
+ const stack = [...node.children];
1424
+ while (stack.length > 0) {
1425
+ const childId = stack.pop();
1426
+ const child = this.nodes.get(childId);
1427
+ if (child) {
1428
+ if (!type || child.type === type) {
1429
+ descendants.push(child);
1430
+ }
1431
+ stack.push(...child.children);
1432
+ }
1433
+ }
1434
+ return descendants;
1435
+ }
1436
+ /**
1437
+ * Get the current (most recent incomplete) LLM call node.
1438
+ */
1439
+ getCurrentLLMCallId() {
1440
+ for (let i = this.rootIds.length - 1; i >= 0; i--) {
1441
+ const node = this.nodes.get(this.rootIds[i]);
1442
+ if (node?.type === "llm_call" && !node.completedAt) {
1443
+ return node.id;
1444
+ }
1445
+ }
1446
+ return void 0;
1447
+ }
1448
+ // ===========================================================================
1449
+ // Aggregation Methods (for subagent support)
1450
+ // ===========================================================================
1451
+ /**
1452
+ * Get total cost for entire tree.
1453
+ */
1454
+ getTotalCost() {
1455
+ let total = 0;
1456
+ for (const node of this.nodes.values()) {
1457
+ if (node.type === "llm_call" && node.cost) {
1458
+ total += node.cost;
1459
+ } else if (node.type === "gadget" && node.cost) {
1460
+ total += node.cost;
1461
+ }
1462
+ }
1463
+ return total;
1464
+ }
1465
+ /**
1466
+ * Get total cost for a subtree (node and all descendants).
1467
+ */
1468
+ getSubtreeCost(nodeId) {
1469
+ const node = this.nodes.get(nodeId);
1470
+ if (!node) return 0;
1471
+ let total = 0;
1472
+ if (node.type === "llm_call" && node.cost) {
1473
+ total += node.cost;
1474
+ } else if (node.type === "gadget" && node.cost) {
1475
+ total += node.cost;
1476
+ }
1477
+ for (const descendant of this.getDescendants(nodeId)) {
1478
+ if (descendant.type === "llm_call" && descendant.cost) {
1479
+ total += descendant.cost;
1480
+ } else if (descendant.type === "gadget" && descendant.cost) {
1481
+ total += descendant.cost;
1482
+ }
1483
+ }
1484
+ return total;
1485
+ }
1486
+ /**
1487
+ * Get token usage for entire tree.
1488
+ */
1489
+ getTotalTokens() {
1490
+ let input = 0;
1491
+ let output = 0;
1492
+ let cached = 0;
1493
+ for (const node of this.nodes.values()) {
1494
+ if (node.type === "llm_call") {
1495
+ const llmNode = node;
1496
+ if (llmNode.usage) {
1497
+ input += llmNode.usage.inputTokens;
1498
+ output += llmNode.usage.outputTokens;
1499
+ cached += llmNode.usage.cachedInputTokens ?? 0;
1500
+ }
1501
+ }
1502
+ }
1503
+ return { input, output, cached };
1504
+ }
1505
+ /**
1506
+ * Get token usage for a subtree.
1507
+ */
1508
+ getSubtreeTokens(nodeId) {
1509
+ const node = this.nodes.get(nodeId);
1510
+ if (!node) return { input: 0, output: 0, cached: 0 };
1511
+ let input = 0;
1512
+ let output = 0;
1513
+ let cached = 0;
1514
+ const nodesToProcess = [node, ...this.getDescendants(nodeId)];
1515
+ for (const n of nodesToProcess) {
1516
+ if (n.type === "llm_call") {
1517
+ const llmNode = n;
1518
+ if (llmNode.usage) {
1519
+ input += llmNode.usage.inputTokens;
1520
+ output += llmNode.usage.outputTokens;
1521
+ cached += llmNode.usage.cachedInputTokens ?? 0;
1522
+ }
1523
+ }
1524
+ }
1525
+ return { input, output, cached };
1526
+ }
1527
+ /**
1528
+ * Collect all media from a subtree.
1529
+ */
1530
+ getSubtreeMedia(nodeId) {
1531
+ const node = this.nodes.get(nodeId);
1532
+ if (!node) return [];
1533
+ const media = [];
1534
+ const nodesToProcess = node.type === "gadget" ? [node] : [];
1535
+ nodesToProcess.push(...this.getDescendants(nodeId, "gadget"));
1536
+ for (const n of nodesToProcess) {
1537
+ if (n.type === "gadget") {
1538
+ const gadgetNode = n;
1539
+ if (gadgetNode.media) {
1540
+ media.push(...gadgetNode.media);
1541
+ }
1542
+ }
1543
+ }
1544
+ return media;
1545
+ }
1546
+ /**
1547
+ * Check if a subtree is complete (all nodes finished).
1548
+ */
1549
+ isSubtreeComplete(nodeId) {
1550
+ const node = this.nodes.get(nodeId);
1551
+ if (!node) return true;
1552
+ if (!node.completedAt) return false;
1553
+ for (const descendant of this.getDescendants(nodeId)) {
1554
+ if (!descendant.completedAt) return false;
1555
+ }
1556
+ return true;
1557
+ }
1558
+ /**
1559
+ * Get node counts.
1560
+ */
1561
+ getNodeCount() {
1562
+ let llmCalls = 0;
1563
+ let gadgets = 0;
1564
+ for (const node of this.nodes.values()) {
1565
+ if (node.type === "llm_call") llmCalls++;
1566
+ else if (node.type === "gadget") gadgets++;
1567
+ }
1568
+ return { llmCalls, gadgets };
1569
+ }
1570
+ // ===========================================================================
1571
+ // Event Subscription
1572
+ // ===========================================================================
1573
+ /**
1574
+ * Subscribe to events of a specific type.
1575
+ * Returns unsubscribe function.
1576
+ *
1577
+ * @param type - Event type to subscribe to (use "*" for all events)
1578
+ * @param listener - Callback function that receives matching events
1579
+ * @returns Unsubscribe function
1580
+ *
1581
+ * @example
1582
+ * ```typescript
1583
+ * const unsubscribe = tree.on("gadget_complete", (event) => {
1584
+ * if (event.type === "gadget_complete") {
1585
+ * console.log(`Gadget ${event.name} completed`);
1586
+ * }
1587
+ * });
1588
+ * ```
1589
+ */
1590
+ on(type, listener) {
1591
+ if (!this.eventListeners.has(type)) {
1592
+ this.eventListeners.set(type, /* @__PURE__ */ new Set());
1593
+ }
1594
+ const listeners = this.eventListeners.get(type);
1595
+ listeners.add(listener);
1596
+ return () => {
1597
+ listeners.delete(listener);
1598
+ };
1599
+ }
1600
+ /**
1601
+ * Subscribe to all events.
1602
+ */
1603
+ onAll(listener) {
1604
+ return this.on("*", listener);
1605
+ }
1606
+ /**
1607
+ * Get async iterable of all events.
1608
+ * Events are yielded as they occur.
1609
+ */
1610
+ async *events() {
1611
+ while (!this.isCompleted) {
1612
+ while (this.eventQueue.length > 0) {
1613
+ yield this.eventQueue.shift();
1614
+ }
1615
+ if (this.isCompleted) break;
1616
+ const event = await new Promise((resolve) => {
1617
+ if (this.eventQueue.length > 0) {
1618
+ resolve(this.eventQueue.shift());
1619
+ } else {
1620
+ this.eventWaiters.push(resolve);
1621
+ }
1622
+ });
1623
+ yield event;
1624
+ }
1625
+ while (this.eventQueue.length > 0) {
1626
+ yield this.eventQueue.shift();
1627
+ }
1628
+ }
1629
+ /**
1630
+ * Mark the tree as complete (no more events will be emitted).
1631
+ */
1632
+ complete() {
1633
+ this.isCompleted = true;
1634
+ for (const waiter of this.eventWaiters) {
1635
+ }
1636
+ this.eventWaiters = [];
1637
+ }
1638
+ /**
1639
+ * Check if the tree is complete.
1640
+ */
1641
+ isComplete() {
1642
+ return this.isCompleted;
1643
+ }
1644
+ };
1645
+ }
1646
+ });
1647
+
1040
1648
  // src/gadgets/media-store.ts
1041
1649
  function getLlmistTmpDir() {
1042
1650
  return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".llmist", "tmp");
@@ -2333,8 +2941,8 @@ var init_conversation_manager = __esm({
2333
2941
  addAssistantMessage(content) {
2334
2942
  this.historyBuilder.addAssistant(content);
2335
2943
  }
2336
- addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2337
- this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2944
+ addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds) {
2945
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds);
2338
2946
  }
2339
2947
  getMessages() {
2340
2948
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -3485,7 +4093,7 @@ var init_executor = __esm({
3485
4093
  init_exceptions();
3486
4094
  init_parser();
3487
4095
  GadgetExecutor = class {
3488
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent) {
4096
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
3489
4097
  this.registry = registry;
3490
4098
  this.requestHumanInput = requestHumanInput;
3491
4099
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -3494,6 +4102,9 @@ var init_executor = __esm({
3494
4102
  this.agentConfig = agentConfig;
3495
4103
  this.subagentConfig = subagentConfig;
3496
4104
  this.onSubagentEvent = onSubagentEvent;
4105
+ this.tree = tree;
4106
+ this.parentNodeId = parentNodeId;
4107
+ this.baseDepth = baseDepth;
3497
4108
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3498
4109
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3499
4110
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3504,15 +4115,21 @@ var init_executor = __esm({
3504
4115
  /**
3505
4116
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
3506
4117
  * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
4118
+ * Returns both the promise and a cancel function to clear the timeout when no longer needed.
3507
4119
  */
3508
4120
  createTimeoutPromise(gadgetName, timeoutMs, abortController) {
3509
- return new Promise((_, reject) => {
3510
- setTimeout(() => {
4121
+ let timeoutId;
4122
+ const promise = new Promise((_, reject) => {
4123
+ timeoutId = setTimeout(() => {
3511
4124
  const timeoutError = new TimeoutException(gadgetName, timeoutMs);
3512
4125
  abortController.abort(timeoutError.message);
3513
4126
  reject(timeoutError);
3514
4127
  }, timeoutMs);
3515
4128
  });
4129
+ return {
4130
+ promise,
4131
+ cancel: () => clearTimeout(timeoutId)
4132
+ };
3516
4133
  }
3517
4134
  /**
3518
4135
  * Unify gadget execute result to consistent internal format.
@@ -3634,6 +4251,8 @@ var init_executor = __esm({
3634
4251
  });
3635
4252
  }
3636
4253
  };
4254
+ const gadgetNodeId = this.tree?.getNodeByInvocationId(call.invocationId)?.id;
4255
+ const gadgetDepth = gadgetNodeId ? this.tree?.getNode(gadgetNodeId)?.depth ?? this.baseDepth : this.baseDepth;
3637
4256
  const ctx = {
3638
4257
  reportCost,
3639
4258
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
@@ -3641,7 +4260,11 @@ var init_executor = __esm({
3641
4260
  agentConfig: this.agentConfig,
3642
4261
  subagentConfig: this.subagentConfig,
3643
4262
  invocationId: call.invocationId,
3644
- onSubagentEvent: this.onSubagentEvent
4263
+ onSubagentEvent: this.onSubagentEvent,
4264
+ // Tree context for subagent support - use gadget's own node ID
4265
+ tree: this.tree,
4266
+ nodeId: gadgetNodeId,
4267
+ depth: gadgetDepth
3645
4268
  };
3646
4269
  let rawResult;
3647
4270
  if (timeoutMs && timeoutMs > 0) {
@@ -3649,10 +4272,15 @@ var init_executor = __esm({
3649
4272
  gadgetName: call.gadgetName,
3650
4273
  timeoutMs
3651
4274
  });
3652
- rawResult = await Promise.race([
3653
- Promise.resolve(gadget.execute(validatedParameters, ctx)),
3654
- this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
3655
- ]);
4275
+ const timeout = this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController);
4276
+ try {
4277
+ rawResult = await Promise.race([
4278
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
4279
+ timeout.promise
4280
+ ]);
4281
+ } finally {
4282
+ timeout.cancel();
4283
+ }
3656
4284
  } else {
3657
4285
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3658
4286
  }
@@ -3847,10 +4475,11 @@ var init_stream_processor = __esm({
3847
4475
  logger;
3848
4476
  parser;
3849
4477
  executor;
3850
- stopOnGadgetError;
3851
- canRecoverFromGadgetError;
4478
+ // Execution Tree context
4479
+ tree;
4480
+ parentNodeId;
4481
+ baseDepth;
3852
4482
  responseText = "";
3853
- executionHalted = false;
3854
4483
  observerFailureCount = 0;
3855
4484
  // Dependency tracking for gadget execution DAG
3856
4485
  /** Gadgets waiting for their dependencies to complete */
@@ -3859,18 +4488,30 @@ var init_stream_processor = __esm({
3859
4488
  completedResults = /* @__PURE__ */ new Map();
3860
4489
  /** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
3861
4490
  failedInvocations = /* @__PURE__ */ new Set();
4491
+ /** Promises for independent gadgets currently executing (fire-and-forget) */
4492
+ inFlightExecutions = /* @__PURE__ */ new Map();
4493
+ /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4494
+ completedResultsQueue = [];
3862
4495
  constructor(options) {
3863
4496
  this.iteration = options.iteration;
3864
4497
  this.registry = options.registry;
3865
4498
  this.hooks = options.hooks ?? {};
3866
4499
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3867
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3868
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4500
+ this.tree = options.tree;
4501
+ this.parentNodeId = options.parentNodeId ?? null;
4502
+ this.baseDepth = options.baseDepth ?? 0;
3869
4503
  this.parser = new GadgetCallParser({
3870
4504
  startPrefix: options.gadgetStartPrefix,
3871
4505
  endPrefix: options.gadgetEndPrefix,
3872
4506
  argPrefix: options.gadgetArgPrefix
3873
4507
  });
4508
+ const wrappedOnSubagentEvent = options.onSubagentEvent ? (event) => {
4509
+ this.completedResultsQueue.push({
4510
+ type: "subagent_event",
4511
+ subagentEvent: event
4512
+ });
4513
+ options.onSubagentEvent?.(event);
4514
+ } : void 0;
3874
4515
  this.executor = new GadgetExecutor(
3875
4516
  options.registry,
3876
4517
  options.requestHumanInput,
@@ -3881,7 +4522,11 @@ var init_stream_processor = __esm({
3881
4522
  options.mediaStore,
3882
4523
  options.agentConfig,
3883
4524
  options.subagentConfig,
3884
- options.onSubagentEvent
4525
+ wrappedOnSubagentEvent,
4526
+ // Tree context for gadget execution
4527
+ options.tree,
4528
+ options.parentNodeId,
4529
+ options.baseDepth
3885
4530
  );
3886
4531
  }
3887
4532
  /**
@@ -3932,7 +4577,7 @@ var init_stream_processor = __esm({
3932
4577
  usage,
3933
4578
  logger: this.logger
3934
4579
  };
3935
- await this.hooks.observers.onStreamChunk(context);
4580
+ await this.hooks.observers?.onStreamChunk?.(context);
3936
4581
  });
3937
4582
  await this.runObserversInParallel(chunkObservers);
3938
4583
  }
@@ -3950,24 +4595,7 @@ var init_stream_processor = __esm({
3950
4595
  }
3951
4596
  }
3952
4597
  }
3953
- if (this.executionHalted) {
3954
- this.logger.info("Breaking from LLM stream due to gadget error");
3955
- break;
3956
- }
3957
- }
3958
- if (!this.executionHalted) {
3959
- for (const event of this.parser.finalize()) {
3960
- for await (const processedEvent of this.processEventGenerator(event)) {
3961
- yield processedEvent;
3962
- if (processedEvent.type === "gadget_result") {
3963
- didExecuteGadgets = true;
3964
- if (processedEvent.result.breaksLoop) {
3965
- shouldBreakLoop = true;
3966
- }
3967
- }
3968
- }
3969
- }
3970
- for await (const evt of this.processPendingGadgetsGenerator()) {
4598
+ for (const evt of this.drainCompletedResults()) {
3971
4599
  yield evt;
3972
4600
  if (evt.type === "gadget_result") {
3973
4601
  didExecuteGadgets = true;
@@ -3977,6 +4605,44 @@ var init_stream_processor = __esm({
3977
4605
  }
3978
4606
  }
3979
4607
  }
4608
+ for (const event of this.parser.finalize()) {
4609
+ for await (const processedEvent of this.processEventGenerator(event)) {
4610
+ yield processedEvent;
4611
+ if (processedEvent.type === "gadget_result") {
4612
+ didExecuteGadgets = true;
4613
+ if (processedEvent.result.breaksLoop) {
4614
+ shouldBreakLoop = true;
4615
+ }
4616
+ }
4617
+ }
4618
+ }
4619
+ for await (const evt of this.waitForInFlightExecutions()) {
4620
+ yield evt;
4621
+ if (evt.type === "gadget_result") {
4622
+ didExecuteGadgets = true;
4623
+ if (evt.result.breaksLoop) {
4624
+ shouldBreakLoop = true;
4625
+ }
4626
+ }
4627
+ }
4628
+ for (const evt of this.drainCompletedResults()) {
4629
+ yield evt;
4630
+ if (evt.type === "gadget_result") {
4631
+ didExecuteGadgets = true;
4632
+ if (evt.result.breaksLoop) {
4633
+ shouldBreakLoop = true;
4634
+ }
4635
+ }
4636
+ }
4637
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4638
+ yield evt;
4639
+ if (evt.type === "gadget_result") {
4640
+ didExecuteGadgets = true;
4641
+ if (evt.result.breaksLoop) {
4642
+ shouldBreakLoop = true;
4643
+ }
4644
+ }
4645
+ }
3980
4646
  let finalMessage = this.responseText;
3981
4647
  if (this.hooks.interceptors?.interceptAssistantMessage) {
3982
4648
  const context = {
@@ -3997,21 +4663,8 @@ var init_stream_processor = __esm({
3997
4663
  };
3998
4664
  yield completionEvent;
3999
4665
  }
4000
- /**
4001
- * Process a single parsed event (text or gadget call).
4002
- * @deprecated Use processEventGenerator for real-time streaming
4003
- */
4004
- async processEvent(event) {
4005
- if (event.type === "text") {
4006
- return this.processTextEvent(event);
4007
- } else if (event.type === "gadget_call") {
4008
- return this.processGadgetCall(event.call);
4009
- }
4010
- return [event];
4011
- }
4012
4666
  /**
4013
4667
  * Process a single parsed event, yielding events in real-time.
4014
- * Generator version of processEvent for streaming support.
4015
4668
  */
4016
4669
  async *processEventGenerator(event) {
4017
4670
  if (event.type === "text") {
@@ -4053,12 +4706,6 @@ var init_stream_processor = __esm({
4053
4706
  * After each execution, pending gadgets are checked to see if they can now run.
4054
4707
  */
4055
4708
  async processGadgetCall(call) {
4056
- if (this.executionHalted) {
4057
- this.logger.debug("Skipping gadget execution due to previous error", {
4058
- gadgetName: call.gadgetName
4059
- });
4060
- return [];
4061
- }
4062
4709
  const events = [];
4063
4710
  events.push({ type: "gadget_call", call });
4064
4711
  if (call.dependencies.length > 0) {
@@ -4109,13 +4756,16 @@ var init_stream_processor = __esm({
4109
4756
  * when parsed (before execution), enabling real-time UI feedback.
4110
4757
  */
4111
4758
  async *processGadgetCallGenerator(call) {
4112
- if (this.executionHalted) {
4113
- this.logger.debug("Skipping gadget execution due to previous error", {
4114
- gadgetName: call.gadgetName
4759
+ yield { type: "gadget_call", call };
4760
+ if (this.tree) {
4761
+ this.tree.addGadget({
4762
+ invocationId: call.invocationId,
4763
+ name: call.gadgetName,
4764
+ parameters: call.parameters ?? {},
4765
+ dependencies: call.dependencies,
4766
+ parentId: this.parentNodeId
4115
4767
  });
4116
- return;
4117
4768
  }
4118
- yield { type: "gadget_call", call };
4119
4769
  if (call.dependencies.length > 0) {
4120
4770
  if (call.dependencies.includes(call.invocationId)) {
4121
4771
  this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
@@ -4152,13 +4802,16 @@ var init_stream_processor = __esm({
4152
4802
  this.gadgetsAwaitingDependencies.set(call.invocationId, call);
4153
4803
  return;
4154
4804
  }
4805
+ for await (const evt of this.executeGadgetGenerator(call)) {
4806
+ yield evt;
4807
+ }
4808
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4809
+ yield evt;
4810
+ }
4811
+ return;
4155
4812
  }
4156
- for await (const evt of this.executeGadgetGenerator(call)) {
4157
- yield evt;
4158
- }
4159
- for await (const evt of this.processPendingGadgetsGenerator()) {
4160
- yield evt;
4161
- }
4813
+ const executionPromise = this.executeGadgetAndCollect(call);
4814
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4162
4815
  }
4163
4816
  /**
4164
4817
  * Execute a gadget through the full hook lifecycle.
@@ -4173,15 +4826,6 @@ var init_stream_processor = __esm({
4173
4826
  error: call.parseError,
4174
4827
  rawParameters: call.parametersRaw
4175
4828
  });
4176
- const shouldContinue = await this.checkCanRecoverFromError(
4177
- call.parseError,
4178
- call.gadgetName,
4179
- "parse",
4180
- call.parameters
4181
- );
4182
- if (!shouldContinue) {
4183
- this.executionHalted = true;
4184
- }
4185
4829
  }
4186
4830
  let parameters = call.parameters ?? {};
4187
4831
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4224,7 +4868,7 @@ var init_stream_processor = __esm({
4224
4868
  parameters,
4225
4869
  logger: this.logger
4226
4870
  };
4227
- await this.hooks.observers.onGadgetExecutionStart(context);
4871
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4228
4872
  });
4229
4873
  }
4230
4874
  await this.runObserversInParallel(startObservers);
@@ -4293,7 +4937,7 @@ var init_stream_processor = __esm({
4293
4937
  cost: result.cost,
4294
4938
  logger: this.logger
4295
4939
  };
4296
- await this.hooks.observers.onGadgetExecutionComplete(context);
4940
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4297
4941
  });
4298
4942
  }
4299
4943
  await this.runObserversInParallel(completeObservers);
@@ -4302,18 +4946,6 @@ var init_stream_processor = __esm({
4302
4946
  this.failedInvocations.add(result.invocationId);
4303
4947
  }
4304
4948
  events.push({ type: "gadget_result", result });
4305
- if (result.error) {
4306
- const errorType = this.determineErrorType(call, result);
4307
- const shouldContinue = await this.checkCanRecoverFromError(
4308
- result.error,
4309
- result.gadgetName,
4310
- errorType,
4311
- result.parameters
4312
- );
4313
- if (!shouldContinue) {
4314
- this.executionHalted = true;
4315
- }
4316
- }
4317
4949
  return events;
4318
4950
  }
4319
4951
  /**
@@ -4327,15 +4959,6 @@ var init_stream_processor = __esm({
4327
4959
  error: call.parseError,
4328
4960
  rawParameters: call.parametersRaw
4329
4961
  });
4330
- const shouldContinue = await this.checkCanRecoverFromError(
4331
- call.parseError,
4332
- call.gadgetName,
4333
- "parse",
4334
- call.parameters
4335
- );
4336
- if (!shouldContinue) {
4337
- this.executionHalted = true;
4338
- }
4339
4962
  }
4340
4963
  let parameters = call.parameters ?? {};
4341
4964
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4378,10 +5001,16 @@ var init_stream_processor = __esm({
4378
5001
  parameters,
4379
5002
  logger: this.logger
4380
5003
  };
4381
- await this.hooks.observers.onGadgetExecutionStart(context);
5004
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4382
5005
  });
4383
5006
  }
4384
5007
  await this.runObserversInParallel(startObservers);
5008
+ if (this.tree) {
5009
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5010
+ if (gadgetNode) {
5011
+ this.tree.startGadget(gadgetNode.id);
5012
+ }
5013
+ }
4385
5014
  let result;
4386
5015
  if (shouldSkip) {
4387
5016
  result = {
@@ -4447,27 +5076,84 @@ var init_stream_processor = __esm({
4447
5076
  cost: result.cost,
4448
5077
  logger: this.logger
4449
5078
  };
4450
- await this.hooks.observers.onGadgetExecutionComplete(context);
5079
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4451
5080
  });
4452
5081
  }
4453
5082
  await this.runObserversInParallel(completeObservers);
5083
+ if (this.tree) {
5084
+ const gadgetNode = this.tree.getNodeByInvocationId(result.invocationId);
5085
+ if (gadgetNode) {
5086
+ if (result.error) {
5087
+ this.tree.completeGadget(gadgetNode.id, {
5088
+ error: result.error,
5089
+ executionTimeMs: result.executionTimeMs,
5090
+ cost: result.cost
5091
+ });
5092
+ } else {
5093
+ this.tree.completeGadget(gadgetNode.id, {
5094
+ result: result.result,
5095
+ executionTimeMs: result.executionTimeMs,
5096
+ cost: result.cost,
5097
+ media: result.media
5098
+ });
5099
+ }
5100
+ }
5101
+ }
4454
5102
  this.completedResults.set(result.invocationId, result);
4455
5103
  if (result.error) {
4456
5104
  this.failedInvocations.add(result.invocationId);
4457
5105
  }
4458
5106
  yield { type: "gadget_result", result };
4459
- if (result.error) {
4460
- const errorType = this.determineErrorType(call, result);
4461
- const shouldContinue = await this.checkCanRecoverFromError(
4462
- result.error,
4463
- result.gadgetName,
4464
- errorType,
4465
- result.parameters
4466
- );
4467
- if (!shouldContinue) {
4468
- this.executionHalted = true;
5107
+ }
5108
+ /**
5109
+ * Execute a gadget and push events to the completed results queue (non-blocking).
5110
+ * Used for fire-and-forget parallel execution of independent gadgets.
5111
+ * Results are pushed to completedResultsQueue for real-time streaming to the caller.
5112
+ */
5113
+ async executeGadgetAndCollect(call) {
5114
+ for await (const evt of this.executeGadgetGenerator(call)) {
5115
+ this.completedResultsQueue.push(evt);
5116
+ }
5117
+ }
5118
+ /**
5119
+ * Drain all completed results from the queue.
5120
+ * Used to yield results as they complete during stream processing.
5121
+ * @returns Generator that yields all events currently in the queue
5122
+ */
5123
+ *drainCompletedResults() {
5124
+ while (this.completedResultsQueue.length > 0) {
5125
+ yield this.completedResultsQueue.shift();
5126
+ }
5127
+ }
5128
+ /**
5129
+ * Wait for all in-flight gadget executions to complete, yielding events in real-time.
5130
+ * Called at stream end to ensure all parallel executions finish.
5131
+ * Results and subagent events are pushed to completedResultsQueue during execution.
5132
+ * This generator yields queued events while polling, enabling real-time display
5133
+ * of subagent activity (LLM calls, nested gadgets) during long-running gadgets.
5134
+ * Clears the inFlightExecutions map after all gadgets complete.
5135
+ */
5136
+ async *waitForInFlightExecutions() {
5137
+ if (this.inFlightExecutions.size === 0) {
5138
+ return;
5139
+ }
5140
+ this.logger.debug("Waiting for in-flight gadget executions", {
5141
+ count: this.inFlightExecutions.size,
5142
+ invocationIds: Array.from(this.inFlightExecutions.keys())
5143
+ });
5144
+ const allDone = Promise.all(this.inFlightExecutions.values()).then(() => "done");
5145
+ const POLL_INTERVAL_MS = 100;
5146
+ while (true) {
5147
+ const result = await Promise.race([
5148
+ allDone,
5149
+ new Promise((resolve) => setTimeout(() => resolve("poll"), POLL_INTERVAL_MS))
5150
+ ]);
5151
+ yield* this.drainCompletedResults();
5152
+ if (result === "done") {
5153
+ break;
4469
5154
  }
4470
5155
  }
5156
+ this.inFlightExecutions.clear();
4471
5157
  }
4472
5158
  /**
4473
5159
  * Handle a gadget that cannot execute because a dependency failed.
@@ -4492,6 +5178,12 @@ var init_stream_processor = __esm({
4492
5178
  }
4493
5179
  if (action.action === "skip") {
4494
5180
  this.failedInvocations.add(call.invocationId);
5181
+ if (this.tree) {
5182
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5183
+ if (gadgetNode) {
5184
+ this.tree.skipGadget(gadgetNode.id, failedDep, depError, "dependency_failed");
5185
+ }
5186
+ }
4495
5187
  const skipEvent = {
4496
5188
  type: "gadget_skipped",
4497
5189
  gadgetName: call.gadgetName,
@@ -4511,7 +5203,7 @@ var init_stream_processor = __esm({
4511
5203
  failedDependencyError: depError,
4512
5204
  logger: this.logger
4513
5205
  };
4514
- await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(observeContext));
5206
+ await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
4515
5207
  }
4516
5208
  this.logger.info("Gadget skipped due to failed dependency", {
4517
5209
  gadgetName: call.gadgetName,
@@ -4743,48 +5435,6 @@ var init_stream_processor = __esm({
4743
5435
  observers.map((observer) => this.safeObserve(observer))
4744
5436
  );
4745
5437
  }
4746
- /**
4747
- * Check if execution can recover from an error.
4748
- *
4749
- * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4750
- *
4751
- * Logic:
4752
- * - If custom canRecoverFromGadgetError is provided, use it
4753
- * - Otherwise, use stopOnGadgetError config:
4754
- * - stopOnGadgetError=true → return false (stop execution)
4755
- * - stopOnGadgetError=false → return true (continue execution)
4756
- */
4757
- async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4758
- if (this.canRecoverFromGadgetError) {
4759
- return await this.canRecoverFromGadgetError({
4760
- error,
4761
- gadgetName,
4762
- errorType,
4763
- parameters
4764
- });
4765
- }
4766
- const shouldContinue = !this.stopOnGadgetError;
4767
- this.logger.debug("Checking if should continue after error", {
4768
- error,
4769
- gadgetName,
4770
- errorType,
4771
- stopOnGadgetError: this.stopOnGadgetError,
4772
- shouldContinue
4773
- });
4774
- return shouldContinue;
4775
- }
4776
- /**
4777
- * Determine the type of error from a gadget execution.
4778
- */
4779
- determineErrorType(call, result) {
4780
- if (call.parseError) {
4781
- return "parse";
4782
- }
4783
- if (result.error?.includes("Invalid parameters:")) {
4784
- return "validation";
4785
- }
4786
- return "execution";
4787
- }
4788
5438
  };
4789
5439
  }
4790
5440
  });
@@ -4795,6 +5445,7 @@ var init_agent = __esm({
4795
5445
  "src/agent/agent.ts"() {
4796
5446
  "use strict";
4797
5447
  init_constants();
5448
+ init_execution_tree();
4798
5449
  init_messages();
4799
5450
  init_model_shortcuts();
4800
5451
  init_media_store();
@@ -4822,8 +5473,6 @@ var init_agent = __esm({
4822
5473
  requestHumanInput;
4823
5474
  textOnlyHandler;
4824
5475
  textWithGadgetsHandler;
4825
- stopOnGadgetError;
4826
- canRecoverFromGadgetError;
4827
5476
  defaultGadgetTimeoutMs;
4828
5477
  defaultMaxTokens;
4829
5478
  hasUserPrompt;
@@ -4846,6 +5495,12 @@ var init_agent = __esm({
4846
5495
  pendingSubagentEvents = [];
4847
5496
  // Combined callback that queues events AND calls user callback
4848
5497
  onSubagentEvent;
5498
+ // Counter for generating synthetic invocation IDs for wrapped text content
5499
+ syntheticInvocationCounter = 0;
5500
+ // Execution Tree - first-class model for nested subagent support
5501
+ tree;
5502
+ parentNodeId;
5503
+ baseDepth;
4849
5504
  /**
4850
5505
  * Creates a new Agent instance.
4851
5506
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4868,8 +5523,6 @@ var init_agent = __esm({
4868
5523
  this.requestHumanInput = options.requestHumanInput;
4869
5524
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4870
5525
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4871
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4872
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4873
5526
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4874
5527
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4875
5528
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -4923,6 +5576,9 @@ var init_agent = __esm({
4923
5576
  temperature: this.temperature
4924
5577
  };
4925
5578
  this.subagentConfig = options.subagentConfig;
5579
+ this.tree = options.parentTree ?? new ExecutionTree();
5580
+ this.parentNodeId = options.parentNodeId ?? null;
5581
+ this.baseDepth = options.baseDepth ?? 0;
4926
5582
  this.userSubagentEventCallback = options.onSubagentEvent;
4927
5583
  this.onSubagentEvent = (event) => {
4928
5584
  this.pendingSubagentEvents.push(event);
@@ -4987,7 +5643,9 @@ var init_agent = __esm({
4987
5643
  *flushPendingSubagentEvents() {
4988
5644
  while (this.pendingSubagentEvents.length > 0) {
4989
5645
  const event = this.pendingSubagentEvents.shift();
4990
- yield { type: "subagent_event", subagentEvent: event };
5646
+ if (event) {
5647
+ yield { type: "subagent_event", subagentEvent: event };
5648
+ }
4991
5649
  }
4992
5650
  }
4993
5651
  /**
@@ -5041,6 +5699,48 @@ var init_agent = __esm({
5041
5699
  getMediaStore() {
5042
5700
  return this.mediaStore;
5043
5701
  }
5702
+ /**
5703
+ * Get the execution tree for this agent.
5704
+ *
5705
+ * The execution tree provides a first-class model of all LLM calls and gadget executions,
5706
+ * including nested subagent activity. Use this to:
5707
+ * - Query execution state: `tree.getNode(id)`
5708
+ * - Get total cost: `tree.getTotalCost()`
5709
+ * - Get subtree cost/media/tokens: `tree.getSubtreeCost(nodeId)`
5710
+ * - Subscribe to events: `tree.on("llm_call_complete", handler)`
5711
+ * - Stream all events: `for await (const event of tree.events())`
5712
+ *
5713
+ * For subagents (created with `withParentContext`), the tree is shared with the parent,
5714
+ * enabling unified tracking and real-time visibility across all nesting levels.
5715
+ *
5716
+ * @returns The ExecutionTree instance
5717
+ *
5718
+ * @example
5719
+ * ```typescript
5720
+ * const agent = LLMist.createAgent()
5721
+ * .withModel("sonnet")
5722
+ * .withGadgets(BrowseWeb)
5723
+ * .ask("Research topic X");
5724
+ *
5725
+ * for await (const event of agent.run()) {
5726
+ * // Process events...
5727
+ * }
5728
+ *
5729
+ * // After execution, query the tree
5730
+ * const tree = agent.getTree();
5731
+ * console.log(`Total cost: $${tree.getTotalCost().toFixed(4)}`);
5732
+ *
5733
+ * // Inspect all LLM calls
5734
+ * for (const node of tree.getAllNodes()) {
5735
+ * if (node.type === "llm_call") {
5736
+ * console.log(`LLM #${node.iteration}: ${node.model}`);
5737
+ * }
5738
+ * }
5739
+ * ```
5740
+ */
5741
+ getTree() {
5742
+ return this.tree;
5743
+ }
5044
5744
  /**
5045
5745
  * Manually trigger context compaction.
5046
5746
  *
@@ -5142,6 +5842,7 @@ var init_agent = __esm({
5142
5842
  await this.hooks.observers.onCompaction({
5143
5843
  iteration: currentIteration,
5144
5844
  event: compactionEvent,
5845
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5145
5846
  stats: this.compactionManager.getStats(),
5146
5847
  logger: this.logger
5147
5848
  });
@@ -5203,6 +5904,13 @@ var init_agent = __esm({
5203
5904
  messageCount: llmOptions.messages.length,
5204
5905
  messages: llmOptions.messages
5205
5906
  });
5907
+ const llmNode = this.tree.addLLMCall({
5908
+ iteration: currentIteration,
5909
+ model: llmOptions.model,
5910
+ parentId: this.parentNodeId,
5911
+ request: llmOptions.messages
5912
+ });
5913
+ const currentLLMNodeId = llmNode.id;
5206
5914
  const stream2 = this.client.stream(llmOptions);
5207
5915
  const processor = new StreamProcessor({
5208
5916
  iteration: currentIteration,
@@ -5213,14 +5921,17 @@ var init_agent = __esm({
5213
5921
  hooks: this.hooks,
5214
5922
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
5215
5923
  requestHumanInput: this.requestHumanInput,
5216
- stopOnGadgetError: this.stopOnGadgetError,
5217
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5218
5924
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5219
5925
  client: this.client,
5220
5926
  mediaStore: this.mediaStore,
5221
5927
  agentConfig: this.agentContextConfig,
5222
5928
  subagentConfig: this.subagentConfig,
5223
- onSubagentEvent: this.onSubagentEvent
5929
+ onSubagentEvent: this.onSubagentEvent,
5930
+ // Tree context for execution tracking
5931
+ tree: this.tree,
5932
+ parentNodeId: currentLLMNodeId,
5933
+ // Gadgets are children of this LLM call
5934
+ baseDepth: this.baseDepth
5224
5935
  });
5225
5936
  let streamMetadata = null;
5226
5937
  let gadgetCallCount = 0;
@@ -5266,6 +5977,11 @@ var init_agent = __esm({
5266
5977
  await this.hooks.observers.onLLMCallComplete(context);
5267
5978
  }
5268
5979
  });
5980
+ this.tree.completeLLMCall(currentLLMNodeId, {
5981
+ response: result.rawResponse,
5982
+ usage: result.usage,
5983
+ finishReason: result.finishReason
5984
+ });
5269
5985
  let finalMessage = result.finalMessage;
5270
5986
  if (this.hooks.controllers?.afterLLMCall) {
5271
5987
  const context = {
@@ -5300,10 +6016,12 @@ var init_agent = __esm({
5300
6016
  const textContent = textOutputs.join("");
5301
6017
  if (textContent.trim()) {
5302
6018
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6019
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
5303
6020
  this.conversation.addGadgetCallResult(
5304
6021
  gadgetName,
5305
6022
  parameterMapping(textContent),
5306
- resultMapping ? resultMapping(textContent) : textContent
6023
+ resultMapping ? resultMapping(textContent) : textContent,
6024
+ syntheticId
5307
6025
  );
5308
6026
  }
5309
6027
  }
@@ -5314,6 +6032,7 @@ var init_agent = __esm({
5314
6032
  gadgetResult.gadgetName,
5315
6033
  gadgetResult.parameters,
5316
6034
  gadgetResult.error ?? gadgetResult.result ?? "",
6035
+ gadgetResult.invocationId,
5317
6036
  gadgetResult.media,
5318
6037
  gadgetResult.mediaIds
5319
6038
  );
@@ -5321,10 +6040,12 @@ var init_agent = __esm({
5321
6040
  }
5322
6041
  } else {
5323
6042
  if (finalMessage.trim()) {
6043
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
5324
6044
  this.conversation.addGadgetCallResult(
5325
6045
  "TellUser",
5326
6046
  { message: finalMessage, done: false, type: "info" },
5327
- `\u2139\uFE0F ${finalMessage}`
6047
+ `\u2139\uFE0F ${finalMessage}`,
6048
+ syntheticId
5328
6049
  );
5329
6050
  }
5330
6051
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
@@ -5535,8 +6256,6 @@ var init_builder = __esm({
5535
6256
  gadgetArgPrefix;
5536
6257
  textOnlyHandler;
5537
6258
  textWithGadgetsHandler;
5538
- stopOnGadgetError;
5539
- canRecoverFromGadgetError;
5540
6259
  defaultGadgetTimeoutMs;
5541
6260
  gadgetOutputLimit;
5542
6261
  gadgetOutputLimitPercent;
@@ -5545,6 +6264,8 @@ var init_builder = __esm({
5545
6264
  trailingMessage;
5546
6265
  subagentConfig;
5547
6266
  subagentEventCallback;
6267
+ // Tree context for subagent support - enables shared tree model
6268
+ // When a gadget calls withParentContext(ctx), it shares the parent's tree
5548
6269
  parentContext;
5549
6270
  constructor(client) {
5550
6271
  this.client = client;
@@ -5827,62 +6548,6 @@ var init_builder = __esm({
5827
6548
  this.textWithGadgetsHandler = handler;
5828
6549
  return this;
5829
6550
  }
5830
- /**
5831
- * Set whether to stop gadget execution on first error.
5832
- *
5833
- * When true (default), if a gadget fails:
5834
- * - Subsequent gadgets in the same response are skipped
5835
- * - LLM stream is cancelled to save costs
5836
- * - Agent loop continues with error in context
5837
- *
5838
- * When false:
5839
- * - All gadgets in the response still execute
5840
- * - LLM stream continues to completion
5841
- *
5842
- * @param stop - Whether to stop on gadget error
5843
- * @returns This builder for chaining
5844
- *
5845
- * @example
5846
- * ```typescript
5847
- * .withStopOnGadgetError(false)
5848
- * ```
5849
- */
5850
- withStopOnGadgetError(stop) {
5851
- this.stopOnGadgetError = stop;
5852
- return this;
5853
- }
5854
- /**
5855
- * Set custom error handling logic.
5856
- *
5857
- * Provides fine-grained control over whether to continue after different types of errors.
5858
- * Overrides `stopOnGadgetError` when provided.
5859
- *
5860
- * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5861
- * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5862
- * but maps to the `canRecoverFromGadgetError` property internally.
5863
- *
5864
- * @param handler - Function that decides whether to continue after an error.
5865
- * Return `true` to continue execution, `false` to stop.
5866
- * @returns This builder for chaining
5867
- *
5868
- * @example
5869
- * ```typescript
5870
- * .withErrorHandler((context) => {
5871
- * // Stop on parse errors, continue on validation/execution errors
5872
- * if (context.errorType === "parse") {
5873
- * return false;
5874
- * }
5875
- * if (context.error.includes("CRITICAL")) {
5876
- * return false;
5877
- * }
5878
- * return true;
5879
- * })
5880
- * ```
5881
- */
5882
- withErrorHandler(handler) {
5883
- this.canRecoverFromGadgetError = handler;
5884
- return this;
5885
- }
5886
6551
  /**
5887
6552
  * Set default timeout for gadget execution.
5888
6553
  *
@@ -6077,6 +6742,15 @@ var init_builder = __esm({
6077
6742
  * The method extracts `invocationId` and `onSubagentEvent` from the execution
6078
6743
  * context and sets up automatic forwarding via hooks and event wrapping.
6079
6744
  *
6745
+ * **NEW: Shared Tree Model** - When the parent provides an ExecutionTree via context,
6746
+ * the subagent shares that tree instead of creating its own. This enables:
6747
+ * - Unified cost tracking across all nesting levels
6748
+ * - Automatic media aggregation via `tree.getSubtreeMedia(nodeId)`
6749
+ * - Real-time visibility of nested execution in the parent
6750
+ *
6751
+ * **Signal Forwarding** - When parent context includes a signal, it's automatically
6752
+ * forwarded to the subagent for proper cancellation propagation.
6753
+ *
6080
6754
  * @param ctx - ExecutionContext passed to the gadget's execute() method
6081
6755
  * @param depth - Nesting depth (default: 1 for direct child)
6082
6756
  * @returns This builder for chaining
@@ -6097,17 +6771,25 @@ var init_builder = __esm({
6097
6771
  * result = event.content;
6098
6772
  * }
6099
6773
  * }
6774
+ *
6775
+ * // After subagent completes, costs are automatically aggregated
6776
+ * // No manual tracking needed - use tree methods:
6777
+ * const totalCost = ctx.tree?.getSubtreeCost(ctx.nodeId!);
6778
+ * const allMedia = ctx.tree?.getSubtreeMedia(ctx.nodeId!);
6100
6779
  * }
6101
6780
  * ```
6102
6781
  */
6103
6782
  withParentContext(ctx, depth = 1) {
6104
- if (ctx.onSubagentEvent && ctx.invocationId) {
6783
+ if (ctx.tree) {
6105
6784
  this.parentContext = {
6106
- invocationId: ctx.invocationId,
6107
- onSubagentEvent: ctx.onSubagentEvent,
6785
+ tree: ctx.tree,
6786
+ nodeId: ctx.nodeId,
6108
6787
  depth
6109
6788
  };
6110
6789
  }
6790
+ if (ctx.signal && !this.signal) {
6791
+ this.signal = ctx.signal;
6792
+ }
6111
6793
  return this;
6112
6794
  }
6113
6795
  /**
@@ -6140,11 +6822,13 @@ var init_builder = __esm({
6140
6822
  *
6141
6823
  * This is useful for in-context learning - showing the LLM what "past self"
6142
6824
  * did correctly so it mimics the pattern. The call is formatted with proper
6143
- * markers and parameter format.
6825
+ * markers and parameter format, including the invocation ID so the LLM can
6826
+ * reference previous calls when building dependencies.
6144
6827
  *
6145
6828
  * @param gadgetName - Name of the gadget
6146
6829
  * @param parameters - Parameters passed to the gadget
6147
6830
  * @param result - Result returned by the gadget
6831
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
6148
6832
  * @returns This builder for chaining
6149
6833
  *
6150
6834
  * @example
@@ -6156,124 +6840,36 @@ var init_builder = __esm({
6156
6840
  * done: false,
6157
6841
  * type: 'info'
6158
6842
  * },
6159
- * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands'
6843
+ * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands',
6844
+ * 'gc_1'
6160
6845
  * )
6161
6846
  * ```
6162
6847
  */
6163
- withSyntheticGadgetCall(gadgetName, parameters, result) {
6848
+ withSyntheticGadgetCall(gadgetName, parameters, result, invocationId) {
6164
6849
  const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
6165
6850
  const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
6166
6851
  const paramStr = this.formatBlockParameters(parameters, "");
6167
6852
  this.initialMessages.push({
6168
6853
  role: "assistant",
6169
- content: `${startPrefix}${gadgetName}
6854
+ content: `${startPrefix}${gadgetName}:${invocationId}
6170
6855
  ${paramStr}
6171
6856
  ${endPrefix}`
6172
6857
  });
6173
6858
  this.initialMessages.push({
6174
6859
  role: "user",
6175
- content: `Result: ${result}`
6860
+ content: `Result (${invocationId}): ${result}`
6176
6861
  });
6177
6862
  return this;
6178
6863
  }
6179
6864
  /**
6180
- * Compose the final hooks, including:
6181
- * - Trailing message injection (if configured)
6182
- * - Subagent event forwarding for LLM calls (if parentContext is set)
6865
+ * Compose the final hooks, including trailing message injection if configured.
6866
+ *
6867
+ * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
6868
+ * When a subagent uses withParentContext(ctx), it shares the parent's tree,
6869
+ * and all events are automatically visible to tree subscribers (like the TUI).
6183
6870
  */
6184
6871
  composeHooks() {
6185
- let hooks = this.hooks;
6186
- if (this.parentContext) {
6187
- const { invocationId, onSubagentEvent, depth } = this.parentContext;
6188
- const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6189
- const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6190
- const existingOnGadgetExecutionStart = hooks?.observers?.onGadgetExecutionStart;
6191
- const existingOnGadgetExecutionComplete = hooks?.observers?.onGadgetExecutionComplete;
6192
- hooks = {
6193
- ...hooks,
6194
- observers: {
6195
- ...hooks?.observers,
6196
- onLLMCallStart: async (context) => {
6197
- let inputTokens;
6198
- try {
6199
- if (this.client) {
6200
- inputTokens = await this.client.countTokens(
6201
- context.options.model,
6202
- context.options.messages
6203
- );
6204
- }
6205
- } catch {
6206
- }
6207
- onSubagentEvent({
6208
- type: "llm_call_start",
6209
- gadgetInvocationId: invocationId,
6210
- depth,
6211
- event: {
6212
- iteration: context.iteration,
6213
- model: context.options.model,
6214
- inputTokens
6215
- }
6216
- });
6217
- if (existingOnLLMCallStart) {
6218
- await existingOnLLMCallStart(context);
6219
- }
6220
- },
6221
- onLLMCallComplete: async (context) => {
6222
- onSubagentEvent({
6223
- type: "llm_call_end",
6224
- gadgetInvocationId: invocationId,
6225
- depth,
6226
- event: {
6227
- iteration: context.iteration,
6228
- model: context.options.model,
6229
- // Backward compat fields
6230
- inputTokens: context.usage?.inputTokens,
6231
- outputTokens: context.usage?.outputTokens,
6232
- finishReason: context.finishReason ?? void 0,
6233
- // Full usage object with cache details (for first-class display)
6234
- usage: context.usage
6235
- // Cost will be calculated by parent if it has model registry
6236
- }
6237
- });
6238
- if (existingOnLLMCallComplete) {
6239
- await existingOnLLMCallComplete(context);
6240
- }
6241
- },
6242
- onGadgetExecutionStart: async (context) => {
6243
- onSubagentEvent({
6244
- type: "gadget_call",
6245
- gadgetInvocationId: invocationId,
6246
- depth,
6247
- event: {
6248
- call: {
6249
- invocationId: context.invocationId,
6250
- gadgetName: context.gadgetName,
6251
- parameters: context.parameters
6252
- }
6253
- }
6254
- });
6255
- if (existingOnGadgetExecutionStart) {
6256
- await existingOnGadgetExecutionStart(context);
6257
- }
6258
- },
6259
- onGadgetExecutionComplete: async (context) => {
6260
- onSubagentEvent({
6261
- type: "gadget_result",
6262
- gadgetInvocationId: invocationId,
6263
- depth,
6264
- event: {
6265
- result: {
6266
- invocationId: context.invocationId
6267
- }
6268
- }
6269
- });
6270
- if (existingOnGadgetExecutionComplete) {
6271
- await existingOnGadgetExecutionComplete(context);
6272
- }
6273
- }
6274
- }
6275
- };
6276
- }
6872
+ const hooks = this.hooks;
6277
6873
  if (!this.trailingMessage) {
6278
6874
  return hooks;
6279
6875
  }
@@ -6356,19 +6952,6 @@ ${endPrefix}`
6356
6952
  this.client = new LLMistClass();
6357
6953
  }
6358
6954
  const registry = GadgetRegistry.from(this.gadgets);
6359
- let onSubagentEvent = this.subagentEventCallback;
6360
- if (this.parentContext) {
6361
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6362
- const existingCallback = this.subagentEventCallback;
6363
- onSubagentEvent = (event) => {
6364
- parentCallback({
6365
- ...event,
6366
- gadgetInvocationId: invocationId,
6367
- depth: event.depth + depth
6368
- });
6369
- existingCallback?.(event);
6370
- };
6371
- }
6372
6955
  return {
6373
6956
  client: this.client,
6374
6957
  model: this.model ?? "openai:gpt-5-nano",
@@ -6387,15 +6970,17 @@ ${endPrefix}`
6387
6970
  gadgetArgPrefix: this.gadgetArgPrefix,
6388
6971
  textOnlyHandler: this.textOnlyHandler,
6389
6972
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6390
- stopOnGadgetError: this.stopOnGadgetError,
6391
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6392
6973
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6393
6974
  gadgetOutputLimit: this.gadgetOutputLimit,
6394
6975
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6395
6976
  compactionConfig: this.compactionConfig,
6396
6977
  signal: this.signal,
6397
6978
  subagentConfig: this.subagentConfig,
6398
- onSubagentEvent
6979
+ onSubagentEvent: this.subagentEventCallback,
6980
+ // Tree context for shared tree model (subagents share parent's tree)
6981
+ parentTree: this.parentContext?.tree,
6982
+ parentNodeId: this.parentContext?.nodeId,
6983
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6399
6984
  };
6400
6985
  }
6401
6986
  ask(userPrompt) {
@@ -6552,19 +7137,6 @@ ${endPrefix}`
6552
7137
  this.client = new LLMistClass();
6553
7138
  }
6554
7139
  const registry = GadgetRegistry.from(this.gadgets);
6555
- let onSubagentEvent = this.subagentEventCallback;
6556
- if (this.parentContext) {
6557
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6558
- const existingCallback = this.subagentEventCallback;
6559
- onSubagentEvent = (event) => {
6560
- parentCallback({
6561
- ...event,
6562
- gadgetInvocationId: invocationId,
6563
- depth: event.depth + depth
6564
- });
6565
- existingCallback?.(event);
6566
- };
6567
- }
6568
7140
  const options = {
6569
7141
  client: this.client,
6570
7142
  model: this.model ?? "openai:gpt-5-nano",
@@ -6583,15 +7155,17 @@ ${endPrefix}`
6583
7155
  gadgetArgPrefix: this.gadgetArgPrefix,
6584
7156
  textOnlyHandler: this.textOnlyHandler,
6585
7157
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6586
- stopOnGadgetError: this.stopOnGadgetError,
6587
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6588
7158
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6589
7159
  gadgetOutputLimit: this.gadgetOutputLimit,
6590
7160
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6591
7161
  compactionConfig: this.compactionConfig,
6592
7162
  signal: this.signal,
6593
7163
  subagentConfig: this.subagentConfig,
6594
- onSubagentEvent
7164
+ onSubagentEvent: this.subagentEventCallback,
7165
+ // Tree context for shared tree model (subagents share parent's tree)
7166
+ parentTree: this.parentContext?.tree,
7167
+ parentNodeId: this.parentContext?.nodeId,
7168
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6595
7169
  };
6596
7170
  return new Agent(AGENT_INTERNAL_KEY, options);
6597
7171
  }
@@ -11224,16 +11798,16 @@ var MockConversationManager = class {
11224
11798
  this.history.push(msg);
11225
11799
  this.addedMessages.push(msg);
11226
11800
  }
11227
- addGadgetCallResult(gadgetName, parameters, result) {
11801
+ addGadgetCallResult(gadgetName, parameters, result, invocationId) {
11228
11802
  const assistantMsg = {
11229
11803
  role: "assistant",
11230
- content: `!!!GADGET_START:${gadgetName}
11804
+ content: `!!!GADGET_START:${gadgetName}:${invocationId}
11231
11805
  ${JSON.stringify(parameters)}
11232
11806
  !!!GADGET_END`
11233
11807
  };
11234
11808
  const resultMsg = {
11235
11809
  role: "user",
11236
- content: `Result: ${result}`
11810
+ content: `Result (${invocationId}): ${result}`
11237
11811
  };
11238
11812
  this.history.push(assistantMsg);
11239
11813
  this.history.push(resultMsg);