llmist 6.0.0 → 6.2.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()];
@@ -3536,20 +4144,65 @@ var init_parser = __esm({
3536
4144
  }
3537
4145
  });
3538
4146
 
4147
+ // src/gadgets/typed-gadget.ts
4148
+ function Gadget(config) {
4149
+ class GadgetBase extends AbstractGadget {
4150
+ description = config.description;
4151
+ parameterSchema = config.schema;
4152
+ name = config.name;
4153
+ timeoutMs = config.timeoutMs;
4154
+ examples = config.examples;
4155
+ /**
4156
+ * Type helper property for accessing inferred parameter type.
4157
+ * This is used in the execute method signature: `execute(params: this['params'])`
4158
+ *
4159
+ * Note: This is just for type inference - the actual params in execute()
4160
+ * will be Record<string, unknown> which you can safely cast to this['params']
4161
+ */
4162
+ params;
4163
+ }
4164
+ return GadgetBase;
4165
+ }
4166
+ var init_typed_gadget = __esm({
4167
+ "src/gadgets/typed-gadget.ts"() {
4168
+ "use strict";
4169
+ init_gadget();
4170
+ }
4171
+ });
4172
+
3539
4173
  // src/gadgets/executor.ts
3540
- var GadgetExecutor;
4174
+ import { z as z4 } from "zod";
4175
+ function getHostExportsInternal() {
4176
+ if (!cachedHostExports) {
4177
+ cachedHostExports = {
4178
+ AgentBuilder,
4179
+ Gadget,
4180
+ createGadget,
4181
+ ExecutionTree,
4182
+ LLMist,
4183
+ z: z4
4184
+ };
4185
+ }
4186
+ return cachedHostExports;
4187
+ }
4188
+ var cachedHostExports, GadgetExecutor;
3541
4189
  var init_executor = __esm({
3542
4190
  "src/gadgets/executor.ts"() {
3543
4191
  "use strict";
4192
+ init_builder();
4193
+ init_client();
3544
4194
  init_constants();
4195
+ init_execution_tree();
3545
4196
  init_logger();
3546
4197
  init_block_params();
3547
4198
  init_cost_reporting_client();
4199
+ init_create_gadget();
3548
4200
  init_error_formatter();
3549
4201
  init_exceptions();
3550
4202
  init_parser();
4203
+ init_typed_gadget();
3551
4204
  GadgetExecutor = class {
3552
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent) {
4205
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
3553
4206
  this.registry = registry;
3554
4207
  this.requestHumanInput = requestHumanInput;
3555
4208
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -3558,6 +4211,9 @@ var init_executor = __esm({
3558
4211
  this.agentConfig = agentConfig;
3559
4212
  this.subagentConfig = subagentConfig;
3560
4213
  this.onSubagentEvent = onSubagentEvent;
4214
+ this.tree = tree;
4215
+ this.parentNodeId = parentNodeId;
4216
+ this.baseDepth = baseDepth;
3561
4217
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3562
4218
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3563
4219
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3568,15 +4224,21 @@ var init_executor = __esm({
3568
4224
  /**
3569
4225
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
3570
4226
  * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
4227
+ * Returns both the promise and a cancel function to clear the timeout when no longer needed.
3571
4228
  */
3572
4229
  createTimeoutPromise(gadgetName, timeoutMs, abortController) {
3573
- return new Promise((_, reject) => {
3574
- setTimeout(() => {
4230
+ let timeoutId;
4231
+ const promise = new Promise((_, reject) => {
4232
+ timeoutId = setTimeout(() => {
3575
4233
  const timeoutError = new TimeoutException(gadgetName, timeoutMs);
3576
4234
  abortController.abort(timeoutError.message);
3577
4235
  reject(timeoutError);
3578
4236
  }, timeoutMs);
3579
4237
  });
4238
+ return {
4239
+ promise,
4240
+ cancel: () => clearTimeout(timeoutId)
4241
+ };
3580
4242
  }
3581
4243
  /**
3582
4244
  * Unify gadget execute result to consistent internal format.
@@ -3698,6 +4360,8 @@ var init_executor = __esm({
3698
4360
  });
3699
4361
  }
3700
4362
  };
4363
+ const gadgetNodeId = this.tree?.getNodeByInvocationId(call.invocationId)?.id;
4364
+ const gadgetDepth = gadgetNodeId ? this.tree?.getNode(gadgetNodeId)?.depth ?? this.baseDepth : this.baseDepth;
3701
4365
  const ctx = {
3702
4366
  reportCost,
3703
4367
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
@@ -3705,7 +4369,13 @@ var init_executor = __esm({
3705
4369
  agentConfig: this.agentConfig,
3706
4370
  subagentConfig: this.subagentConfig,
3707
4371
  invocationId: call.invocationId,
3708
- onSubagentEvent: this.onSubagentEvent
4372
+ onSubagentEvent: this.onSubagentEvent,
4373
+ // Tree context for subagent support - use gadget's own node ID
4374
+ tree: this.tree,
4375
+ nodeId: gadgetNodeId,
4376
+ depth: gadgetDepth,
4377
+ // Host exports for external gadgets to use host's llmist classes
4378
+ hostExports: getHostExportsInternal()
3709
4379
  };
3710
4380
  let rawResult;
3711
4381
  if (timeoutMs && timeoutMs > 0) {
@@ -3713,10 +4383,15 @@ var init_executor = __esm({
3713
4383
  gadgetName: call.gadgetName,
3714
4384
  timeoutMs
3715
4385
  });
3716
- rawResult = await Promise.race([
3717
- Promise.resolve(gadget.execute(validatedParameters, ctx)),
3718
- this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
3719
- ]);
4386
+ const timeout = this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController);
4387
+ try {
4388
+ rawResult = await Promise.race([
4389
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
4390
+ timeout.promise
4391
+ ]);
4392
+ } finally {
4393
+ timeout.cancel();
4394
+ }
3720
4395
  } else {
3721
4396
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3722
4397
  }
@@ -3911,10 +4586,11 @@ var init_stream_processor = __esm({
3911
4586
  logger;
3912
4587
  parser;
3913
4588
  executor;
3914
- stopOnGadgetError;
3915
- canRecoverFromGadgetError;
4589
+ // Execution Tree context
4590
+ tree;
4591
+ parentNodeId;
4592
+ baseDepth;
3916
4593
  responseText = "";
3917
- executionHalted = false;
3918
4594
  observerFailureCount = 0;
3919
4595
  // Dependency tracking for gadget execution DAG
3920
4596
  /** Gadgets waiting for their dependencies to complete */
@@ -3925,18 +4601,28 @@ var init_stream_processor = __esm({
3925
4601
  failedInvocations = /* @__PURE__ */ new Set();
3926
4602
  /** Promises for independent gadgets currently executing (fire-and-forget) */
3927
4603
  inFlightExecutions = /* @__PURE__ */ new Map();
4604
+ /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4605
+ completedResultsQueue = [];
3928
4606
  constructor(options) {
3929
4607
  this.iteration = options.iteration;
3930
4608
  this.registry = options.registry;
3931
4609
  this.hooks = options.hooks ?? {};
3932
4610
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3933
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3934
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4611
+ this.tree = options.tree;
4612
+ this.parentNodeId = options.parentNodeId ?? null;
4613
+ this.baseDepth = options.baseDepth ?? 0;
3935
4614
  this.parser = new GadgetCallParser({
3936
4615
  startPrefix: options.gadgetStartPrefix,
3937
4616
  endPrefix: options.gadgetEndPrefix,
3938
4617
  argPrefix: options.gadgetArgPrefix
3939
4618
  });
4619
+ const wrappedOnSubagentEvent = options.onSubagentEvent ? (event) => {
4620
+ this.completedResultsQueue.push({
4621
+ type: "subagent_event",
4622
+ subagentEvent: event
4623
+ });
4624
+ options.onSubagentEvent?.(event);
4625
+ } : void 0;
3940
4626
  this.executor = new GadgetExecutor(
3941
4627
  options.registry,
3942
4628
  options.requestHumanInput,
@@ -3947,7 +4633,11 @@ var init_stream_processor = __esm({
3947
4633
  options.mediaStore,
3948
4634
  options.agentConfig,
3949
4635
  options.subagentConfig,
3950
- options.onSubagentEvent
4636
+ wrappedOnSubagentEvent,
4637
+ // Tree context for gadget execution
4638
+ options.tree,
4639
+ options.parentNodeId,
4640
+ options.baseDepth
3951
4641
  );
3952
4642
  }
3953
4643
  /**
@@ -3998,7 +4688,7 @@ var init_stream_processor = __esm({
3998
4688
  usage,
3999
4689
  logger: this.logger
4000
4690
  };
4001
- await this.hooks.observers.onStreamChunk(context);
4691
+ await this.hooks.observers?.onStreamChunk?.(context);
4002
4692
  });
4003
4693
  await this.runObserversInParallel(chunkObservers);
4004
4694
  }
@@ -4016,25 +4706,7 @@ var init_stream_processor = __esm({
4016
4706
  }
4017
4707
  }
4018
4708
  }
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) {
4709
+ for (const evt of this.drainCompletedResults()) {
4038
4710
  yield evt;
4039
4711
  if (evt.type === "gadget_result") {
4040
4712
  didExecuteGadgets = true;
@@ -4043,16 +4715,45 @@ var init_stream_processor = __esm({
4043
4715
  }
4044
4716
  }
4045
4717
  }
4046
- for await (const evt of this.processPendingGadgetsGenerator()) {
4047
- yield evt;
4048
- if (evt.type === "gadget_result") {
4718
+ }
4719
+ for (const event of this.parser.finalize()) {
4720
+ for await (const processedEvent of this.processEventGenerator(event)) {
4721
+ yield processedEvent;
4722
+ if (processedEvent.type === "gadget_result") {
4049
4723
  didExecuteGadgets = true;
4050
- if (evt.result.breaksLoop) {
4724
+ if (processedEvent.result.breaksLoop) {
4051
4725
  shouldBreakLoop = true;
4052
4726
  }
4053
4727
  }
4054
4728
  }
4055
4729
  }
4730
+ for await (const evt of this.waitForInFlightExecutions()) {
4731
+ yield evt;
4732
+ if (evt.type === "gadget_result") {
4733
+ didExecuteGadgets = true;
4734
+ if (evt.result.breaksLoop) {
4735
+ shouldBreakLoop = true;
4736
+ }
4737
+ }
4738
+ }
4739
+ for (const evt of this.drainCompletedResults()) {
4740
+ yield evt;
4741
+ if (evt.type === "gadget_result") {
4742
+ didExecuteGadgets = true;
4743
+ if (evt.result.breaksLoop) {
4744
+ shouldBreakLoop = true;
4745
+ }
4746
+ }
4747
+ }
4748
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4749
+ yield evt;
4750
+ if (evt.type === "gadget_result") {
4751
+ didExecuteGadgets = true;
4752
+ if (evt.result.breaksLoop) {
4753
+ shouldBreakLoop = true;
4754
+ }
4755
+ }
4756
+ }
4056
4757
  let finalMessage = this.responseText;
4057
4758
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4058
4759
  const context = {
@@ -4073,21 +4774,8 @@ var init_stream_processor = __esm({
4073
4774
  };
4074
4775
  yield completionEvent;
4075
4776
  }
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
4777
  /**
4089
4778
  * Process a single parsed event, yielding events in real-time.
4090
- * Generator version of processEvent for streaming support.
4091
4779
  */
4092
4780
  async *processEventGenerator(event) {
4093
4781
  if (event.type === "text") {
@@ -4129,12 +4817,6 @@ var init_stream_processor = __esm({
4129
4817
  * After each execution, pending gadgets are checked to see if they can now run.
4130
4818
  */
4131
4819
  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
4820
  const events = [];
4139
4821
  events.push({ type: "gadget_call", call });
4140
4822
  if (call.dependencies.length > 0) {
@@ -4185,13 +4867,16 @@ var init_stream_processor = __esm({
4185
4867
  * when parsed (before execution), enabling real-time UI feedback.
4186
4868
  */
4187
4869
  async *processGadgetCallGenerator(call) {
4188
- if (this.executionHalted) {
4189
- this.logger.debug("Skipping gadget execution due to previous error", {
4190
- gadgetName: call.gadgetName
4870
+ yield { type: "gadget_call", call };
4871
+ if (this.tree) {
4872
+ this.tree.addGadget({
4873
+ invocationId: call.invocationId,
4874
+ name: call.gadgetName,
4875
+ parameters: call.parameters ?? {},
4876
+ dependencies: call.dependencies,
4877
+ parentId: this.parentNodeId
4191
4878
  });
4192
- return;
4193
4879
  }
4194
- yield { type: "gadget_call", call };
4195
4880
  if (call.dependencies.length > 0) {
4196
4881
  if (call.dependencies.includes(call.invocationId)) {
4197
4882
  this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
@@ -4236,17 +4921,8 @@ var init_stream_processor = __esm({
4236
4921
  }
4237
4922
  return;
4238
4923
  }
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
- }
4924
+ const executionPromise = this.executeGadgetAndCollect(call);
4925
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4250
4926
  }
4251
4927
  /**
4252
4928
  * Execute a gadget through the full hook lifecycle.
@@ -4261,15 +4937,6 @@ var init_stream_processor = __esm({
4261
4937
  error: call.parseError,
4262
4938
  rawParameters: call.parametersRaw
4263
4939
  });
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
4940
  }
4274
4941
  let parameters = call.parameters ?? {};
4275
4942
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4312,7 +4979,7 @@ var init_stream_processor = __esm({
4312
4979
  parameters,
4313
4980
  logger: this.logger
4314
4981
  };
4315
- await this.hooks.observers.onGadgetExecutionStart(context);
4982
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4316
4983
  });
4317
4984
  }
4318
4985
  await this.runObserversInParallel(startObservers);
@@ -4381,7 +5048,7 @@ var init_stream_processor = __esm({
4381
5048
  cost: result.cost,
4382
5049
  logger: this.logger
4383
5050
  };
4384
- await this.hooks.observers.onGadgetExecutionComplete(context);
5051
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4385
5052
  });
4386
5053
  }
4387
5054
  await this.runObserversInParallel(completeObservers);
@@ -4390,18 +5057,6 @@ var init_stream_processor = __esm({
4390
5057
  this.failedInvocations.add(result.invocationId);
4391
5058
  }
4392
5059
  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
5060
  return events;
4406
5061
  }
4407
5062
  /**
@@ -4415,15 +5070,6 @@ var init_stream_processor = __esm({
4415
5070
  error: call.parseError,
4416
5071
  rawParameters: call.parametersRaw
4417
5072
  });
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
5073
  }
4428
5074
  let parameters = call.parameters ?? {};
4429
5075
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4466,10 +5112,16 @@ var init_stream_processor = __esm({
4466
5112
  parameters,
4467
5113
  logger: this.logger
4468
5114
  };
4469
- await this.hooks.observers.onGadgetExecutionStart(context);
5115
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4470
5116
  });
4471
5117
  }
4472
5118
  await this.runObserversInParallel(startObservers);
5119
+ if (this.tree) {
5120
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5121
+ if (gadgetNode) {
5122
+ this.tree.startGadget(gadgetNode.id);
5123
+ }
5124
+ }
4473
5125
  let result;
4474
5126
  if (shouldSkip) {
4475
5127
  result = {
@@ -4535,57 +5187,84 @@ var init_stream_processor = __esm({
4535
5187
  cost: result.cost,
4536
5188
  logger: this.logger
4537
5189
  };
4538
- await this.hooks.observers.onGadgetExecutionComplete(context);
5190
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4539
5191
  });
4540
5192
  }
4541
5193
  await this.runObserversInParallel(completeObservers);
5194
+ if (this.tree) {
5195
+ const gadgetNode = this.tree.getNodeByInvocationId(result.invocationId);
5196
+ if (gadgetNode) {
5197
+ if (result.error) {
5198
+ this.tree.completeGadget(gadgetNode.id, {
5199
+ error: result.error,
5200
+ executionTimeMs: result.executionTimeMs,
5201
+ cost: result.cost
5202
+ });
5203
+ } else {
5204
+ this.tree.completeGadget(gadgetNode.id, {
5205
+ result: result.result,
5206
+ executionTimeMs: result.executionTimeMs,
5207
+ cost: result.cost,
5208
+ media: result.media
5209
+ });
5210
+ }
5211
+ }
5212
+ }
4542
5213
  this.completedResults.set(result.invocationId, result);
4543
5214
  if (result.error) {
4544
5215
  this.failedInvocations.add(result.invocationId);
4545
5216
  }
4546
5217
  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
5218
  }
4560
5219
  /**
4561
- * Execute a gadget and collect all events into an array (non-blocking).
5220
+ * Execute a gadget and push events to the completed results queue (non-blocking).
4562
5221
  * Used for fire-and-forget parallel execution of independent gadgets.
5222
+ * Results are pushed to completedResultsQueue for real-time streaming to the caller.
4563
5223
  */
4564
5224
  async executeGadgetAndCollect(call) {
4565
- const events = [];
4566
5225
  for await (const evt of this.executeGadgetGenerator(call)) {
4567
- events.push(evt);
5226
+ this.completedResultsQueue.push(evt);
5227
+ }
5228
+ }
5229
+ /**
5230
+ * Drain all completed results from the queue.
5231
+ * Used to yield results as they complete during stream processing.
5232
+ * @returns Generator that yields all events currently in the queue
5233
+ */
5234
+ *drainCompletedResults() {
5235
+ while (this.completedResultsQueue.length > 0) {
5236
+ yield this.completedResultsQueue.shift();
4568
5237
  }
4569
- return events;
4570
5238
  }
4571
5239
  /**
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
5240
+ * Wait for all in-flight gadget executions to complete, yielding events in real-time.
5241
+ * Called at stream end to ensure all parallel executions finish.
5242
+ * Results and subagent events are pushed to completedResultsQueue during execution.
5243
+ * This generator yields queued events while polling, enabling real-time display
5244
+ * of subagent activity (LLM calls, nested gadgets) during long-running gadgets.
5245
+ * Clears the inFlightExecutions map after all gadgets complete.
4576
5246
  */
4577
- async collectInFlightResults() {
5247
+ async *waitForInFlightExecutions() {
4578
5248
  if (this.inFlightExecutions.size === 0) {
4579
- return [];
5249
+ return;
4580
5250
  }
4581
- this.logger.debug("Collecting in-flight gadget results", {
5251
+ this.logger.debug("Waiting for in-flight gadget executions", {
4582
5252
  count: this.inFlightExecutions.size,
4583
5253
  invocationIds: Array.from(this.inFlightExecutions.keys())
4584
5254
  });
4585
- const promises = Array.from(this.inFlightExecutions.values());
4586
- const results = await Promise.all(promises);
5255
+ const allDone = Promise.all(this.inFlightExecutions.values()).then(() => "done");
5256
+ const POLL_INTERVAL_MS = 100;
5257
+ while (true) {
5258
+ const result = await Promise.race([
5259
+ allDone,
5260
+ new Promise((resolve) => setTimeout(() => resolve("poll"), POLL_INTERVAL_MS))
5261
+ ]);
5262
+ yield* this.drainCompletedResults();
5263
+ if (result === "done") {
5264
+ break;
5265
+ }
5266
+ }
4587
5267
  this.inFlightExecutions.clear();
4588
- return results.flat();
4589
5268
  }
4590
5269
  /**
4591
5270
  * Handle a gadget that cannot execute because a dependency failed.
@@ -4610,6 +5289,12 @@ var init_stream_processor = __esm({
4610
5289
  }
4611
5290
  if (action.action === "skip") {
4612
5291
  this.failedInvocations.add(call.invocationId);
5292
+ if (this.tree) {
5293
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5294
+ if (gadgetNode) {
5295
+ this.tree.skipGadget(gadgetNode.id, failedDep, depError, "dependency_failed");
5296
+ }
5297
+ }
4613
5298
  const skipEvent = {
4614
5299
  type: "gadget_skipped",
4615
5300
  gadgetName: call.gadgetName,
@@ -4629,7 +5314,7 @@ var init_stream_processor = __esm({
4629
5314
  failedDependencyError: depError,
4630
5315
  logger: this.logger
4631
5316
  };
4632
- await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(observeContext));
5317
+ await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
4633
5318
  }
4634
5319
  this.logger.info("Gadget skipped due to failed dependency", {
4635
5320
  gadgetName: call.gadgetName,
@@ -4861,48 +5546,6 @@ var init_stream_processor = __esm({
4861
5546
  observers.map((observer) => this.safeObserve(observer))
4862
5547
  );
4863
5548
  }
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
5549
  };
4907
5550
  }
4908
5551
  });
@@ -4913,6 +5556,7 @@ var init_agent = __esm({
4913
5556
  "src/agent/agent.ts"() {
4914
5557
  "use strict";
4915
5558
  init_constants();
5559
+ init_execution_tree();
4916
5560
  init_messages();
4917
5561
  init_model_shortcuts();
4918
5562
  init_media_store();
@@ -4940,8 +5584,6 @@ var init_agent = __esm({
4940
5584
  requestHumanInput;
4941
5585
  textOnlyHandler;
4942
5586
  textWithGadgetsHandler;
4943
- stopOnGadgetError;
4944
- canRecoverFromGadgetError;
4945
5587
  defaultGadgetTimeoutMs;
4946
5588
  defaultMaxTokens;
4947
5589
  hasUserPrompt;
@@ -4964,6 +5606,12 @@ var init_agent = __esm({
4964
5606
  pendingSubagentEvents = [];
4965
5607
  // Combined callback that queues events AND calls user callback
4966
5608
  onSubagentEvent;
5609
+ // Counter for generating synthetic invocation IDs for wrapped text content
5610
+ syntheticInvocationCounter = 0;
5611
+ // Execution Tree - first-class model for nested subagent support
5612
+ tree;
5613
+ parentNodeId;
5614
+ baseDepth;
4967
5615
  /**
4968
5616
  * Creates a new Agent instance.
4969
5617
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4986,8 +5634,6 @@ var init_agent = __esm({
4986
5634
  this.requestHumanInput = options.requestHumanInput;
4987
5635
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
4988
5636
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
4989
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
4990
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4991
5637
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
4992
5638
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
4993
5639
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -5041,6 +5687,9 @@ var init_agent = __esm({
5041
5687
  temperature: this.temperature
5042
5688
  };
5043
5689
  this.subagentConfig = options.subagentConfig;
5690
+ this.tree = options.parentTree ?? new ExecutionTree();
5691
+ this.parentNodeId = options.parentNodeId ?? null;
5692
+ this.baseDepth = options.baseDepth ?? 0;
5044
5693
  this.userSubagentEventCallback = options.onSubagentEvent;
5045
5694
  this.onSubagentEvent = (event) => {
5046
5695
  this.pendingSubagentEvents.push(event);
@@ -5105,7 +5754,9 @@ var init_agent = __esm({
5105
5754
  *flushPendingSubagentEvents() {
5106
5755
  while (this.pendingSubagentEvents.length > 0) {
5107
5756
  const event = this.pendingSubagentEvents.shift();
5108
- yield { type: "subagent_event", subagentEvent: event };
5757
+ if (event) {
5758
+ yield { type: "subagent_event", subagentEvent: event };
5759
+ }
5109
5760
  }
5110
5761
  }
5111
5762
  /**
@@ -5159,6 +5810,48 @@ var init_agent = __esm({
5159
5810
  getMediaStore() {
5160
5811
  return this.mediaStore;
5161
5812
  }
5813
+ /**
5814
+ * Get the execution tree for this agent.
5815
+ *
5816
+ * The execution tree provides a first-class model of all LLM calls and gadget executions,
5817
+ * including nested subagent activity. Use this to:
5818
+ * - Query execution state: `tree.getNode(id)`
5819
+ * - Get total cost: `tree.getTotalCost()`
5820
+ * - Get subtree cost/media/tokens: `tree.getSubtreeCost(nodeId)`
5821
+ * - Subscribe to events: `tree.on("llm_call_complete", handler)`
5822
+ * - Stream all events: `for await (const event of tree.events())`
5823
+ *
5824
+ * For subagents (created with `withParentContext`), the tree is shared with the parent,
5825
+ * enabling unified tracking and real-time visibility across all nesting levels.
5826
+ *
5827
+ * @returns The ExecutionTree instance
5828
+ *
5829
+ * @example
5830
+ * ```typescript
5831
+ * const agent = LLMist.createAgent()
5832
+ * .withModel("sonnet")
5833
+ * .withGadgets(BrowseWeb)
5834
+ * .ask("Research topic X");
5835
+ *
5836
+ * for await (const event of agent.run()) {
5837
+ * // Process events...
5838
+ * }
5839
+ *
5840
+ * // After execution, query the tree
5841
+ * const tree = agent.getTree();
5842
+ * console.log(`Total cost: $${tree.getTotalCost().toFixed(4)}`);
5843
+ *
5844
+ * // Inspect all LLM calls
5845
+ * for (const node of tree.getAllNodes()) {
5846
+ * if (node.type === "llm_call") {
5847
+ * console.log(`LLM #${node.iteration}: ${node.model}`);
5848
+ * }
5849
+ * }
5850
+ * ```
5851
+ */
5852
+ getTree() {
5853
+ return this.tree;
5854
+ }
5162
5855
  /**
5163
5856
  * Manually trigger context compaction.
5164
5857
  *
@@ -5260,6 +5953,7 @@ var init_agent = __esm({
5260
5953
  await this.hooks.observers.onCompaction({
5261
5954
  iteration: currentIteration,
5262
5955
  event: compactionEvent,
5956
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5263
5957
  stats: this.compactionManager.getStats(),
5264
5958
  logger: this.logger
5265
5959
  });
@@ -5321,6 +6015,13 @@ var init_agent = __esm({
5321
6015
  messageCount: llmOptions.messages.length,
5322
6016
  messages: llmOptions.messages
5323
6017
  });
6018
+ const llmNode = this.tree.addLLMCall({
6019
+ iteration: currentIteration,
6020
+ model: llmOptions.model,
6021
+ parentId: this.parentNodeId,
6022
+ request: llmOptions.messages
6023
+ });
6024
+ const currentLLMNodeId = llmNode.id;
5324
6025
  const stream2 = this.client.stream(llmOptions);
5325
6026
  const processor = new StreamProcessor({
5326
6027
  iteration: currentIteration,
@@ -5331,14 +6032,17 @@ var init_agent = __esm({
5331
6032
  hooks: this.hooks,
5332
6033
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
5333
6034
  requestHumanInput: this.requestHumanInput,
5334
- stopOnGadgetError: this.stopOnGadgetError,
5335
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5336
6035
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5337
6036
  client: this.client,
5338
6037
  mediaStore: this.mediaStore,
5339
6038
  agentConfig: this.agentContextConfig,
5340
6039
  subagentConfig: this.subagentConfig,
5341
- onSubagentEvent: this.onSubagentEvent
6040
+ onSubagentEvent: this.onSubagentEvent,
6041
+ // Tree context for execution tracking
6042
+ tree: this.tree,
6043
+ parentNodeId: currentLLMNodeId,
6044
+ // Gadgets are children of this LLM call
6045
+ baseDepth: this.baseDepth
5342
6046
  });
5343
6047
  let streamMetadata = null;
5344
6048
  let gadgetCallCount = 0;
@@ -5384,6 +6088,11 @@ var init_agent = __esm({
5384
6088
  await this.hooks.observers.onLLMCallComplete(context);
5385
6089
  }
5386
6090
  });
6091
+ this.tree.completeLLMCall(currentLLMNodeId, {
6092
+ response: result.rawResponse,
6093
+ usage: result.usage,
6094
+ finishReason: result.finishReason
6095
+ });
5387
6096
  let finalMessage = result.finalMessage;
5388
6097
  if (this.hooks.controllers?.afterLLMCall) {
5389
6098
  const context = {
@@ -5418,10 +6127,12 @@ var init_agent = __esm({
5418
6127
  const textContent = textOutputs.join("");
5419
6128
  if (textContent.trim()) {
5420
6129
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6130
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
5421
6131
  this.conversation.addGadgetCallResult(
5422
6132
  gadgetName,
5423
6133
  parameterMapping(textContent),
5424
- resultMapping ? resultMapping(textContent) : textContent
6134
+ resultMapping ? resultMapping(textContent) : textContent,
6135
+ syntheticId
5425
6136
  );
5426
6137
  }
5427
6138
  }
@@ -5432,6 +6143,7 @@ var init_agent = __esm({
5432
6143
  gadgetResult.gadgetName,
5433
6144
  gadgetResult.parameters,
5434
6145
  gadgetResult.error ?? gadgetResult.result ?? "",
6146
+ gadgetResult.invocationId,
5435
6147
  gadgetResult.media,
5436
6148
  gadgetResult.mediaIds
5437
6149
  );
@@ -5439,10 +6151,12 @@ var init_agent = __esm({
5439
6151
  }
5440
6152
  } else {
5441
6153
  if (finalMessage.trim()) {
6154
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
5442
6155
  this.conversation.addGadgetCallResult(
5443
6156
  "TellUser",
5444
6157
  { message: finalMessage, done: false, type: "info" },
5445
- `\u2139\uFE0F ${finalMessage}`
6158
+ `\u2139\uFE0F ${finalMessage}`,
6159
+ syntheticId
5446
6160
  );
5447
6161
  }
5448
6162
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
@@ -5653,8 +6367,6 @@ var init_builder = __esm({
5653
6367
  gadgetArgPrefix;
5654
6368
  textOnlyHandler;
5655
6369
  textWithGadgetsHandler;
5656
- stopOnGadgetError;
5657
- canRecoverFromGadgetError;
5658
6370
  defaultGadgetTimeoutMs;
5659
6371
  gadgetOutputLimit;
5660
6372
  gadgetOutputLimitPercent;
@@ -5663,6 +6375,8 @@ var init_builder = __esm({
5663
6375
  trailingMessage;
5664
6376
  subagentConfig;
5665
6377
  subagentEventCallback;
6378
+ // Tree context for subagent support - enables shared tree model
6379
+ // When a gadget calls withParentContext(ctx), it shares the parent's tree
5666
6380
  parentContext;
5667
6381
  constructor(client) {
5668
6382
  this.client = client;
@@ -5945,62 +6659,6 @@ var init_builder = __esm({
5945
6659
  this.textWithGadgetsHandler = handler;
5946
6660
  return this;
5947
6661
  }
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
6662
  /**
6005
6663
  * Set default timeout for gadget execution.
6006
6664
  *
@@ -6195,6 +6853,15 @@ var init_builder = __esm({
6195
6853
  * The method extracts `invocationId` and `onSubagentEvent` from the execution
6196
6854
  * context and sets up automatic forwarding via hooks and event wrapping.
6197
6855
  *
6856
+ * **NEW: Shared Tree Model** - When the parent provides an ExecutionTree via context,
6857
+ * the subagent shares that tree instead of creating its own. This enables:
6858
+ * - Unified cost tracking across all nesting levels
6859
+ * - Automatic media aggregation via `tree.getSubtreeMedia(nodeId)`
6860
+ * - Real-time visibility of nested execution in the parent
6861
+ *
6862
+ * **Signal Forwarding** - When parent context includes a signal, it's automatically
6863
+ * forwarded to the subagent for proper cancellation propagation.
6864
+ *
6198
6865
  * @param ctx - ExecutionContext passed to the gadget's execute() method
6199
6866
  * @param depth - Nesting depth (default: 1 for direct child)
6200
6867
  * @returns This builder for chaining
@@ -6215,17 +6882,25 @@ var init_builder = __esm({
6215
6882
  * result = event.content;
6216
6883
  * }
6217
6884
  * }
6885
+ *
6886
+ * // After subagent completes, costs are automatically aggregated
6887
+ * // No manual tracking needed - use tree methods:
6888
+ * const totalCost = ctx.tree?.getSubtreeCost(ctx.nodeId!);
6889
+ * const allMedia = ctx.tree?.getSubtreeMedia(ctx.nodeId!);
6218
6890
  * }
6219
6891
  * ```
6220
6892
  */
6221
6893
  withParentContext(ctx, depth = 1) {
6222
- if (ctx.onSubagentEvent && ctx.invocationId) {
6894
+ if (ctx.tree) {
6223
6895
  this.parentContext = {
6224
- invocationId: ctx.invocationId,
6225
- onSubagentEvent: ctx.onSubagentEvent,
6896
+ tree: ctx.tree,
6897
+ nodeId: ctx.nodeId,
6226
6898
  depth
6227
6899
  };
6228
6900
  }
6901
+ if (ctx.signal && !this.signal) {
6902
+ this.signal = ctx.signal;
6903
+ }
6229
6904
  return this;
6230
6905
  }
6231
6906
  /**
@@ -6258,11 +6933,13 @@ var init_builder = __esm({
6258
6933
  *
6259
6934
  * This is useful for in-context learning - showing the LLM what "past self"
6260
6935
  * did correctly so it mimics the pattern. The call is formatted with proper
6261
- * markers and parameter format.
6936
+ * markers and parameter format, including the invocation ID so the LLM can
6937
+ * reference previous calls when building dependencies.
6262
6938
  *
6263
6939
  * @param gadgetName - Name of the gadget
6264
6940
  * @param parameters - Parameters passed to the gadget
6265
6941
  * @param result - Result returned by the gadget
6942
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
6266
6943
  * @returns This builder for chaining
6267
6944
  *
6268
6945
  * @example
@@ -6274,124 +6951,36 @@ var init_builder = __esm({
6274
6951
  * done: false,
6275
6952
  * type: 'info'
6276
6953
  * },
6277
- * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands'
6954
+ * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands',
6955
+ * 'gc_1'
6278
6956
  * )
6279
6957
  * ```
6280
6958
  */
6281
- withSyntheticGadgetCall(gadgetName, parameters, result) {
6959
+ withSyntheticGadgetCall(gadgetName, parameters, result, invocationId) {
6282
6960
  const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
6283
6961
  const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
6284
6962
  const paramStr = this.formatBlockParameters(parameters, "");
6285
6963
  this.initialMessages.push({
6286
6964
  role: "assistant",
6287
- content: `${startPrefix}${gadgetName}
6965
+ content: `${startPrefix}${gadgetName}:${invocationId}
6288
6966
  ${paramStr}
6289
6967
  ${endPrefix}`
6290
6968
  });
6291
6969
  this.initialMessages.push({
6292
6970
  role: "user",
6293
- content: `Result: ${result}`
6971
+ content: `Result (${invocationId}): ${result}`
6294
6972
  });
6295
6973
  return this;
6296
6974
  }
6297
6975
  /**
6298
- * Compose the final hooks, including:
6299
- * - Trailing message injection (if configured)
6300
- * - Subagent event forwarding for LLM calls (if parentContext is set)
6976
+ * Compose the final hooks, including trailing message injection if configured.
6977
+ *
6978
+ * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
6979
+ * When a subagent uses withParentContext(ctx), it shares the parent's tree,
6980
+ * and all events are automatically visible to tree subscribers (like the TUI).
6301
6981
  */
6302
6982
  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
- }
6983
+ const hooks = this.hooks;
6395
6984
  if (!this.trailingMessage) {
6396
6985
  return hooks;
6397
6986
  }
@@ -6474,19 +7063,6 @@ ${endPrefix}`
6474
7063
  this.client = new LLMistClass();
6475
7064
  }
6476
7065
  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
7066
  return {
6491
7067
  client: this.client,
6492
7068
  model: this.model ?? "openai:gpt-5-nano",
@@ -6505,15 +7081,17 @@ ${endPrefix}`
6505
7081
  gadgetArgPrefix: this.gadgetArgPrefix,
6506
7082
  textOnlyHandler: this.textOnlyHandler,
6507
7083
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6508
- stopOnGadgetError: this.stopOnGadgetError,
6509
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6510
7084
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6511
7085
  gadgetOutputLimit: this.gadgetOutputLimit,
6512
7086
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6513
7087
  compactionConfig: this.compactionConfig,
6514
7088
  signal: this.signal,
6515
7089
  subagentConfig: this.subagentConfig,
6516
- onSubagentEvent
7090
+ onSubagentEvent: this.subagentEventCallback,
7091
+ // Tree context for shared tree model (subagents share parent's tree)
7092
+ parentTree: this.parentContext?.tree,
7093
+ parentNodeId: this.parentContext?.nodeId,
7094
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6517
7095
  };
6518
7096
  }
6519
7097
  ask(userPrompt) {
@@ -6670,19 +7248,6 @@ ${endPrefix}`
6670
7248
  this.client = new LLMistClass();
6671
7249
  }
6672
7250
  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
7251
  const options = {
6687
7252
  client: this.client,
6688
7253
  model: this.model ?? "openai:gpt-5-nano",
@@ -6701,15 +7266,17 @@ ${endPrefix}`
6701
7266
  gadgetArgPrefix: this.gadgetArgPrefix,
6702
7267
  textOnlyHandler: this.textOnlyHandler,
6703
7268
  textWithGadgetsHandler: this.textWithGadgetsHandler,
6704
- stopOnGadgetError: this.stopOnGadgetError,
6705
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
6706
7269
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
6707
7270
  gadgetOutputLimit: this.gadgetOutputLimit,
6708
7271
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
6709
7272
  compactionConfig: this.compactionConfig,
6710
7273
  signal: this.signal,
6711
7274
  subagentConfig: this.subagentConfig,
6712
- onSubagentEvent
7275
+ onSubagentEvent: this.subagentEventCallback,
7276
+ // Tree context for shared tree model (subagents share parent's tree)
7277
+ parentTree: this.parentContext?.tree,
7278
+ parentNodeId: this.parentContext?.nodeId,
7279
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
6713
7280
  };
6714
7281
  return new Agent(AGENT_INTERNAL_KEY, options);
6715
7282
  }
@@ -11297,16 +11864,16 @@ var MockConversationManager = class {
11297
11864
  this.history.push(msg);
11298
11865
  this.addedMessages.push(msg);
11299
11866
  }
11300
- addGadgetCallResult(gadgetName, parameters, result) {
11867
+ addGadgetCallResult(gadgetName, parameters, result, invocationId) {
11301
11868
  const assistantMsg = {
11302
11869
  role: "assistant",
11303
- content: `!!!GADGET_START:${gadgetName}
11870
+ content: `!!!GADGET_START:${gadgetName}:${invocationId}
11304
11871
  ${JSON.stringify(parameters)}
11305
11872
  !!!GADGET_END`
11306
11873
  };
11307
11874
  const resultMsg = {
11308
11875
  role: "user",
11309
- content: `Result: ${result}`
11876
+ content: `Result (${invocationId}): ${result}`
11310
11877
  };
11311
11878
  this.history.push(assistantMsg);
11312
11879
  this.history.push(resultMsg);
@@ -11654,6 +12221,8 @@ export {
11654
12221
  init_schema_validator,
11655
12222
  GadgetRegistry,
11656
12223
  init_registry,
12224
+ ExecutionTree,
12225
+ init_execution_tree,
11657
12226
  DEFAULT_HINTS,
11658
12227
  DEFAULT_PROMPTS,
11659
12228
  resolvePromptTemplate,
@@ -11700,12 +12269,6 @@ export {
11700
12269
  init_event_handlers,
11701
12270
  GadgetOutputStore,
11702
12271
  init_gadget_output_store,
11703
- GadgetCallParser,
11704
- init_parser,
11705
- GadgetExecutor,
11706
- init_executor,
11707
- StreamProcessor,
11708
- init_stream_processor,
11709
12272
  FALLBACK_CHARS_PER_TOKEN,
11710
12273
  init_constants2 as init_constants,
11711
12274
  AnthropicMessagesProvider,
@@ -11728,6 +12291,14 @@ export {
11728
12291
  init_options,
11729
12292
  LLMist,
11730
12293
  init_client,
12294
+ GadgetCallParser,
12295
+ init_parser,
12296
+ Gadget,
12297
+ init_typed_gadget,
12298
+ GadgetExecutor,
12299
+ init_executor,
12300
+ StreamProcessor,
12301
+ init_stream_processor,
11731
12302
  AgentBuilder,
11732
12303
  init_builder,
11733
12304
  validateAndApplyDefaults,
@@ -11772,4 +12343,4 @@ export {
11772
12343
  createEmptyStream,
11773
12344
  createErrorStream
11774
12345
  };
11775
- //# sourceMappingURL=chunk-EIE5VRSI.js.map
12346
+ //# sourceMappingURL=chunk-36YSBSGB.js.map