llmist 6.0.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 */
@@ -3925,18 +4554,28 @@ var init_stream_processor = __esm({
3925
4554
  failedInvocations = /* @__PURE__ */ new Set();
3926
4555
  /** Promises for independent gadgets currently executing (fire-and-forget) */
3927
4556
  inFlightExecutions = /* @__PURE__ */ new Map();
4557
+ /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4558
+ completedResultsQueue = [];
3928
4559
  constructor(options) {
3929
4560
  this.iteration = options.iteration;
3930
4561
  this.registry = options.registry;
3931
4562
  this.hooks = options.hooks ?? {};
3932
4563
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3933
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3934
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4564
+ this.tree = options.tree;
4565
+ this.parentNodeId = options.parentNodeId ?? null;
4566
+ this.baseDepth = options.baseDepth ?? 0;
3935
4567
  this.parser = new GadgetCallParser({
3936
4568
  startPrefix: options.gadgetStartPrefix,
3937
4569
  endPrefix: options.gadgetEndPrefix,
3938
4570
  argPrefix: options.gadgetArgPrefix
3939
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;
3940
4579
  this.executor = new GadgetExecutor(
3941
4580
  options.registry,
3942
4581
  options.requestHumanInput,
@@ -3947,7 +4586,11 @@ var init_stream_processor = __esm({
3947
4586
  options.mediaStore,
3948
4587
  options.agentConfig,
3949
4588
  options.subagentConfig,
3950
- options.onSubagentEvent
4589
+ wrappedOnSubagentEvent,
4590
+ // Tree context for gadget execution
4591
+ options.tree,
4592
+ options.parentNodeId,
4593
+ options.baseDepth
3951
4594
  );
3952
4595
  }
3953
4596
  /**
@@ -3998,7 +4641,7 @@ var init_stream_processor = __esm({
3998
4641
  usage,
3999
4642
  logger: this.logger
4000
4643
  };
4001
- await this.hooks.observers.onStreamChunk(context);
4644
+ await this.hooks.observers?.onStreamChunk?.(context);
4002
4645
  });
4003
4646
  await this.runObserversInParallel(chunkObservers);
4004
4647
  }
@@ -4016,25 +4659,7 @@ var init_stream_processor = __esm({
4016
4659
  }
4017
4660
  }
4018
4661
  }
4019
- if (this.executionHalted) {
4020
- this.logger.info("Breaking from LLM stream due to gadget error");
4021
- break;
4022
- }
4023
- }
4024
- if (!this.executionHalted) {
4025
- for (const event of this.parser.finalize()) {
4026
- for await (const processedEvent of this.processEventGenerator(event)) {
4027
- yield processedEvent;
4028
- if (processedEvent.type === "gadget_result") {
4029
- didExecuteGadgets = true;
4030
- if (processedEvent.result.breaksLoop) {
4031
- shouldBreakLoop = true;
4032
- }
4033
- }
4034
- }
4035
- }
4036
- const inFlightResults = await this.collectInFlightResults();
4037
- for (const evt of inFlightResults) {
4662
+ for (const evt of this.drainCompletedResults()) {
4038
4663
  yield evt;
4039
4664
  if (evt.type === "gadget_result") {
4040
4665
  didExecuteGadgets = true;
@@ -4043,16 +4668,45 @@ var init_stream_processor = __esm({
4043
4668
  }
4044
4669
  }
4045
4670
  }
4046
- for await (const evt of this.processPendingGadgetsGenerator()) {
4047
- yield evt;
4048
- if (evt.type === "gadget_result") {
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") {
4049
4676
  didExecuteGadgets = true;
4050
- if (evt.result.breaksLoop) {
4677
+ if (processedEvent.result.breaksLoop) {
4051
4678
  shouldBreakLoop = true;
4052
4679
  }
4053
4680
  }
4054
4681
  }
4055
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
+ }
4056
4710
  let finalMessage = this.responseText;
4057
4711
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4058
4712
  const context = {
@@ -4073,21 +4727,8 @@ var init_stream_processor = __esm({
4073
4727
  };
4074
4728
  yield completionEvent;
4075
4729
  }
4076
- /**
4077
- * Process a single parsed event (text or gadget call).
4078
- * @deprecated Use processEventGenerator for real-time streaming
4079
- */
4080
- async processEvent(event) {
4081
- if (event.type === "text") {
4082
- return this.processTextEvent(event);
4083
- } else if (event.type === "gadget_call") {
4084
- return this.processGadgetCall(event.call);
4085
- }
4086
- return [event];
4087
- }
4088
4730
  /**
4089
4731
  * Process a single parsed event, yielding events in real-time.
4090
- * Generator version of processEvent for streaming support.
4091
4732
  */
4092
4733
  async *processEventGenerator(event) {
4093
4734
  if (event.type === "text") {
@@ -4129,12 +4770,6 @@ var init_stream_processor = __esm({
4129
4770
  * After each execution, pending gadgets are checked to see if they can now run.
4130
4771
  */
4131
4772
  async processGadgetCall(call) {
4132
- if (this.executionHalted) {
4133
- this.logger.debug("Skipping gadget execution due to previous error", {
4134
- gadgetName: call.gadgetName
4135
- });
4136
- return [];
4137
- }
4138
4773
  const events = [];
4139
4774
  events.push({ type: "gadget_call", call });
4140
4775
  if (call.dependencies.length > 0) {
@@ -4185,13 +4820,16 @@ var init_stream_processor = __esm({
4185
4820
  * when parsed (before execution), enabling real-time UI feedback.
4186
4821
  */
4187
4822
  async *processGadgetCallGenerator(call) {
4188
- if (this.executionHalted) {
4189
- this.logger.debug("Skipping gadget execution due to previous error", {
4190
- 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
4191
4831
  });
4192
- return;
4193
4832
  }
4194
- yield { type: "gadget_call", call };
4195
4833
  if (call.dependencies.length > 0) {
4196
4834
  if (call.dependencies.includes(call.invocationId)) {
4197
4835
  this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
@@ -4236,17 +4874,8 @@ var init_stream_processor = __esm({
4236
4874
  }
4237
4875
  return;
4238
4876
  }
4239
- if (this.stopOnGadgetError) {
4240
- for await (const evt of this.executeGadgetGenerator(call)) {
4241
- yield evt;
4242
- }
4243
- for await (const evt of this.processPendingGadgetsGenerator()) {
4244
- yield evt;
4245
- }
4246
- } else {
4247
- const executionPromise = this.executeGadgetAndCollect(call);
4248
- this.inFlightExecutions.set(call.invocationId, executionPromise);
4249
- }
4877
+ const executionPromise = this.executeGadgetAndCollect(call);
4878
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4250
4879
  }
4251
4880
  /**
4252
4881
  * Execute a gadget through the full hook lifecycle.
@@ -4261,15 +4890,6 @@ var init_stream_processor = __esm({
4261
4890
  error: call.parseError,
4262
4891
  rawParameters: call.parametersRaw
4263
4892
  });
4264
- const shouldContinue = await this.checkCanRecoverFromError(
4265
- call.parseError,
4266
- call.gadgetName,
4267
- "parse",
4268
- call.parameters
4269
- );
4270
- if (!shouldContinue) {
4271
- this.executionHalted = true;
4272
- }
4273
4893
  }
4274
4894
  let parameters = call.parameters ?? {};
4275
4895
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4312,7 +4932,7 @@ var init_stream_processor = __esm({
4312
4932
  parameters,
4313
4933
  logger: this.logger
4314
4934
  };
4315
- await this.hooks.observers.onGadgetExecutionStart(context);
4935
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4316
4936
  });
4317
4937
  }
4318
4938
  await this.runObserversInParallel(startObservers);
@@ -4381,7 +5001,7 @@ var init_stream_processor = __esm({
4381
5001
  cost: result.cost,
4382
5002
  logger: this.logger
4383
5003
  };
4384
- await this.hooks.observers.onGadgetExecutionComplete(context);
5004
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4385
5005
  });
4386
5006
  }
4387
5007
  await this.runObserversInParallel(completeObservers);
@@ -4390,18 +5010,6 @@ var init_stream_processor = __esm({
4390
5010
  this.failedInvocations.add(result.invocationId);
4391
5011
  }
4392
5012
  events.push({ type: "gadget_result", result });
4393
- if (result.error) {
4394
- const errorType = this.determineErrorType(call, result);
4395
- const shouldContinue = await this.checkCanRecoverFromError(
4396
- result.error,
4397
- result.gadgetName,
4398
- errorType,
4399
- result.parameters
4400
- );
4401
- if (!shouldContinue) {
4402
- this.executionHalted = true;
4403
- }
4404
- }
4405
5013
  return events;
4406
5014
  }
4407
5015
  /**
@@ -4415,15 +5023,6 @@ var init_stream_processor = __esm({
4415
5023
  error: call.parseError,
4416
5024
  rawParameters: call.parametersRaw
4417
5025
  });
4418
- const shouldContinue = await this.checkCanRecoverFromError(
4419
- call.parseError,
4420
- call.gadgetName,
4421
- "parse",
4422
- call.parameters
4423
- );
4424
- if (!shouldContinue) {
4425
- this.executionHalted = true;
4426
- }
4427
5026
  }
4428
5027
  let parameters = call.parameters ?? {};
4429
5028
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4466,10 +5065,16 @@ var init_stream_processor = __esm({
4466
5065
  parameters,
4467
5066
  logger: this.logger
4468
5067
  };
4469
- await this.hooks.observers.onGadgetExecutionStart(context);
5068
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4470
5069
  });
4471
5070
  }
4472
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
+ }
4473
5078
  let result;
4474
5079
  if (shouldSkip) {
4475
5080
  result = {
@@ -4535,57 +5140,84 @@ var init_stream_processor = __esm({
4535
5140
  cost: result.cost,
4536
5141
  logger: this.logger
4537
5142
  };
4538
- await this.hooks.observers.onGadgetExecutionComplete(context);
5143
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4539
5144
  });
4540
5145
  }
4541
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
+ }
4542
5166
  this.completedResults.set(result.invocationId, result);
4543
5167
  if (result.error) {
4544
5168
  this.failedInvocations.add(result.invocationId);
4545
5169
  }
4546
5170
  yield { type: "gadget_result", result };
4547
- if (result.error) {
4548
- const errorType = this.determineErrorType(call, result);
4549
- const shouldContinue = await this.checkCanRecoverFromError(
4550
- result.error,
4551
- result.gadgetName,
4552
- errorType,
4553
- result.parameters
4554
- );
4555
- if (!shouldContinue) {
4556
- this.executionHalted = true;
4557
- }
4558
- }
4559
5171
  }
4560
5172
  /**
4561
- * Execute a gadget and collect all events into an array (non-blocking).
5173
+ * Execute a gadget and push events to the completed results queue (non-blocking).
4562
5174
  * Used for fire-and-forget parallel execution of independent gadgets.
5175
+ * Results are pushed to completedResultsQueue for real-time streaming to the caller.
4563
5176
  */
4564
5177
  async executeGadgetAndCollect(call) {
4565
- const events = [];
4566
5178
  for await (const evt of this.executeGadgetGenerator(call)) {
4567
- events.push(evt);
5179
+ this.completedResultsQueue.push(evt);
4568
5180
  }
4569
- return events;
4570
5181
  }
4571
5182
  /**
4572
- * Collect results from all fire-and-forget (in-flight) gadget executions.
4573
- * Called at stream end to await parallel independent gadgets.
4574
- * Clears the inFlightExecutions map after collection.
4575
- * @returns Array of all events from completed gadgets
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
4576
5186
  */
4577
- async collectInFlightResults() {
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() {
4578
5201
  if (this.inFlightExecutions.size === 0) {
4579
- return [];
5202
+ return;
4580
5203
  }
4581
- this.logger.debug("Collecting in-flight gadget results", {
5204
+ this.logger.debug("Waiting for in-flight gadget executions", {
4582
5205
  count: this.inFlightExecutions.size,
4583
5206
  invocationIds: Array.from(this.inFlightExecutions.keys())
4584
5207
  });
4585
- const promises = Array.from(this.inFlightExecutions.values());
4586
- const results = await Promise.all(promises);
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;
5218
+ }
5219
+ }
4587
5220
  this.inFlightExecutions.clear();
4588
- return results.flat();
4589
5221
  }
4590
5222
  /**
4591
5223
  * Handle a gadget that cannot execute because a dependency failed.
@@ -4610,6 +5242,12 @@ var init_stream_processor = __esm({
4610
5242
  }
4611
5243
  if (action.action === "skip") {
4612
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
+ }
4613
5251
  const skipEvent = {
4614
5252
  type: "gadget_skipped",
4615
5253
  gadgetName: call.gadgetName,
@@ -4629,7 +5267,7 @@ var init_stream_processor = __esm({
4629
5267
  failedDependencyError: depError,
4630
5268
  logger: this.logger
4631
5269
  };
4632
- await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(observeContext));
5270
+ await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
4633
5271
  }
4634
5272
  this.logger.info("Gadget skipped due to failed dependency", {
4635
5273
  gadgetName: call.gadgetName,
@@ -4861,48 +5499,6 @@ var init_stream_processor = __esm({
4861
5499
  observers.map((observer) => this.safeObserve(observer))
4862
5500
  );
4863
5501
  }
4864
- /**
4865
- * Check if execution can recover from an error.
4866
- *
4867
- * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4868
- *
4869
- * Logic:
4870
- * - If custom canRecoverFromGadgetError is provided, use it
4871
- * - Otherwise, use stopOnGadgetError config:
4872
- * - stopOnGadgetError=true → return false (stop execution)
4873
- * - stopOnGadgetError=false → return true (continue execution)
4874
- */
4875
- async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4876
- if (this.canRecoverFromGadgetError) {
4877
- return await this.canRecoverFromGadgetError({
4878
- error,
4879
- gadgetName,
4880
- errorType,
4881
- parameters
4882
- });
4883
- }
4884
- const shouldContinue = !this.stopOnGadgetError;
4885
- this.logger.debug("Checking if should continue after error", {
4886
- error,
4887
- gadgetName,
4888
- errorType,
4889
- stopOnGadgetError: this.stopOnGadgetError,
4890
- shouldContinue
4891
- });
4892
- return shouldContinue;
4893
- }
4894
- /**
4895
- * Determine the type of error from a gadget execution.
4896
- */
4897
- determineErrorType(call, result) {
4898
- if (call.parseError) {
4899
- return "parse";
4900
- }
4901
- if (result.error?.includes("Invalid parameters:")) {
4902
- return "validation";
4903
- }
4904
- return "execution";
4905
- }
4906
5502
  };
4907
5503
  }
4908
5504
  });
@@ -4913,6 +5509,7 @@ var init_agent = __esm({
4913
5509
  "src/agent/agent.ts"() {
4914
5510
  "use strict";
4915
5511
  init_constants();
5512
+ init_execution_tree();
4916
5513
  init_messages();
4917
5514
  init_model_shortcuts();
4918
5515
  init_media_store();
@@ -4940,8 +5537,6 @@ var init_agent = __esm({
4940
5537
  requestHumanInput;
4941
5538
  textOnlyHandler;
4942
5539
  textWithGadgetsHandler;
4943
- stopOnGadgetError;
4944
- canRecoverFromGadgetError;
4945
5540
  defaultGadgetTimeoutMs;
4946
5541
  defaultMaxTokens;
4947
5542
  hasUserPrompt;
@@ -4964,6 +5559,12 @@ var init_agent = __esm({
4964
5559
  pendingSubagentEvents = [];
4965
5560
  // Combined callback that queues events AND calls user callback
4966
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;
4967
5568
  /**
4968
5569
  * Creates a new Agent instance.
4969
5570
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4986,8 +5587,6 @@ var init_agent = __esm({
4986
5587
  this.requestHumanInput = options.requestHumanInput;
4987
5588
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4988
5589
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4989
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4990
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4991
5590
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4992
5591
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4993
5592
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -5041,6 +5640,9 @@ var init_agent = __esm({
5041
5640
  temperature: this.temperature
5042
5641
  };
5043
5642
  this.subagentConfig = options.subagentConfig;
5643
+ this.tree = options.parentTree ?? new ExecutionTree();
5644
+ this.parentNodeId = options.parentNodeId ?? null;
5645
+ this.baseDepth = options.baseDepth ?? 0;
5044
5646
  this.userSubagentEventCallback = options.onSubagentEvent;
5045
5647
  this.onSubagentEvent = (event) => {
5046
5648
  this.pendingSubagentEvents.push(event);
@@ -5105,7 +5707,9 @@ var init_agent = __esm({
5105
5707
  *flushPendingSubagentEvents() {
5106
5708
  while (this.pendingSubagentEvents.length > 0) {
5107
5709
  const event = this.pendingSubagentEvents.shift();
5108
- yield { type: "subagent_event", subagentEvent: event };
5710
+ if (event) {
5711
+ yield { type: "subagent_event", subagentEvent: event };
5712
+ }
5109
5713
  }
5110
5714
  }
5111
5715
  /**
@@ -5159,6 +5763,48 @@ var init_agent = __esm({
5159
5763
  getMediaStore() {
5160
5764
  return this.mediaStore;
5161
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
+ }
5162
5808
  /**
5163
5809
  * Manually trigger context compaction.
5164
5810
  *
@@ -5260,6 +5906,7 @@ var init_agent = __esm({
5260
5906
  await this.hooks.observers.onCompaction({
5261
5907
  iteration: currentIteration,
5262
5908
  event: compactionEvent,
5909
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5263
5910
  stats: this.compactionManager.getStats(),
5264
5911
  logger: this.logger
5265
5912
  });
@@ -5321,6 +5968,13 @@ var init_agent = __esm({
5321
5968
  messageCount: llmOptions.messages.length,
5322
5969
  messages: llmOptions.messages
5323
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;
5324
5978
  const stream2 = this.client.stream(llmOptions);
5325
5979
  const processor = new StreamProcessor({
5326
5980
  iteration: currentIteration,
@@ -5331,14 +5985,17 @@ var init_agent = __esm({
5331
5985
  hooks: this.hooks,
5332
5986
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
5333
5987
  requestHumanInput: this.requestHumanInput,
5334
- stopOnGadgetError: this.stopOnGadgetError,
5335
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5336
5988
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5337
5989
  client: this.client,
5338
5990
  mediaStore: this.mediaStore,
5339
5991
  agentConfig: this.agentContextConfig,
5340
5992
  subagentConfig: this.subagentConfig,
5341
- 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
5342
5999
  });
5343
6000
  let streamMetadata = null;
5344
6001
  let gadgetCallCount = 0;
@@ -5384,6 +6041,11 @@ var init_agent = __esm({
5384
6041
  await this.hooks.observers.onLLMCallComplete(context);
5385
6042
  }
5386
6043
  });
6044
+ this.tree.completeLLMCall(currentLLMNodeId, {
6045
+ response: result.rawResponse,
6046
+ usage: result.usage,
6047
+ finishReason: result.finishReason
6048
+ });
5387
6049
  let finalMessage = result.finalMessage;
5388
6050
  if (this.hooks.controllers?.afterLLMCall) {
5389
6051
  const context = {
@@ -5418,10 +6080,12 @@ var init_agent = __esm({
5418
6080
  const textContent = textOutputs.join("");
5419
6081
  if (textContent.trim()) {
5420
6082
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6083
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
5421
6084
  this.conversation.addGadgetCallResult(
5422
6085
  gadgetName,
5423
6086
  parameterMapping(textContent),
5424
- resultMapping ? resultMapping(textContent) : textContent
6087
+ resultMapping ? resultMapping(textContent) : textContent,
6088
+ syntheticId
5425
6089
  );
5426
6090
  }
5427
6091
  }
@@ -5432,6 +6096,7 @@ var init_agent = __esm({
5432
6096
  gadgetResult.gadgetName,
5433
6097
  gadgetResult.parameters,
5434
6098
  gadgetResult.error ?? gadgetResult.result ?? "",
6099
+ gadgetResult.invocationId,
5435
6100
  gadgetResult.media,
5436
6101
  gadgetResult.mediaIds
5437
6102
  );
@@ -5439,10 +6104,12 @@ var init_agent = __esm({
5439
6104
  }
5440
6105
  } else {
5441
6106
  if (finalMessage.trim()) {
6107
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
5442
6108
  this.conversation.addGadgetCallResult(
5443
6109
  "TellUser",
5444
6110
  { message: finalMessage, done: false, type: "info" },
5445
- `\u2139\uFE0F ${finalMessage}`
6111
+ `\u2139\uFE0F ${finalMessage}`,
6112
+ syntheticId
5446
6113
  );
5447
6114
  }
5448
6115
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
@@ -5653,8 +6320,6 @@ var init_builder = __esm({
5653
6320
  gadgetArgPrefix;
5654
6321
  textOnlyHandler;
5655
6322
  textWithGadgetsHandler;
5656
- stopOnGadgetError;
5657
- canRecoverFromGadgetError;
5658
6323
  defaultGadgetTimeoutMs;
5659
6324
  gadgetOutputLimit;
5660
6325
  gadgetOutputLimitPercent;
@@ -5663,6 +6328,8 @@ var init_builder = __esm({
5663
6328
  trailingMessage;
5664
6329
  subagentConfig;
5665
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
5666
6333
  parentContext;
5667
6334
  constructor(client) {
5668
6335
  this.client = client;
@@ -5945,62 +6612,6 @@ var init_builder = __esm({
5945
6612
  this.textWithGadgetsHandler = handler;
5946
6613
  return this;
5947
6614
  }
5948
- /**
5949
- * Set whether to stop gadget execution on first error.
5950
- *
5951
- * When true (default), if a gadget fails:
5952
- * - Subsequent gadgets in the same response are skipped
5953
- * - LLM stream is cancelled to save costs
5954
- * - Agent loop continues with error in context
5955
- *
5956
- * When false:
5957
- * - All gadgets in the response still execute
5958
- * - LLM stream continues to completion
5959
- *
5960
- * @param stop - Whether to stop on gadget error
5961
- * @returns This builder for chaining
5962
- *
5963
- * @example
5964
- * ```typescript
5965
- * .withStopOnGadgetError(false)
5966
- * ```
5967
- */
5968
- withStopOnGadgetError(stop) {
5969
- this.stopOnGadgetError = stop;
5970
- return this;
5971
- }
5972
- /**
5973
- * Set custom error handling logic.
5974
- *
5975
- * Provides fine-grained control over whether to continue after different types of errors.
5976
- * Overrides `stopOnGadgetError` when provided.
5977
- *
5978
- * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
5979
- * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
5980
- * but maps to the `canRecoverFromGadgetError` property internally.
5981
- *
5982
- * @param handler - Function that decides whether to continue after an error.
5983
- * Return `true` to continue execution, `false` to stop.
5984
- * @returns This builder for chaining
5985
- *
5986
- * @example
5987
- * ```typescript
5988
- * .withErrorHandler((context) => {
5989
- * // Stop on parse errors, continue on validation/execution errors
5990
- * if (context.errorType === "parse") {
5991
- * return false;
5992
- * }
5993
- * if (context.error.includes("CRITICAL")) {
5994
- * return false;
5995
- * }
5996
- * return true;
5997
- * })
5998
- * ```
5999
- */
6000
- withErrorHandler(handler) {
6001
- this.canRecoverFromGadgetError = handler;
6002
- return this;
6003
- }
6004
6615
  /**
6005
6616
  * Set default timeout for gadget execution.
6006
6617
  *
@@ -6195,6 +6806,15 @@ var init_builder = __esm({
6195
6806
  * The method extracts `invocationId` and `onSubagentEvent` from the execution
6196
6807
  * context and sets up automatic forwarding via hooks and event wrapping.
6197
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
+ *
6198
6818
  * @param ctx - ExecutionContext passed to the gadget's execute() method
6199
6819
  * @param depth - Nesting depth (default: 1 for direct child)
6200
6820
  * @returns This builder for chaining
@@ -6215,17 +6835,25 @@ var init_builder = __esm({
6215
6835
  * result = event.content;
6216
6836
  * }
6217
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!);
6218
6843
  * }
6219
6844
  * ```
6220
6845
  */
6221
6846
  withParentContext(ctx, depth = 1) {
6222
- if (ctx.onSubagentEvent && ctx.invocationId) {
6847
+ if (ctx.tree) {
6223
6848
  this.parentContext = {
6224
- invocationId: ctx.invocationId,
6225
- onSubagentEvent: ctx.onSubagentEvent,
6849
+ tree: ctx.tree,
6850
+ nodeId: ctx.nodeId,
6226
6851
  depth
6227
6852
  };
6228
6853
  }
6854
+ if (ctx.signal && !this.signal) {
6855
+ this.signal = ctx.signal;
6856
+ }
6229
6857
  return this;
6230
6858
  }
6231
6859
  /**
@@ -6258,11 +6886,13 @@ var init_builder = __esm({
6258
6886
  *
6259
6887
  * This is useful for in-context learning - showing the LLM what "past self"
6260
6888
  * did correctly so it mimics the pattern. The call is formatted with proper
6261
- * markers and parameter format.
6889
+ * markers and parameter format, including the invocation ID so the LLM can
6890
+ * reference previous calls when building dependencies.
6262
6891
  *
6263
6892
  * @param gadgetName - Name of the gadget
6264
6893
  * @param parameters - Parameters passed to the gadget
6265
6894
  * @param result - Result returned by the gadget
6895
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
6266
6896
  * @returns This builder for chaining
6267
6897
  *
6268
6898
  * @example
@@ -6274,124 +6904,36 @@ var init_builder = __esm({
6274
6904
  * done: false,
6275
6905
  * type: 'info'
6276
6906
  * },
6277
- * 'ℹ️ 👋 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'
6278
6909
  * )
6279
6910
  * ```
6280
6911
  */
6281
- withSyntheticGadgetCall(gadgetName, parameters, result) {
6912
+ withSyntheticGadgetCall(gadgetName, parameters, result, invocationId) {
6282
6913
  const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
6283
6914
  const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
6284
6915
  const paramStr = this.formatBlockParameters(parameters, "");
6285
6916
  this.initialMessages.push({
6286
6917
  role: "assistant",
6287
- content: `${startPrefix}${gadgetName}
6918
+ content: `${startPrefix}${gadgetName}:${invocationId}
6288
6919
  ${paramStr}
6289
6920
  ${endPrefix}`
6290
6921
  });
6291
6922
  this.initialMessages.push({
6292
6923
  role: "user",
6293
- content: `Result: ${result}`
6924
+ content: `Result (${invocationId}): ${result}`
6294
6925
  });
6295
6926
  return this;
6296
6927
  }
6297
6928
  /**
6298
- * Compose the final hooks, including:
6299
- * - Trailing message injection (if configured)
6300
- * - 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).
6301
6934
  */
6302
6935
  composeHooks() {
6303
- let hooks = this.hooks;
6304
- if (this.parentContext) {
6305
- const { invocationId, onSubagentEvent, depth } = this.parentContext;
6306
- const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
6307
- const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
6308
- const existingOnGadgetExecutionStart = hooks?.observers?.onGadgetExecutionStart;
6309
- const existingOnGadgetExecutionComplete = hooks?.observers?.onGadgetExecutionComplete;
6310
- hooks = {
6311
- ...hooks,
6312
- observers: {
6313
- ...hooks?.observers,
6314
- onLLMCallStart: async (context) => {
6315
- let inputTokens;
6316
- try {
6317
- if (this.client) {
6318
- inputTokens = await this.client.countTokens(
6319
- context.options.model,
6320
- context.options.messages
6321
- );
6322
- }
6323
- } catch {
6324
- }
6325
- onSubagentEvent({
6326
- type: "llm_call_start",
6327
- gadgetInvocationId: invocationId,
6328
- depth,
6329
- event: {
6330
- iteration: context.iteration,
6331
- model: context.options.model,
6332
- inputTokens
6333
- }
6334
- });
6335
- if (existingOnLLMCallStart) {
6336
- await existingOnLLMCallStart(context);
6337
- }
6338
- },
6339
- onLLMCallComplete: async (context) => {
6340
- onSubagentEvent({
6341
- type: "llm_call_end",
6342
- gadgetInvocationId: invocationId,
6343
- depth,
6344
- event: {
6345
- iteration: context.iteration,
6346
- model: context.options.model,
6347
- // Backward compat fields
6348
- inputTokens: context.usage?.inputTokens,
6349
- outputTokens: context.usage?.outputTokens,
6350
- finishReason: context.finishReason ?? void 0,
6351
- // Full usage object with cache details (for first-class display)
6352
- usage: context.usage
6353
- // Cost will be calculated by parent if it has model registry
6354
- }
6355
- });
6356
- if (existingOnLLMCallComplete) {
6357
- await existingOnLLMCallComplete(context);
6358
- }
6359
- },
6360
- onGadgetExecutionStart: async (context) => {
6361
- onSubagentEvent({
6362
- type: "gadget_call",
6363
- gadgetInvocationId: invocationId,
6364
- depth,
6365
- event: {
6366
- call: {
6367
- invocationId: context.invocationId,
6368
- gadgetName: context.gadgetName,
6369
- parameters: context.parameters
6370
- }
6371
- }
6372
- });
6373
- if (existingOnGadgetExecutionStart) {
6374
- await existingOnGadgetExecutionStart(context);
6375
- }
6376
- },
6377
- onGadgetExecutionComplete: async (context) => {
6378
- onSubagentEvent({
6379
- type: "gadget_result",
6380
- gadgetInvocationId: invocationId,
6381
- depth,
6382
- event: {
6383
- result: {
6384
- invocationId: context.invocationId
6385
- }
6386
- }
6387
- });
6388
- if (existingOnGadgetExecutionComplete) {
6389
- await existingOnGadgetExecutionComplete(context);
6390
- }
6391
- }
6392
- }
6393
- };
6394
- }
6936
+ const hooks = this.hooks;
6395
6937
  if (!this.trailingMessage) {
6396
6938
  return hooks;
6397
6939
  }
@@ -6474,19 +7016,6 @@ ${endPrefix}`
6474
7016
  this.client = new LLMistClass();
6475
7017
  }
6476
7018
  const registry = GadgetRegistry.from(this.gadgets);
6477
- let onSubagentEvent = this.subagentEventCallback;
6478
- if (this.parentContext) {
6479
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6480
- const existingCallback = this.subagentEventCallback;
6481
- onSubagentEvent = (event) => {
6482
- parentCallback({
6483
- ...event,
6484
- gadgetInvocationId: invocationId,
6485
- depth: event.depth + depth
6486
- });
6487
- existingCallback?.(event);
6488
- };
6489
- }
6490
7019
  return {
6491
7020
  client: this.client,
6492
7021
  model: this.model ?? "openai:gpt-5-nano",
@@ -6505,15 +7034,17 @@ ${endPrefix}`
6505
7034
  gadgetArgPrefix: this.gadgetArgPrefix,
6506
7035
  textOnlyHandler: this.textOnlyHandler,
6507
7036
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6508
- stopOnGadgetError: this.stopOnGadgetError,
6509
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6510
7037
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6511
7038
  gadgetOutputLimit: this.gadgetOutputLimit,
6512
7039
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6513
7040
  compactionConfig: this.compactionConfig,
6514
7041
  signal: this.signal,
6515
7042
  subagentConfig: this.subagentConfig,
6516
- 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
6517
7048
  };
6518
7049
  }
6519
7050
  ask(userPrompt) {
@@ -6670,19 +7201,6 @@ ${endPrefix}`
6670
7201
  this.client = new LLMistClass();
6671
7202
  }
6672
7203
  const registry = GadgetRegistry.from(this.gadgets);
6673
- let onSubagentEvent = this.subagentEventCallback;
6674
- if (this.parentContext) {
6675
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
6676
- const existingCallback = this.subagentEventCallback;
6677
- onSubagentEvent = (event) => {
6678
- parentCallback({
6679
- ...event,
6680
- gadgetInvocationId: invocationId,
6681
- depth: event.depth + depth
6682
- });
6683
- existingCallback?.(event);
6684
- };
6685
- }
6686
7204
  const options = {
6687
7205
  client: this.client,
6688
7206
  model: this.model ?? "openai:gpt-5-nano",
@@ -6701,15 +7219,17 @@ ${endPrefix}`
6701
7219
  gadgetArgPrefix: this.gadgetArgPrefix,
6702
7220
  textOnlyHandler: this.textOnlyHandler,
6703
7221
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6704
- stopOnGadgetError: this.stopOnGadgetError,
6705
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6706
7222
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6707
7223
  gadgetOutputLimit: this.gadgetOutputLimit,
6708
7224
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6709
7225
  compactionConfig: this.compactionConfig,
6710
7226
  signal: this.signal,
6711
7227
  subagentConfig: this.subagentConfig,
6712
- 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
6713
7233
  };
6714
7234
  return new Agent(AGENT_INTERNAL_KEY, options);
6715
7235
  }
@@ -11297,16 +11817,16 @@ var MockConversationManager = class {
11297
11817
  this.history.push(msg);
11298
11818
  this.addedMessages.push(msg);
11299
11819
  }
11300
- addGadgetCallResult(gadgetName, parameters, result) {
11820
+ addGadgetCallResult(gadgetName, parameters, result, invocationId) {
11301
11821
  const assistantMsg = {
11302
11822
  role: "assistant",
11303
- content: `!!!GADGET_START:${gadgetName}
11823
+ content: `!!!GADGET_START:${gadgetName}:${invocationId}
11304
11824
  ${JSON.stringify(parameters)}
11305
11825
  !!!GADGET_END`
11306
11826
  };
11307
11827
  const resultMsg = {
11308
11828
  role: "user",
11309
- content: `Result: ${result}`
11829
+ content: `Result (${invocationId}): ${result}`
11310
11830
  };
11311
11831
  this.history.push(assistantMsg);
11312
11832
  this.history.push(resultMsg);
@@ -11654,6 +12174,8 @@ export {
11654
12174
  init_schema_validator,
11655
12175
  GadgetRegistry,
11656
12176
  init_registry,
12177
+ ExecutionTree,
12178
+ init_execution_tree,
11657
12179
  DEFAULT_HINTS,
11658
12180
  DEFAULT_PROMPTS,
11659
12181
  resolvePromptTemplate,
@@ -11772,4 +12294,4 @@ export {
11772
12294
  createEmptyStream,
11773
12295
  createErrorStream
11774
12296
  };
11775
- //# sourceMappingURL=chunk-EIE5VRSI.js.map
12297
+ //# sourceMappingURL=chunk-VAJLPRJ6.js.map