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.
package/dist/index.cjs CHANGED
@@ -513,6 +513,611 @@ var init_registry = __esm({
513
513
  }
514
514
  });
515
515
 
516
+ // src/core/execution-tree.ts
517
+ var ExecutionTree;
518
+ var init_execution_tree = __esm({
519
+ "src/core/execution-tree.ts"() {
520
+ "use strict";
521
+ ExecutionTree = class {
522
+ nodes = /* @__PURE__ */ new Map();
523
+ rootIds = [];
524
+ eventListeners = /* @__PURE__ */ new Map();
525
+ eventIdCounter = 0;
526
+ invocationIdToNodeId = /* @__PURE__ */ new Map();
527
+ // For async event streaming
528
+ eventQueue = [];
529
+ eventWaiters = [];
530
+ isCompleted = false;
531
+ /**
532
+ * Base depth for all nodes in this tree.
533
+ * Used when this tree is a subagent's view into a parent tree.
534
+ */
535
+ baseDepth;
536
+ /**
537
+ * Parent node ID for subagent trees.
538
+ * All root nodes in this tree will have this as their parentId.
539
+ */
540
+ parentNodeId;
541
+ constructor(options) {
542
+ this.baseDepth = options?.baseDepth ?? 0;
543
+ this.parentNodeId = options?.parentNodeId ?? null;
544
+ }
545
+ // ===========================================================================
546
+ // Node ID Generation
547
+ // ===========================================================================
548
+ generateLLMCallId(iteration, parentId) {
549
+ if (parentId) {
550
+ return `llm_${parentId}_${iteration}`;
551
+ }
552
+ return `llm_${iteration}`;
553
+ }
554
+ gadgetIdCounter = 0;
555
+ generateGadgetId(invocationId) {
556
+ return `gadget_${invocationId}_${++this.gadgetIdCounter}`;
557
+ }
558
+ // ===========================================================================
559
+ // Event Emission
560
+ // ===========================================================================
561
+ emit(event) {
562
+ const listeners = this.eventListeners.get(event.type);
563
+ if (listeners) {
564
+ for (const listener of listeners) {
565
+ try {
566
+ listener(event);
567
+ } catch (error) {
568
+ console.error(`Error in event listener for ${event.type}:`, error);
569
+ }
570
+ }
571
+ }
572
+ const allListeners = this.eventListeners.get("*");
573
+ if (allListeners) {
574
+ for (const listener of allListeners) {
575
+ try {
576
+ listener(event);
577
+ } catch (error) {
578
+ console.error("Error in wildcard event listener:", error);
579
+ }
580
+ }
581
+ }
582
+ if (this.eventWaiters.length > 0) {
583
+ const waiter = this.eventWaiters.shift();
584
+ if (waiter) waiter(event);
585
+ } else {
586
+ this.eventQueue.push(event);
587
+ }
588
+ }
589
+ createBaseEventProps(node) {
590
+ return {
591
+ eventId: ++this.eventIdCounter,
592
+ timestamp: Date.now(),
593
+ nodeId: node.id,
594
+ parentId: node.parentId,
595
+ depth: node.depth,
596
+ path: node.path
597
+ };
598
+ }
599
+ // ===========================================================================
600
+ // Node Creation
601
+ // ===========================================================================
602
+ /**
603
+ * Add a new LLM call node to the tree.
604
+ */
605
+ addLLMCall(params) {
606
+ const parentId = params.parentId ?? this.parentNodeId;
607
+ const parent = parentId ? this.nodes.get(parentId) : null;
608
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
609
+ const path = parent ? [...parent.path] : [];
610
+ const id = this.generateLLMCallId(params.iteration, parentId);
611
+ path.push(id);
612
+ const node = {
613
+ id,
614
+ type: "llm_call",
615
+ parentId,
616
+ depth,
617
+ path,
618
+ createdAt: Date.now(),
619
+ completedAt: null,
620
+ iteration: params.iteration,
621
+ model: params.model,
622
+ request: params.request,
623
+ response: "",
624
+ children: []
625
+ };
626
+ this.nodes.set(id, node);
627
+ if (!parentId) {
628
+ this.rootIds.push(id);
629
+ } else if (parent) {
630
+ parent.children.push(id);
631
+ }
632
+ this.emit({
633
+ type: "llm_call_start",
634
+ ...this.createBaseEventProps(node),
635
+ iteration: node.iteration,
636
+ model: node.model,
637
+ request: node.request
638
+ });
639
+ return node;
640
+ }
641
+ /**
642
+ * Add text to an LLM call's response (for streaming).
643
+ */
644
+ appendLLMResponse(nodeId, chunk) {
645
+ const node = this.nodes.get(nodeId);
646
+ if (!node || node.type !== "llm_call") {
647
+ throw new Error(`LLM call node not found: ${nodeId}`);
648
+ }
649
+ node.response += chunk;
650
+ this.emit({
651
+ type: "llm_call_stream",
652
+ ...this.createBaseEventProps(node),
653
+ chunk
654
+ });
655
+ }
656
+ /**
657
+ * Complete an LLM call node.
658
+ */
659
+ completeLLMCall(nodeId, params) {
660
+ const node = this.nodes.get(nodeId);
661
+ if (!node || node.type !== "llm_call") {
662
+ throw new Error(`LLM call node not found: ${nodeId}`);
663
+ }
664
+ const llmNode = node;
665
+ llmNode.completedAt = Date.now();
666
+ if (params.response !== void 0) llmNode.response = params.response;
667
+ if (params.usage) llmNode.usage = params.usage;
668
+ if (params.finishReason !== void 0) llmNode.finishReason = params.finishReason;
669
+ if (params.cost !== void 0) llmNode.cost = params.cost;
670
+ this.emit({
671
+ type: "llm_call_complete",
672
+ ...this.createBaseEventProps(node),
673
+ response: llmNode.response,
674
+ usage: llmNode.usage,
675
+ finishReason: llmNode.finishReason,
676
+ cost: llmNode.cost
677
+ });
678
+ }
679
+ /**
680
+ * Mark an LLM call as failed.
681
+ */
682
+ failLLMCall(nodeId, error, recovered) {
683
+ const node = this.nodes.get(nodeId);
684
+ if (!node || node.type !== "llm_call") {
685
+ throw new Error(`LLM call node not found: ${nodeId}`);
686
+ }
687
+ const llmNode = node;
688
+ llmNode.completedAt = Date.now();
689
+ this.emit({
690
+ type: "llm_call_error",
691
+ ...this.createBaseEventProps(node),
692
+ error,
693
+ recovered
694
+ });
695
+ }
696
+ /**
697
+ * Add a new gadget node to the tree.
698
+ */
699
+ addGadget(params) {
700
+ const parentId = params.parentId ?? this.getCurrentLLMCallId() ?? this.parentNodeId;
701
+ const parent = parentId ? this.nodes.get(parentId) : null;
702
+ const depth = parent ? parent.depth + 1 : this.baseDepth;
703
+ const path = parent ? [...parent.path] : [];
704
+ const id = this.generateGadgetId(params.invocationId);
705
+ path.push(id);
706
+ const node = {
707
+ id,
708
+ type: "gadget",
709
+ parentId,
710
+ depth,
711
+ path,
712
+ createdAt: Date.now(),
713
+ completedAt: null,
714
+ invocationId: params.invocationId,
715
+ name: params.name,
716
+ parameters: params.parameters,
717
+ dependencies: params.dependencies ?? [],
718
+ state: "pending",
719
+ children: [],
720
+ isSubagent: false
721
+ };
722
+ this.nodes.set(id, node);
723
+ this.invocationIdToNodeId.set(params.invocationId, id);
724
+ if (parent) {
725
+ parent.children.push(id);
726
+ }
727
+ this.emit({
728
+ type: "gadget_call",
729
+ ...this.createBaseEventProps(node),
730
+ invocationId: node.invocationId,
731
+ name: node.name,
732
+ parameters: node.parameters,
733
+ dependencies: node.dependencies
734
+ });
735
+ return node;
736
+ }
737
+ /**
738
+ * Mark a gadget as started (running).
739
+ */
740
+ startGadget(nodeId) {
741
+ const node = this.nodes.get(nodeId);
742
+ if (!node || node.type !== "gadget") {
743
+ throw new Error(`Gadget node not found: ${nodeId}`);
744
+ }
745
+ const gadgetNode = node;
746
+ gadgetNode.state = "running";
747
+ this.emit({
748
+ type: "gadget_start",
749
+ ...this.createBaseEventProps(node),
750
+ invocationId: gadgetNode.invocationId,
751
+ name: gadgetNode.name
752
+ });
753
+ }
754
+ /**
755
+ * Complete a gadget node successfully.
756
+ */
757
+ completeGadget(nodeId, params) {
758
+ const node = this.nodes.get(nodeId);
759
+ if (!node || node.type !== "gadget") {
760
+ throw new Error(`Gadget node not found: ${nodeId}`);
761
+ }
762
+ const gadgetNode = node;
763
+ gadgetNode.completedAt = Date.now();
764
+ gadgetNode.state = params.error ? "failed" : "completed";
765
+ if (params.result !== void 0) gadgetNode.result = params.result;
766
+ if (params.error) gadgetNode.error = params.error;
767
+ if (params.executionTimeMs !== void 0) gadgetNode.executionTimeMs = params.executionTimeMs;
768
+ if (params.cost !== void 0) gadgetNode.cost = params.cost;
769
+ if (params.media) gadgetNode.media = params.media;
770
+ gadgetNode.isSubagent = gadgetNode.children.some((childId) => {
771
+ const child = this.nodes.get(childId);
772
+ return child?.type === "llm_call";
773
+ });
774
+ if (params.error) {
775
+ this.emit({
776
+ type: "gadget_error",
777
+ ...this.createBaseEventProps(node),
778
+ invocationId: gadgetNode.invocationId,
779
+ name: gadgetNode.name,
780
+ error: params.error,
781
+ executionTimeMs: params.executionTimeMs ?? 0
782
+ });
783
+ } else {
784
+ this.emit({
785
+ type: "gadget_complete",
786
+ ...this.createBaseEventProps(node),
787
+ invocationId: gadgetNode.invocationId,
788
+ name: gadgetNode.name,
789
+ result: params.result ?? "",
790
+ executionTimeMs: params.executionTimeMs ?? 0,
791
+ cost: params.cost,
792
+ media: params.media
793
+ });
794
+ }
795
+ }
796
+ /**
797
+ * Mark a gadget as skipped due to dependency failure.
798
+ */
799
+ skipGadget(nodeId, failedDependency, failedDependencyError, reason) {
800
+ const node = this.nodes.get(nodeId);
801
+ if (!node || node.type !== "gadget") {
802
+ throw new Error(`Gadget node not found: ${nodeId}`);
803
+ }
804
+ const gadgetNode = node;
805
+ gadgetNode.completedAt = Date.now();
806
+ gadgetNode.state = "skipped";
807
+ gadgetNode.failedDependency = failedDependency;
808
+ gadgetNode.error = failedDependencyError;
809
+ const error = reason === "controller_skip" ? "Skipped by controller" : `Dependency ${failedDependency} failed: ${failedDependencyError}`;
810
+ this.emit({
811
+ type: "gadget_skipped",
812
+ ...this.createBaseEventProps(node),
813
+ invocationId: gadgetNode.invocationId,
814
+ name: gadgetNode.name,
815
+ reason,
816
+ error,
817
+ failedDependency,
818
+ failedDependencyError
819
+ });
820
+ }
821
+ // ===========================================================================
822
+ // Text Events (pure notifications, not tree nodes)
823
+ // ===========================================================================
824
+ /**
825
+ * Emit a text event (notification only, not stored in tree).
826
+ */
827
+ emitText(content, llmCallNodeId) {
828
+ const node = this.nodes.get(llmCallNodeId);
829
+ if (!node) {
830
+ throw new Error(`Node not found: ${llmCallNodeId}`);
831
+ }
832
+ this.emit({
833
+ type: "text",
834
+ ...this.createBaseEventProps(node),
835
+ content
836
+ });
837
+ }
838
+ // ===========================================================================
839
+ // Query Methods
840
+ // ===========================================================================
841
+ /**
842
+ * Get a node by ID.
843
+ */
844
+ getNode(id) {
845
+ return this.nodes.get(id);
846
+ }
847
+ /**
848
+ * Get a gadget node by invocation ID.
849
+ */
850
+ getNodeByInvocationId(invocationId) {
851
+ const nodeId = this.invocationIdToNodeId.get(invocationId);
852
+ if (!nodeId) return void 0;
853
+ const node = this.nodes.get(nodeId);
854
+ return node?.type === "gadget" ? node : void 0;
855
+ }
856
+ /**
857
+ * Get all root nodes (depth 0 for this tree).
858
+ */
859
+ getRoots() {
860
+ return this.rootIds.map((id) => this.nodes.get(id)).filter((node) => node !== void 0);
861
+ }
862
+ /**
863
+ * Get children of a node.
864
+ */
865
+ getChildren(id) {
866
+ const node = this.nodes.get(id);
867
+ if (!node) return [];
868
+ return node.children.map((childId) => this.nodes.get(childId)).filter((child) => child !== void 0);
869
+ }
870
+ /**
871
+ * Get ancestors of a node (from root to parent).
872
+ */
873
+ getAncestors(id) {
874
+ const node = this.nodes.get(id);
875
+ if (!node) return [];
876
+ const ancestors = [];
877
+ let currentId = node.parentId;
878
+ while (currentId) {
879
+ const ancestor = this.nodes.get(currentId);
880
+ if (ancestor) {
881
+ ancestors.unshift(ancestor);
882
+ currentId = ancestor.parentId;
883
+ } else {
884
+ break;
885
+ }
886
+ }
887
+ return ancestors;
888
+ }
889
+ /**
890
+ * Get all descendants of a node.
891
+ */
892
+ getDescendants(id, type) {
893
+ const node = this.nodes.get(id);
894
+ if (!node) return [];
895
+ const descendants = [];
896
+ const stack = [...node.children];
897
+ while (stack.length > 0) {
898
+ const childId = stack.pop();
899
+ const child = this.nodes.get(childId);
900
+ if (child) {
901
+ if (!type || child.type === type) {
902
+ descendants.push(child);
903
+ }
904
+ stack.push(...child.children);
905
+ }
906
+ }
907
+ return descendants;
908
+ }
909
+ /**
910
+ * Get the current (most recent incomplete) LLM call node.
911
+ */
912
+ getCurrentLLMCallId() {
913
+ for (let i = this.rootIds.length - 1; i >= 0; i--) {
914
+ const node = this.nodes.get(this.rootIds[i]);
915
+ if (node?.type === "llm_call" && !node.completedAt) {
916
+ return node.id;
917
+ }
918
+ }
919
+ return void 0;
920
+ }
921
+ // ===========================================================================
922
+ // Aggregation Methods (for subagent support)
923
+ // ===========================================================================
924
+ /**
925
+ * Get total cost for entire tree.
926
+ */
927
+ getTotalCost() {
928
+ let total = 0;
929
+ for (const node of this.nodes.values()) {
930
+ if (node.type === "llm_call" && node.cost) {
931
+ total += node.cost;
932
+ } else if (node.type === "gadget" && node.cost) {
933
+ total += node.cost;
934
+ }
935
+ }
936
+ return total;
937
+ }
938
+ /**
939
+ * Get total cost for a subtree (node and all descendants).
940
+ */
941
+ getSubtreeCost(nodeId) {
942
+ const node = this.nodes.get(nodeId);
943
+ if (!node) return 0;
944
+ let total = 0;
945
+ if (node.type === "llm_call" && node.cost) {
946
+ total += node.cost;
947
+ } else if (node.type === "gadget" && node.cost) {
948
+ total += node.cost;
949
+ }
950
+ for (const descendant of this.getDescendants(nodeId)) {
951
+ if (descendant.type === "llm_call" && descendant.cost) {
952
+ total += descendant.cost;
953
+ } else if (descendant.type === "gadget" && descendant.cost) {
954
+ total += descendant.cost;
955
+ }
956
+ }
957
+ return total;
958
+ }
959
+ /**
960
+ * Get token usage for entire tree.
961
+ */
962
+ getTotalTokens() {
963
+ let input = 0;
964
+ let output = 0;
965
+ let cached = 0;
966
+ for (const node of this.nodes.values()) {
967
+ if (node.type === "llm_call") {
968
+ const llmNode = node;
969
+ if (llmNode.usage) {
970
+ input += llmNode.usage.inputTokens;
971
+ output += llmNode.usage.outputTokens;
972
+ cached += llmNode.usage.cachedInputTokens ?? 0;
973
+ }
974
+ }
975
+ }
976
+ return { input, output, cached };
977
+ }
978
+ /**
979
+ * Get token usage for a subtree.
980
+ */
981
+ getSubtreeTokens(nodeId) {
982
+ const node = this.nodes.get(nodeId);
983
+ if (!node) return { input: 0, output: 0, cached: 0 };
984
+ let input = 0;
985
+ let output = 0;
986
+ let cached = 0;
987
+ const nodesToProcess = [node, ...this.getDescendants(nodeId)];
988
+ for (const n of nodesToProcess) {
989
+ if (n.type === "llm_call") {
990
+ const llmNode = n;
991
+ if (llmNode.usage) {
992
+ input += llmNode.usage.inputTokens;
993
+ output += llmNode.usage.outputTokens;
994
+ cached += llmNode.usage.cachedInputTokens ?? 0;
995
+ }
996
+ }
997
+ }
998
+ return { input, output, cached };
999
+ }
1000
+ /**
1001
+ * Collect all media from a subtree.
1002
+ */
1003
+ getSubtreeMedia(nodeId) {
1004
+ const node = this.nodes.get(nodeId);
1005
+ if (!node) return [];
1006
+ const media = [];
1007
+ const nodesToProcess = node.type === "gadget" ? [node] : [];
1008
+ nodesToProcess.push(...this.getDescendants(nodeId, "gadget"));
1009
+ for (const n of nodesToProcess) {
1010
+ if (n.type === "gadget") {
1011
+ const gadgetNode = n;
1012
+ if (gadgetNode.media) {
1013
+ media.push(...gadgetNode.media);
1014
+ }
1015
+ }
1016
+ }
1017
+ return media;
1018
+ }
1019
+ /**
1020
+ * Check if a subtree is complete (all nodes finished).
1021
+ */
1022
+ isSubtreeComplete(nodeId) {
1023
+ const node = this.nodes.get(nodeId);
1024
+ if (!node) return true;
1025
+ if (!node.completedAt) return false;
1026
+ for (const descendant of this.getDescendants(nodeId)) {
1027
+ if (!descendant.completedAt) return false;
1028
+ }
1029
+ return true;
1030
+ }
1031
+ /**
1032
+ * Get node counts.
1033
+ */
1034
+ getNodeCount() {
1035
+ let llmCalls = 0;
1036
+ let gadgets = 0;
1037
+ for (const node of this.nodes.values()) {
1038
+ if (node.type === "llm_call") llmCalls++;
1039
+ else if (node.type === "gadget") gadgets++;
1040
+ }
1041
+ return { llmCalls, gadgets };
1042
+ }
1043
+ // ===========================================================================
1044
+ // Event Subscription
1045
+ // ===========================================================================
1046
+ /**
1047
+ * Subscribe to events of a specific type.
1048
+ * Returns unsubscribe function.
1049
+ *
1050
+ * @param type - Event type to subscribe to (use "*" for all events)
1051
+ * @param listener - Callback function that receives matching events
1052
+ * @returns Unsubscribe function
1053
+ *
1054
+ * @example
1055
+ * ```typescript
1056
+ * const unsubscribe = tree.on("gadget_complete", (event) => {
1057
+ * if (event.type === "gadget_complete") {
1058
+ * console.log(`Gadget ${event.name} completed`);
1059
+ * }
1060
+ * });
1061
+ * ```
1062
+ */
1063
+ on(type, listener) {
1064
+ if (!this.eventListeners.has(type)) {
1065
+ this.eventListeners.set(type, /* @__PURE__ */ new Set());
1066
+ }
1067
+ const listeners = this.eventListeners.get(type);
1068
+ listeners.add(listener);
1069
+ return () => {
1070
+ listeners.delete(listener);
1071
+ };
1072
+ }
1073
+ /**
1074
+ * Subscribe to all events.
1075
+ */
1076
+ onAll(listener) {
1077
+ return this.on("*", listener);
1078
+ }
1079
+ /**
1080
+ * Get async iterable of all events.
1081
+ * Events are yielded as they occur.
1082
+ */
1083
+ async *events() {
1084
+ while (!this.isCompleted) {
1085
+ while (this.eventQueue.length > 0) {
1086
+ yield this.eventQueue.shift();
1087
+ }
1088
+ if (this.isCompleted) break;
1089
+ const event = await new Promise((resolve) => {
1090
+ if (this.eventQueue.length > 0) {
1091
+ resolve(this.eventQueue.shift());
1092
+ } else {
1093
+ this.eventWaiters.push(resolve);
1094
+ }
1095
+ });
1096
+ yield event;
1097
+ }
1098
+ while (this.eventQueue.length > 0) {
1099
+ yield this.eventQueue.shift();
1100
+ }
1101
+ }
1102
+ /**
1103
+ * Mark the tree as complete (no more events will be emitted).
1104
+ */
1105
+ complete() {
1106
+ this.isCompleted = true;
1107
+ for (const waiter of this.eventWaiters) {
1108
+ }
1109
+ this.eventWaiters = [];
1110
+ }
1111
+ /**
1112
+ * Check if the tree is complete.
1113
+ */
1114
+ isComplete() {
1115
+ return this.isCompleted;
1116
+ }
1117
+ };
1118
+ }
1119
+ });
1120
+
516
1121
  // src/core/prompt-config.ts
517
1122
  function resolvePromptTemplate(template, defaultValue, context) {
518
1123
  const resolved = template ?? defaultValue;
@@ -936,23 +1541,26 @@ Produces: { "items": ["first", "second"] }`);
936
1541
  * Record a gadget execution result in the message history.
937
1542
  * Creates an assistant message with the gadget invocation and a user message with the result.
938
1543
  *
1544
+ * The invocationId is shown to the LLM so it can reference previous calls when building dependencies.
1545
+ *
939
1546
  * @param gadget - Name of the gadget that was executed
940
1547
  * @param parameters - Parameters that were passed to the gadget
941
1548
  * @param result - Text result from the gadget execution
1549
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
942
1550
  * @param media - Optional media outputs from the gadget
943
1551
  * @param mediaIds - Optional IDs for the media outputs
944
1552
  */
945
- addGadgetCallResult(gadget, parameters, result, media, mediaIds) {
1553
+ addGadgetCallResult(gadget, parameters, result, invocationId, media, mediaIds) {
946
1554
  const paramStr = this.formatBlockParameters(parameters, "");
947
1555
  this.messages.push({
948
1556
  role: "assistant",
949
- content: `${this.startPrefix}${gadget}
1557
+ content: `${this.startPrefix}${gadget}:${invocationId}
950
1558
  ${paramStr}
951
1559
  ${this.endPrefix}`
952
1560
  });
953
1561
  if (media && media.length > 0 && mediaIds && mediaIds.length > 0) {
954
1562
  const idRefs = media.map((m, i) => `[Media: ${mediaIds[i]} (${m.kind})]`).join("\n");
955
- const textWithIds = `Result: ${result}
1563
+ const textWithIds = `Result (${invocationId}): ${result}
956
1564
  ${idRefs}`;
957
1565
  const parts = [text(textWithIds)];
958
1566
  for (const item of media) {
@@ -966,7 +1574,7 @@ ${idRefs}`;
966
1574
  } else {
967
1575
  this.messages.push({
968
1576
  role: "user",
969
- content: `Result: ${result}`
1577
+ content: `Result (${invocationId}): ${result}`
970
1578
  });
971
1579
  }
972
1580
  return this;
@@ -2380,8 +2988,8 @@ var init_conversation_manager = __esm({
2380
2988
  addAssistantMessage(content) {
2381
2989
  this.historyBuilder.addAssistant(content);
2382
2990
  }
2383
- addGadgetCallResult(gadgetName, parameters, result, media, mediaIds) {
2384
- this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, media, mediaIds);
2991
+ addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds) {
2992
+ this.historyBuilder.addGadgetCallResult(gadgetName, parameters, result, invocationId, media, mediaIds);
2385
2993
  }
2386
2994
  getMessages() {
2387
2995
  return [...this.baseMessages, ...this.initialMessages, ...this.historyBuilder.build()];
@@ -3562,7 +4170,7 @@ var init_executor = __esm({
3562
4170
  init_exceptions();
3563
4171
  init_parser();
3564
4172
  GadgetExecutor = class {
3565
- constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent) {
4173
+ constructor(registry, requestHumanInput, logger, defaultGadgetTimeoutMs, errorFormatterOptions, client, mediaStore, agentConfig, subagentConfig, onSubagentEvent, tree, parentNodeId, baseDepth) {
3566
4174
  this.registry = registry;
3567
4175
  this.requestHumanInput = requestHumanInput;
3568
4176
  this.defaultGadgetTimeoutMs = defaultGadgetTimeoutMs;
@@ -3571,6 +4179,9 @@ var init_executor = __esm({
3571
4179
  this.agentConfig = agentConfig;
3572
4180
  this.subagentConfig = subagentConfig;
3573
4181
  this.onSubagentEvent = onSubagentEvent;
4182
+ this.tree = tree;
4183
+ this.parentNodeId = parentNodeId;
4184
+ this.baseDepth = baseDepth;
3574
4185
  this.logger = logger ?? createLogger({ name: "llmist:executor" });
3575
4186
  this.errorFormatter = new GadgetExecutionErrorFormatter(errorFormatterOptions);
3576
4187
  this.argPrefix = errorFormatterOptions?.argPrefix ?? GADGET_ARG_PREFIX;
@@ -3581,15 +4192,21 @@ var init_executor = __esm({
3581
4192
  /**
3582
4193
  * Creates a promise that rejects with a TimeoutException after the specified timeout.
3583
4194
  * Aborts the provided AbortController before rejecting, allowing gadgets to clean up.
4195
+ * Returns both the promise and a cancel function to clear the timeout when no longer needed.
3584
4196
  */
3585
4197
  createTimeoutPromise(gadgetName, timeoutMs, abortController) {
3586
- return new Promise((_, reject) => {
3587
- setTimeout(() => {
4198
+ let timeoutId;
4199
+ const promise = new Promise((_, reject) => {
4200
+ timeoutId = setTimeout(() => {
3588
4201
  const timeoutError = new TimeoutException(gadgetName, timeoutMs);
3589
4202
  abortController.abort(timeoutError.message);
3590
4203
  reject(timeoutError);
3591
4204
  }, timeoutMs);
3592
4205
  });
4206
+ return {
4207
+ promise,
4208
+ cancel: () => clearTimeout(timeoutId)
4209
+ };
3593
4210
  }
3594
4211
  /**
3595
4212
  * Unify gadget execute result to consistent internal format.
@@ -3711,6 +4328,8 @@ var init_executor = __esm({
3711
4328
  });
3712
4329
  }
3713
4330
  };
4331
+ const gadgetNodeId = this.tree?.getNodeByInvocationId(call.invocationId)?.id;
4332
+ const gadgetDepth = gadgetNodeId ? this.tree?.getNode(gadgetNodeId)?.depth ?? this.baseDepth : this.baseDepth;
3714
4333
  const ctx = {
3715
4334
  reportCost,
3716
4335
  llmist: this.client ? new CostReportingLLMistWrapper(this.client, reportCost) : void 0,
@@ -3718,7 +4337,11 @@ var init_executor = __esm({
3718
4337
  agentConfig: this.agentConfig,
3719
4338
  subagentConfig: this.subagentConfig,
3720
4339
  invocationId: call.invocationId,
3721
- onSubagentEvent: this.onSubagentEvent
4340
+ onSubagentEvent: this.onSubagentEvent,
4341
+ // Tree context for subagent support - use gadget's own node ID
4342
+ tree: this.tree,
4343
+ nodeId: gadgetNodeId,
4344
+ depth: gadgetDepth
3722
4345
  };
3723
4346
  let rawResult;
3724
4347
  if (timeoutMs && timeoutMs > 0) {
@@ -3726,10 +4349,15 @@ var init_executor = __esm({
3726
4349
  gadgetName: call.gadgetName,
3727
4350
  timeoutMs
3728
4351
  });
3729
- rawResult = await Promise.race([
3730
- Promise.resolve(gadget.execute(validatedParameters, ctx)),
3731
- this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController)
3732
- ]);
4352
+ const timeout = this.createTimeoutPromise(call.gadgetName, timeoutMs, abortController);
4353
+ try {
4354
+ rawResult = await Promise.race([
4355
+ Promise.resolve(gadget.execute(validatedParameters, ctx)),
4356
+ timeout.promise
4357
+ ]);
4358
+ } finally {
4359
+ timeout.cancel();
4360
+ }
3733
4361
  } else {
3734
4362
  rawResult = await Promise.resolve(gadget.execute(validatedParameters, ctx));
3735
4363
  }
@@ -3924,10 +4552,11 @@ var init_stream_processor = __esm({
3924
4552
  logger;
3925
4553
  parser;
3926
4554
  executor;
3927
- stopOnGadgetError;
3928
- canRecoverFromGadgetError;
4555
+ // Execution Tree context
4556
+ tree;
4557
+ parentNodeId;
4558
+ baseDepth;
3929
4559
  responseText = "";
3930
- executionHalted = false;
3931
4560
  observerFailureCount = 0;
3932
4561
  // Dependency tracking for gadget execution DAG
3933
4562
  /** Gadgets waiting for their dependencies to complete */
@@ -3938,18 +4567,28 @@ var init_stream_processor = __esm({
3938
4567
  failedInvocations = /* @__PURE__ */ new Set();
3939
4568
  /** Promises for independent gadgets currently executing (fire-and-forget) */
3940
4569
  inFlightExecutions = /* @__PURE__ */ new Map();
4570
+ /** Queue of completed gadget results ready to be yielded (for real-time streaming) */
4571
+ completedResultsQueue = [];
3941
4572
  constructor(options) {
3942
4573
  this.iteration = options.iteration;
3943
4574
  this.registry = options.registry;
3944
4575
  this.hooks = options.hooks ?? {};
3945
4576
  this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
3946
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
3947
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
4577
+ this.tree = options.tree;
4578
+ this.parentNodeId = options.parentNodeId ?? null;
4579
+ this.baseDepth = options.baseDepth ?? 0;
3948
4580
  this.parser = new GadgetCallParser({
3949
4581
  startPrefix: options.gadgetStartPrefix,
3950
4582
  endPrefix: options.gadgetEndPrefix,
3951
4583
  argPrefix: options.gadgetArgPrefix
3952
4584
  });
4585
+ const wrappedOnSubagentEvent = options.onSubagentEvent ? (event) => {
4586
+ this.completedResultsQueue.push({
4587
+ type: "subagent_event",
4588
+ subagentEvent: event
4589
+ });
4590
+ options.onSubagentEvent?.(event);
4591
+ } : void 0;
3953
4592
  this.executor = new GadgetExecutor(
3954
4593
  options.registry,
3955
4594
  options.requestHumanInput,
@@ -3960,7 +4599,11 @@ var init_stream_processor = __esm({
3960
4599
  options.mediaStore,
3961
4600
  options.agentConfig,
3962
4601
  options.subagentConfig,
3963
- options.onSubagentEvent
4602
+ wrappedOnSubagentEvent,
4603
+ // Tree context for gadget execution
4604
+ options.tree,
4605
+ options.parentNodeId,
4606
+ options.baseDepth
3964
4607
  );
3965
4608
  }
3966
4609
  /**
@@ -4011,7 +4654,7 @@ var init_stream_processor = __esm({
4011
4654
  usage,
4012
4655
  logger: this.logger
4013
4656
  };
4014
- await this.hooks.observers.onStreamChunk(context);
4657
+ await this.hooks.observers?.onStreamChunk?.(context);
4015
4658
  });
4016
4659
  await this.runObserversInParallel(chunkObservers);
4017
4660
  }
@@ -4029,25 +4672,7 @@ var init_stream_processor = __esm({
4029
4672
  }
4030
4673
  }
4031
4674
  }
4032
- if (this.executionHalted) {
4033
- this.logger.info("Breaking from LLM stream due to gadget error");
4034
- break;
4035
- }
4036
- }
4037
- if (!this.executionHalted) {
4038
- for (const event of this.parser.finalize()) {
4039
- for await (const processedEvent of this.processEventGenerator(event)) {
4040
- yield processedEvent;
4041
- if (processedEvent.type === "gadget_result") {
4042
- didExecuteGadgets = true;
4043
- if (processedEvent.result.breaksLoop) {
4044
- shouldBreakLoop = true;
4045
- }
4046
- }
4047
- }
4048
- }
4049
- const inFlightResults = await this.collectInFlightResults();
4050
- for (const evt of inFlightResults) {
4675
+ for (const evt of this.drainCompletedResults()) {
4051
4676
  yield evt;
4052
4677
  if (evt.type === "gadget_result") {
4053
4678
  didExecuteGadgets = true;
@@ -4056,16 +4681,45 @@ var init_stream_processor = __esm({
4056
4681
  }
4057
4682
  }
4058
4683
  }
4059
- for await (const evt of this.processPendingGadgetsGenerator()) {
4060
- yield evt;
4061
- if (evt.type === "gadget_result") {
4684
+ }
4685
+ for (const event of this.parser.finalize()) {
4686
+ for await (const processedEvent of this.processEventGenerator(event)) {
4687
+ yield processedEvent;
4688
+ if (processedEvent.type === "gadget_result") {
4062
4689
  didExecuteGadgets = true;
4063
- if (evt.result.breaksLoop) {
4690
+ if (processedEvent.result.breaksLoop) {
4064
4691
  shouldBreakLoop = true;
4065
4692
  }
4066
4693
  }
4067
4694
  }
4068
4695
  }
4696
+ for await (const evt of this.waitForInFlightExecutions()) {
4697
+ yield evt;
4698
+ if (evt.type === "gadget_result") {
4699
+ didExecuteGadgets = true;
4700
+ if (evt.result.breaksLoop) {
4701
+ shouldBreakLoop = true;
4702
+ }
4703
+ }
4704
+ }
4705
+ for (const evt of this.drainCompletedResults()) {
4706
+ yield evt;
4707
+ if (evt.type === "gadget_result") {
4708
+ didExecuteGadgets = true;
4709
+ if (evt.result.breaksLoop) {
4710
+ shouldBreakLoop = true;
4711
+ }
4712
+ }
4713
+ }
4714
+ for await (const evt of this.processPendingGadgetsGenerator()) {
4715
+ yield evt;
4716
+ if (evt.type === "gadget_result") {
4717
+ didExecuteGadgets = true;
4718
+ if (evt.result.breaksLoop) {
4719
+ shouldBreakLoop = true;
4720
+ }
4721
+ }
4722
+ }
4069
4723
  let finalMessage = this.responseText;
4070
4724
  if (this.hooks.interceptors?.interceptAssistantMessage) {
4071
4725
  const context = {
@@ -4086,21 +4740,8 @@ var init_stream_processor = __esm({
4086
4740
  };
4087
4741
  yield completionEvent;
4088
4742
  }
4089
- /**
4090
- * Process a single parsed event (text or gadget call).
4091
- * @deprecated Use processEventGenerator for real-time streaming
4092
- */
4093
- async processEvent(event) {
4094
- if (event.type === "text") {
4095
- return this.processTextEvent(event);
4096
- } else if (event.type === "gadget_call") {
4097
- return this.processGadgetCall(event.call);
4098
- }
4099
- return [event];
4100
- }
4101
4743
  /**
4102
4744
  * Process a single parsed event, yielding events in real-time.
4103
- * Generator version of processEvent for streaming support.
4104
4745
  */
4105
4746
  async *processEventGenerator(event) {
4106
4747
  if (event.type === "text") {
@@ -4142,12 +4783,6 @@ var init_stream_processor = __esm({
4142
4783
  * After each execution, pending gadgets are checked to see if they can now run.
4143
4784
  */
4144
4785
  async processGadgetCall(call) {
4145
- if (this.executionHalted) {
4146
- this.logger.debug("Skipping gadget execution due to previous error", {
4147
- gadgetName: call.gadgetName
4148
- });
4149
- return [];
4150
- }
4151
4786
  const events = [];
4152
4787
  events.push({ type: "gadget_call", call });
4153
4788
  if (call.dependencies.length > 0) {
@@ -4198,13 +4833,16 @@ var init_stream_processor = __esm({
4198
4833
  * when parsed (before execution), enabling real-time UI feedback.
4199
4834
  */
4200
4835
  async *processGadgetCallGenerator(call) {
4201
- if (this.executionHalted) {
4202
- this.logger.debug("Skipping gadget execution due to previous error", {
4203
- gadgetName: call.gadgetName
4836
+ yield { type: "gadget_call", call };
4837
+ if (this.tree) {
4838
+ this.tree.addGadget({
4839
+ invocationId: call.invocationId,
4840
+ name: call.gadgetName,
4841
+ parameters: call.parameters ?? {},
4842
+ dependencies: call.dependencies,
4843
+ parentId: this.parentNodeId
4204
4844
  });
4205
- return;
4206
4845
  }
4207
- yield { type: "gadget_call", call };
4208
4846
  if (call.dependencies.length > 0) {
4209
4847
  if (call.dependencies.includes(call.invocationId)) {
4210
4848
  this.logger.warn("Gadget has self-referential dependency (depends on itself)", {
@@ -4249,17 +4887,8 @@ var init_stream_processor = __esm({
4249
4887
  }
4250
4888
  return;
4251
4889
  }
4252
- if (this.stopOnGadgetError) {
4253
- for await (const evt of this.executeGadgetGenerator(call)) {
4254
- yield evt;
4255
- }
4256
- for await (const evt of this.processPendingGadgetsGenerator()) {
4257
- yield evt;
4258
- }
4259
- } else {
4260
- const executionPromise = this.executeGadgetAndCollect(call);
4261
- this.inFlightExecutions.set(call.invocationId, executionPromise);
4262
- }
4890
+ const executionPromise = this.executeGadgetAndCollect(call);
4891
+ this.inFlightExecutions.set(call.invocationId, executionPromise);
4263
4892
  }
4264
4893
  /**
4265
4894
  * Execute a gadget through the full hook lifecycle.
@@ -4274,15 +4903,6 @@ var init_stream_processor = __esm({
4274
4903
  error: call.parseError,
4275
4904
  rawParameters: call.parametersRaw
4276
4905
  });
4277
- const shouldContinue = await this.checkCanRecoverFromError(
4278
- call.parseError,
4279
- call.gadgetName,
4280
- "parse",
4281
- call.parameters
4282
- );
4283
- if (!shouldContinue) {
4284
- this.executionHalted = true;
4285
- }
4286
4906
  }
4287
4907
  let parameters = call.parameters ?? {};
4288
4908
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4325,7 +4945,7 @@ var init_stream_processor = __esm({
4325
4945
  parameters,
4326
4946
  logger: this.logger
4327
4947
  };
4328
- await this.hooks.observers.onGadgetExecutionStart(context);
4948
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4329
4949
  });
4330
4950
  }
4331
4951
  await this.runObserversInParallel(startObservers);
@@ -4394,7 +5014,7 @@ var init_stream_processor = __esm({
4394
5014
  cost: result.cost,
4395
5015
  logger: this.logger
4396
5016
  };
4397
- await this.hooks.observers.onGadgetExecutionComplete(context);
5017
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4398
5018
  });
4399
5019
  }
4400
5020
  await this.runObserversInParallel(completeObservers);
@@ -4403,18 +5023,6 @@ var init_stream_processor = __esm({
4403
5023
  this.failedInvocations.add(result.invocationId);
4404
5024
  }
4405
5025
  events.push({ type: "gadget_result", result });
4406
- if (result.error) {
4407
- const errorType = this.determineErrorType(call, result);
4408
- const shouldContinue = await this.checkCanRecoverFromError(
4409
- result.error,
4410
- result.gadgetName,
4411
- errorType,
4412
- result.parameters
4413
- );
4414
- if (!shouldContinue) {
4415
- this.executionHalted = true;
4416
- }
4417
- }
4418
5026
  return events;
4419
5027
  }
4420
5028
  /**
@@ -4428,15 +5036,6 @@ var init_stream_processor = __esm({
4428
5036
  error: call.parseError,
4429
5037
  rawParameters: call.parametersRaw
4430
5038
  });
4431
- const shouldContinue = await this.checkCanRecoverFromError(
4432
- call.parseError,
4433
- call.gadgetName,
4434
- "parse",
4435
- call.parameters
4436
- );
4437
- if (!shouldContinue) {
4438
- this.executionHalted = true;
4439
- }
4440
5039
  }
4441
5040
  let parameters = call.parameters ?? {};
4442
5041
  if (this.hooks.interceptors?.interceptGadgetParameters) {
@@ -4479,10 +5078,16 @@ var init_stream_processor = __esm({
4479
5078
  parameters,
4480
5079
  logger: this.logger
4481
5080
  };
4482
- await this.hooks.observers.onGadgetExecutionStart(context);
5081
+ await this.hooks.observers?.onGadgetExecutionStart?.(context);
4483
5082
  });
4484
5083
  }
4485
5084
  await this.runObserversInParallel(startObservers);
5085
+ if (this.tree) {
5086
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5087
+ if (gadgetNode) {
5088
+ this.tree.startGadget(gadgetNode.id);
5089
+ }
5090
+ }
4486
5091
  let result;
4487
5092
  if (shouldSkip) {
4488
5093
  result = {
@@ -4548,57 +5153,84 @@ var init_stream_processor = __esm({
4548
5153
  cost: result.cost,
4549
5154
  logger: this.logger
4550
5155
  };
4551
- await this.hooks.observers.onGadgetExecutionComplete(context);
5156
+ await this.hooks.observers?.onGadgetExecutionComplete?.(context);
4552
5157
  });
4553
5158
  }
4554
5159
  await this.runObserversInParallel(completeObservers);
5160
+ if (this.tree) {
5161
+ const gadgetNode = this.tree.getNodeByInvocationId(result.invocationId);
5162
+ if (gadgetNode) {
5163
+ if (result.error) {
5164
+ this.tree.completeGadget(gadgetNode.id, {
5165
+ error: result.error,
5166
+ executionTimeMs: result.executionTimeMs,
5167
+ cost: result.cost
5168
+ });
5169
+ } else {
5170
+ this.tree.completeGadget(gadgetNode.id, {
5171
+ result: result.result,
5172
+ executionTimeMs: result.executionTimeMs,
5173
+ cost: result.cost,
5174
+ media: result.media
5175
+ });
5176
+ }
5177
+ }
5178
+ }
4555
5179
  this.completedResults.set(result.invocationId, result);
4556
5180
  if (result.error) {
4557
5181
  this.failedInvocations.add(result.invocationId);
4558
5182
  }
4559
5183
  yield { type: "gadget_result", result };
4560
- if (result.error) {
4561
- const errorType = this.determineErrorType(call, result);
4562
- const shouldContinue = await this.checkCanRecoverFromError(
4563
- result.error,
4564
- result.gadgetName,
4565
- errorType,
4566
- result.parameters
4567
- );
4568
- if (!shouldContinue) {
4569
- this.executionHalted = true;
4570
- }
4571
- }
4572
5184
  }
4573
5185
  /**
4574
- * Execute a gadget and collect all events into an array (non-blocking).
5186
+ * Execute a gadget and push events to the completed results queue (non-blocking).
4575
5187
  * Used for fire-and-forget parallel execution of independent gadgets.
5188
+ * Results are pushed to completedResultsQueue for real-time streaming to the caller.
4576
5189
  */
4577
5190
  async executeGadgetAndCollect(call) {
4578
- const events = [];
4579
5191
  for await (const evt of this.executeGadgetGenerator(call)) {
4580
- events.push(evt);
5192
+ this.completedResultsQueue.push(evt);
5193
+ }
5194
+ }
5195
+ /**
5196
+ * Drain all completed results from the queue.
5197
+ * Used to yield results as they complete during stream processing.
5198
+ * @returns Generator that yields all events currently in the queue
5199
+ */
5200
+ *drainCompletedResults() {
5201
+ while (this.completedResultsQueue.length > 0) {
5202
+ yield this.completedResultsQueue.shift();
4581
5203
  }
4582
- return events;
4583
5204
  }
4584
5205
  /**
4585
- * Collect results from all fire-and-forget (in-flight) gadget executions.
4586
- * Called at stream end to await parallel independent gadgets.
4587
- * Clears the inFlightExecutions map after collection.
4588
- * @returns Array of all events from completed gadgets
5206
+ * Wait for all in-flight gadget executions to complete, yielding events in real-time.
5207
+ * Called at stream end to ensure all parallel executions finish.
5208
+ * Results and subagent events are pushed to completedResultsQueue during execution.
5209
+ * This generator yields queued events while polling, enabling real-time display
5210
+ * of subagent activity (LLM calls, nested gadgets) during long-running gadgets.
5211
+ * Clears the inFlightExecutions map after all gadgets complete.
4589
5212
  */
4590
- async collectInFlightResults() {
5213
+ async *waitForInFlightExecutions() {
4591
5214
  if (this.inFlightExecutions.size === 0) {
4592
- return [];
5215
+ return;
4593
5216
  }
4594
- this.logger.debug("Collecting in-flight gadget results", {
5217
+ this.logger.debug("Waiting for in-flight gadget executions", {
4595
5218
  count: this.inFlightExecutions.size,
4596
5219
  invocationIds: Array.from(this.inFlightExecutions.keys())
4597
5220
  });
4598
- const promises = Array.from(this.inFlightExecutions.values());
4599
- const results = await Promise.all(promises);
5221
+ const allDone = Promise.all(this.inFlightExecutions.values()).then(() => "done");
5222
+ const POLL_INTERVAL_MS = 100;
5223
+ while (true) {
5224
+ const result = await Promise.race([
5225
+ allDone,
5226
+ new Promise((resolve) => setTimeout(() => resolve("poll"), POLL_INTERVAL_MS))
5227
+ ]);
5228
+ yield* this.drainCompletedResults();
5229
+ if (result === "done") {
5230
+ break;
5231
+ }
5232
+ }
4600
5233
  this.inFlightExecutions.clear();
4601
- return results.flat();
4602
5234
  }
4603
5235
  /**
4604
5236
  * Handle a gadget that cannot execute because a dependency failed.
@@ -4623,6 +5255,12 @@ var init_stream_processor = __esm({
4623
5255
  }
4624
5256
  if (action.action === "skip") {
4625
5257
  this.failedInvocations.add(call.invocationId);
5258
+ if (this.tree) {
5259
+ const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
5260
+ if (gadgetNode) {
5261
+ this.tree.skipGadget(gadgetNode.id, failedDep, depError, "dependency_failed");
5262
+ }
5263
+ }
4626
5264
  const skipEvent = {
4627
5265
  type: "gadget_skipped",
4628
5266
  gadgetName: call.gadgetName,
@@ -4642,7 +5280,7 @@ var init_stream_processor = __esm({
4642
5280
  failedDependencyError: depError,
4643
5281
  logger: this.logger
4644
5282
  };
4645
- await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(observeContext));
5283
+ await this.safeObserve(() => this.hooks.observers?.onGadgetSkipped?.(observeContext));
4646
5284
  }
4647
5285
  this.logger.info("Gadget skipped due to failed dependency", {
4648
5286
  gadgetName: call.gadgetName,
@@ -4874,48 +5512,6 @@ var init_stream_processor = __esm({
4874
5512
  observers.map((observer) => this.safeObserve(observer))
4875
5513
  );
4876
5514
  }
4877
- /**
4878
- * Check if execution can recover from an error.
4879
- *
4880
- * Returns true if we should continue processing subsequent gadgets, false if we should stop.
4881
- *
4882
- * Logic:
4883
- * - If custom canRecoverFromGadgetError is provided, use it
4884
- * - Otherwise, use stopOnGadgetError config:
4885
- * - stopOnGadgetError=true → return false (stop execution)
4886
- * - stopOnGadgetError=false → return true (continue execution)
4887
- */
4888
- async checkCanRecoverFromError(error, gadgetName, errorType, parameters) {
4889
- if (this.canRecoverFromGadgetError) {
4890
- return await this.canRecoverFromGadgetError({
4891
- error,
4892
- gadgetName,
4893
- errorType,
4894
- parameters
4895
- });
4896
- }
4897
- const shouldContinue = !this.stopOnGadgetError;
4898
- this.logger.debug("Checking if should continue after error", {
4899
- error,
4900
- gadgetName,
4901
- errorType,
4902
- stopOnGadgetError: this.stopOnGadgetError,
4903
- shouldContinue
4904
- });
4905
- return shouldContinue;
4906
- }
4907
- /**
4908
- * Determine the type of error from a gadget execution.
4909
- */
4910
- determineErrorType(call, result) {
4911
- if (call.parseError) {
4912
- return "parse";
4913
- }
4914
- if (result.error?.includes("Invalid parameters:")) {
4915
- return "validation";
4916
- }
4917
- return "execution";
4918
- }
4919
5515
  };
4920
5516
  }
4921
5517
  });
@@ -4926,6 +5522,7 @@ var init_agent = __esm({
4926
5522
  "src/agent/agent.ts"() {
4927
5523
  "use strict";
4928
5524
  init_constants();
5525
+ init_execution_tree();
4929
5526
  init_messages();
4930
5527
  init_model_shortcuts();
4931
5528
  init_media_store();
@@ -4953,8 +5550,6 @@ var init_agent = __esm({
4953
5550
  requestHumanInput;
4954
5551
  textOnlyHandler;
4955
5552
  textWithGadgetsHandler;
4956
- stopOnGadgetError;
4957
- canRecoverFromGadgetError;
4958
5553
  defaultGadgetTimeoutMs;
4959
5554
  defaultMaxTokens;
4960
5555
  hasUserPrompt;
@@ -4977,6 +5572,12 @@ var init_agent = __esm({
4977
5572
  pendingSubagentEvents = [];
4978
5573
  // Combined callback that queues events AND calls user callback
4979
5574
  onSubagentEvent;
5575
+ // Counter for generating synthetic invocation IDs for wrapped text content
5576
+ syntheticInvocationCounter = 0;
5577
+ // Execution Tree - first-class model for nested subagent support
5578
+ tree;
5579
+ parentNodeId;
5580
+ baseDepth;
4980
5581
  /**
4981
5582
  * Creates a new Agent instance.
4982
5583
  * @internal This constructor is private. Use LLMist.createAgent() or AgentBuilder instead.
@@ -4999,8 +5600,6 @@ var init_agent = __esm({
4999
5600
  this.requestHumanInput = options.requestHumanInput;
5000
5601
  this.textOnlyHandler = options.textOnlyHandler ?? "terminate";
5001
5602
  this.textWithGadgetsHandler = options.textWithGadgetsHandler;
5002
- this.stopOnGadgetError = options.stopOnGadgetError ?? true;
5003
- this.canRecoverFromGadgetError = options.canRecoverFromGadgetError;
5004
5603
  this.defaultGadgetTimeoutMs = options.defaultGadgetTimeoutMs;
5005
5604
  this.defaultMaxTokens = this.resolveMaxTokensFromCatalog(options.model);
5006
5605
  this.outputLimitEnabled = options.gadgetOutputLimit ?? DEFAULT_GADGET_OUTPUT_LIMIT;
@@ -5054,6 +5653,9 @@ var init_agent = __esm({
5054
5653
  temperature: this.temperature
5055
5654
  };
5056
5655
  this.subagentConfig = options.subagentConfig;
5656
+ this.tree = options.parentTree ?? new ExecutionTree();
5657
+ this.parentNodeId = options.parentNodeId ?? null;
5658
+ this.baseDepth = options.baseDepth ?? 0;
5057
5659
  this.userSubagentEventCallback = options.onSubagentEvent;
5058
5660
  this.onSubagentEvent = (event) => {
5059
5661
  this.pendingSubagentEvents.push(event);
@@ -5118,7 +5720,9 @@ var init_agent = __esm({
5118
5720
  *flushPendingSubagentEvents() {
5119
5721
  while (this.pendingSubagentEvents.length > 0) {
5120
5722
  const event = this.pendingSubagentEvents.shift();
5121
- yield { type: "subagent_event", subagentEvent: event };
5723
+ if (event) {
5724
+ yield { type: "subagent_event", subagentEvent: event };
5725
+ }
5122
5726
  }
5123
5727
  }
5124
5728
  /**
@@ -5172,6 +5776,48 @@ var init_agent = __esm({
5172
5776
  getMediaStore() {
5173
5777
  return this.mediaStore;
5174
5778
  }
5779
+ /**
5780
+ * Get the execution tree for this agent.
5781
+ *
5782
+ * The execution tree provides a first-class model of all LLM calls and gadget executions,
5783
+ * including nested subagent activity. Use this to:
5784
+ * - Query execution state: `tree.getNode(id)`
5785
+ * - Get total cost: `tree.getTotalCost()`
5786
+ * - Get subtree cost/media/tokens: `tree.getSubtreeCost(nodeId)`
5787
+ * - Subscribe to events: `tree.on("llm_call_complete", handler)`
5788
+ * - Stream all events: `for await (const event of tree.events())`
5789
+ *
5790
+ * For subagents (created with `withParentContext`), the tree is shared with the parent,
5791
+ * enabling unified tracking and real-time visibility across all nesting levels.
5792
+ *
5793
+ * @returns The ExecutionTree instance
5794
+ *
5795
+ * @example
5796
+ * ```typescript
5797
+ * const agent = LLMist.createAgent()
5798
+ * .withModel("sonnet")
5799
+ * .withGadgets(BrowseWeb)
5800
+ * .ask("Research topic X");
5801
+ *
5802
+ * for await (const event of agent.run()) {
5803
+ * // Process events...
5804
+ * }
5805
+ *
5806
+ * // After execution, query the tree
5807
+ * const tree = agent.getTree();
5808
+ * console.log(`Total cost: $${tree.getTotalCost().toFixed(4)}`);
5809
+ *
5810
+ * // Inspect all LLM calls
5811
+ * for (const node of tree.getAllNodes()) {
5812
+ * if (node.type === "llm_call") {
5813
+ * console.log(`LLM #${node.iteration}: ${node.model}`);
5814
+ * }
5815
+ * }
5816
+ * ```
5817
+ */
5818
+ getTree() {
5819
+ return this.tree;
5820
+ }
5175
5821
  /**
5176
5822
  * Manually trigger context compaction.
5177
5823
  *
@@ -5273,6 +5919,7 @@ var init_agent = __esm({
5273
5919
  await this.hooks.observers.onCompaction({
5274
5920
  iteration: currentIteration,
5275
5921
  event: compactionEvent,
5922
+ // biome-ignore lint/style/noNonNullAssertion: compactionManager exists if compactionEvent is truthy
5276
5923
  stats: this.compactionManager.getStats(),
5277
5924
  logger: this.logger
5278
5925
  });
@@ -5334,6 +5981,13 @@ var init_agent = __esm({
5334
5981
  messageCount: llmOptions.messages.length,
5335
5982
  messages: llmOptions.messages
5336
5983
  });
5984
+ const llmNode = this.tree.addLLMCall({
5985
+ iteration: currentIteration,
5986
+ model: llmOptions.model,
5987
+ parentId: this.parentNodeId,
5988
+ request: llmOptions.messages
5989
+ });
5990
+ const currentLLMNodeId = llmNode.id;
5337
5991
  const stream2 = this.client.stream(llmOptions);
5338
5992
  const processor = new StreamProcessor({
5339
5993
  iteration: currentIteration,
@@ -5344,14 +5998,17 @@ var init_agent = __esm({
5344
5998
  hooks: this.hooks,
5345
5999
  logger: this.logger.getSubLogger({ name: "stream-processor" }),
5346
6000
  requestHumanInput: this.requestHumanInput,
5347
- stopOnGadgetError: this.stopOnGadgetError,
5348
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
5349
6001
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
5350
6002
  client: this.client,
5351
6003
  mediaStore: this.mediaStore,
5352
6004
  agentConfig: this.agentContextConfig,
5353
6005
  subagentConfig: this.subagentConfig,
5354
- onSubagentEvent: this.onSubagentEvent
6006
+ onSubagentEvent: this.onSubagentEvent,
6007
+ // Tree context for execution tracking
6008
+ tree: this.tree,
6009
+ parentNodeId: currentLLMNodeId,
6010
+ // Gadgets are children of this LLM call
6011
+ baseDepth: this.baseDepth
5355
6012
  });
5356
6013
  let streamMetadata = null;
5357
6014
  let gadgetCallCount = 0;
@@ -5397,6 +6054,11 @@ var init_agent = __esm({
5397
6054
  await this.hooks.observers.onLLMCallComplete(context);
5398
6055
  }
5399
6056
  });
6057
+ this.tree.completeLLMCall(currentLLMNodeId, {
6058
+ response: result.rawResponse,
6059
+ usage: result.usage,
6060
+ finishReason: result.finishReason
6061
+ });
5400
6062
  let finalMessage = result.finalMessage;
5401
6063
  if (this.hooks.controllers?.afterLLMCall) {
5402
6064
  const context = {
@@ -5431,10 +6093,12 @@ var init_agent = __esm({
5431
6093
  const textContent = textOutputs.join("");
5432
6094
  if (textContent.trim()) {
5433
6095
  const { gadgetName, parameterMapping, resultMapping } = this.textWithGadgetsHandler;
6096
+ const syntheticId = `gc_text_${++this.syntheticInvocationCounter}`;
5434
6097
  this.conversation.addGadgetCallResult(
5435
6098
  gadgetName,
5436
6099
  parameterMapping(textContent),
5437
- resultMapping ? resultMapping(textContent) : textContent
6100
+ resultMapping ? resultMapping(textContent) : textContent,
6101
+ syntheticId
5438
6102
  );
5439
6103
  }
5440
6104
  }
@@ -5445,6 +6109,7 @@ var init_agent = __esm({
5445
6109
  gadgetResult.gadgetName,
5446
6110
  gadgetResult.parameters,
5447
6111
  gadgetResult.error ?? gadgetResult.result ?? "",
6112
+ gadgetResult.invocationId,
5448
6113
  gadgetResult.media,
5449
6114
  gadgetResult.mediaIds
5450
6115
  );
@@ -5452,10 +6117,12 @@ var init_agent = __esm({
5452
6117
  }
5453
6118
  } else {
5454
6119
  if (finalMessage.trim()) {
6120
+ const syntheticId = `gc_tell_${++this.syntheticInvocationCounter}`;
5455
6121
  this.conversation.addGadgetCallResult(
5456
6122
  "TellUser",
5457
6123
  { message: finalMessage, done: false, type: "info" },
5458
- `\u2139\uFE0F ${finalMessage}`
6124
+ `\u2139\uFE0F ${finalMessage}`,
6125
+ syntheticId
5459
6126
  );
5460
6127
  }
5461
6128
  const shouldBreak = await this.handleTextOnlyResponse(finalMessage);
@@ -8940,8 +9607,6 @@ var init_builder = __esm({
8940
9607
  gadgetArgPrefix;
8941
9608
  textOnlyHandler;
8942
9609
  textWithGadgetsHandler;
8943
- stopOnGadgetError;
8944
- canRecoverFromGadgetError;
8945
9610
  defaultGadgetTimeoutMs;
8946
9611
  gadgetOutputLimit;
8947
9612
  gadgetOutputLimitPercent;
@@ -8950,6 +9615,8 @@ var init_builder = __esm({
8950
9615
  trailingMessage;
8951
9616
  subagentConfig;
8952
9617
  subagentEventCallback;
9618
+ // Tree context for subagent support - enables shared tree model
9619
+ // When a gadget calls withParentContext(ctx), it shares the parent's tree
8953
9620
  parentContext;
8954
9621
  constructor(client) {
8955
9622
  this.client = client;
@@ -9232,62 +9899,6 @@ var init_builder = __esm({
9232
9899
  this.textWithGadgetsHandler = handler;
9233
9900
  return this;
9234
9901
  }
9235
- /**
9236
- * Set whether to stop gadget execution on first error.
9237
- *
9238
- * When true (default), if a gadget fails:
9239
- * - Subsequent gadgets in the same response are skipped
9240
- * - LLM stream is cancelled to save costs
9241
- * - Agent loop continues with error in context
9242
- *
9243
- * When false:
9244
- * - All gadgets in the response still execute
9245
- * - LLM stream continues to completion
9246
- *
9247
- * @param stop - Whether to stop on gadget error
9248
- * @returns This builder for chaining
9249
- *
9250
- * @example
9251
- * ```typescript
9252
- * .withStopOnGadgetError(false)
9253
- * ```
9254
- */
9255
- withStopOnGadgetError(stop) {
9256
- this.stopOnGadgetError = stop;
9257
- return this;
9258
- }
9259
- /**
9260
- * Set custom error handling logic.
9261
- *
9262
- * Provides fine-grained control over whether to continue after different types of errors.
9263
- * Overrides `stopOnGadgetError` when provided.
9264
- *
9265
- * **Note:** This builder method configures the underlying `canRecoverFromGadgetError` option
9266
- * in `AgentOptions`. The method is named `withErrorHandler` for better developer experience,
9267
- * but maps to the `canRecoverFromGadgetError` property internally.
9268
- *
9269
- * @param handler - Function that decides whether to continue after an error.
9270
- * Return `true` to continue execution, `false` to stop.
9271
- * @returns This builder for chaining
9272
- *
9273
- * @example
9274
- * ```typescript
9275
- * .withErrorHandler((context) => {
9276
- * // Stop on parse errors, continue on validation/execution errors
9277
- * if (context.errorType === "parse") {
9278
- * return false;
9279
- * }
9280
- * if (context.error.includes("CRITICAL")) {
9281
- * return false;
9282
- * }
9283
- * return true;
9284
- * })
9285
- * ```
9286
- */
9287
- withErrorHandler(handler) {
9288
- this.canRecoverFromGadgetError = handler;
9289
- return this;
9290
- }
9291
9902
  /**
9292
9903
  * Set default timeout for gadget execution.
9293
9904
  *
@@ -9482,6 +10093,15 @@ var init_builder = __esm({
9482
10093
  * The method extracts `invocationId` and `onSubagentEvent` from the execution
9483
10094
  * context and sets up automatic forwarding via hooks and event wrapping.
9484
10095
  *
10096
+ * **NEW: Shared Tree Model** - When the parent provides an ExecutionTree via context,
10097
+ * the subagent shares that tree instead of creating its own. This enables:
10098
+ * - Unified cost tracking across all nesting levels
10099
+ * - Automatic media aggregation via `tree.getSubtreeMedia(nodeId)`
10100
+ * - Real-time visibility of nested execution in the parent
10101
+ *
10102
+ * **Signal Forwarding** - When parent context includes a signal, it's automatically
10103
+ * forwarded to the subagent for proper cancellation propagation.
10104
+ *
9485
10105
  * @param ctx - ExecutionContext passed to the gadget's execute() method
9486
10106
  * @param depth - Nesting depth (default: 1 for direct child)
9487
10107
  * @returns This builder for chaining
@@ -9502,17 +10122,25 @@ var init_builder = __esm({
9502
10122
  * result = event.content;
9503
10123
  * }
9504
10124
  * }
10125
+ *
10126
+ * // After subagent completes, costs are automatically aggregated
10127
+ * // No manual tracking needed - use tree methods:
10128
+ * const totalCost = ctx.tree?.getSubtreeCost(ctx.nodeId!);
10129
+ * const allMedia = ctx.tree?.getSubtreeMedia(ctx.nodeId!);
9505
10130
  * }
9506
10131
  * ```
9507
10132
  */
9508
10133
  withParentContext(ctx, depth = 1) {
9509
- if (ctx.onSubagentEvent && ctx.invocationId) {
10134
+ if (ctx.tree) {
9510
10135
  this.parentContext = {
9511
- invocationId: ctx.invocationId,
9512
- onSubagentEvent: ctx.onSubagentEvent,
10136
+ tree: ctx.tree,
10137
+ nodeId: ctx.nodeId,
9513
10138
  depth
9514
10139
  };
9515
10140
  }
10141
+ if (ctx.signal && !this.signal) {
10142
+ this.signal = ctx.signal;
10143
+ }
9516
10144
  return this;
9517
10145
  }
9518
10146
  /**
@@ -9545,11 +10173,13 @@ var init_builder = __esm({
9545
10173
  *
9546
10174
  * This is useful for in-context learning - showing the LLM what "past self"
9547
10175
  * did correctly so it mimics the pattern. The call is formatted with proper
9548
- * markers and parameter format.
10176
+ * markers and parameter format, including the invocation ID so the LLM can
10177
+ * reference previous calls when building dependencies.
9549
10178
  *
9550
10179
  * @param gadgetName - Name of the gadget
9551
10180
  * @param parameters - Parameters passed to the gadget
9552
10181
  * @param result - Result returned by the gadget
10182
+ * @param invocationId - Invocation ID (shown to LLM so it can reference for dependencies)
9553
10183
  * @returns This builder for chaining
9554
10184
  *
9555
10185
  * @example
@@ -9561,124 +10191,36 @@ var init_builder = __esm({
9561
10191
  * done: false,
9562
10192
  * type: 'info'
9563
10193
  * },
9564
- * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands'
10194
+ * 'ℹ️ 👋 Hello!\n\nHere\'s what I can do:\n- Analyze code\n- Run commands',
10195
+ * 'gc_1'
9565
10196
  * )
9566
10197
  * ```
9567
10198
  */
9568
- withSyntheticGadgetCall(gadgetName, parameters, result) {
10199
+ withSyntheticGadgetCall(gadgetName, parameters, result, invocationId) {
9569
10200
  const startPrefix = this.gadgetStartPrefix ?? GADGET_START_PREFIX;
9570
10201
  const endPrefix = this.gadgetEndPrefix ?? GADGET_END_PREFIX;
9571
10202
  const paramStr = this.formatBlockParameters(parameters, "");
9572
10203
  this.initialMessages.push({
9573
10204
  role: "assistant",
9574
- content: `${startPrefix}${gadgetName}
10205
+ content: `${startPrefix}${gadgetName}:${invocationId}
9575
10206
  ${paramStr}
9576
10207
  ${endPrefix}`
9577
10208
  });
9578
10209
  this.initialMessages.push({
9579
10210
  role: "user",
9580
- content: `Result: ${result}`
10211
+ content: `Result (${invocationId}): ${result}`
9581
10212
  });
9582
10213
  return this;
9583
10214
  }
9584
10215
  /**
9585
- * Compose the final hooks, including:
9586
- * - Trailing message injection (if configured)
9587
- * - Subagent event forwarding for LLM calls (if parentContext is set)
10216
+ * Compose the final hooks, including trailing message injection if configured.
10217
+ *
10218
+ * Note: Subagent event visibility is now handled entirely by the ExecutionTree.
10219
+ * When a subagent uses withParentContext(ctx), it shares the parent's tree,
10220
+ * and all events are automatically visible to tree subscribers (like the TUI).
9588
10221
  */
9589
10222
  composeHooks() {
9590
- let hooks = this.hooks;
9591
- if (this.parentContext) {
9592
- const { invocationId, onSubagentEvent, depth } = this.parentContext;
9593
- const existingOnLLMCallStart = hooks?.observers?.onLLMCallStart;
9594
- const existingOnLLMCallComplete = hooks?.observers?.onLLMCallComplete;
9595
- const existingOnGadgetExecutionStart = hooks?.observers?.onGadgetExecutionStart;
9596
- const existingOnGadgetExecutionComplete = hooks?.observers?.onGadgetExecutionComplete;
9597
- hooks = {
9598
- ...hooks,
9599
- observers: {
9600
- ...hooks?.observers,
9601
- onLLMCallStart: async (context) => {
9602
- let inputTokens;
9603
- try {
9604
- if (this.client) {
9605
- inputTokens = await this.client.countTokens(
9606
- context.options.model,
9607
- context.options.messages
9608
- );
9609
- }
9610
- } catch {
9611
- }
9612
- onSubagentEvent({
9613
- type: "llm_call_start",
9614
- gadgetInvocationId: invocationId,
9615
- depth,
9616
- event: {
9617
- iteration: context.iteration,
9618
- model: context.options.model,
9619
- inputTokens
9620
- }
9621
- });
9622
- if (existingOnLLMCallStart) {
9623
- await existingOnLLMCallStart(context);
9624
- }
9625
- },
9626
- onLLMCallComplete: async (context) => {
9627
- onSubagentEvent({
9628
- type: "llm_call_end",
9629
- gadgetInvocationId: invocationId,
9630
- depth,
9631
- event: {
9632
- iteration: context.iteration,
9633
- model: context.options.model,
9634
- // Backward compat fields
9635
- inputTokens: context.usage?.inputTokens,
9636
- outputTokens: context.usage?.outputTokens,
9637
- finishReason: context.finishReason ?? void 0,
9638
- // Full usage object with cache details (for first-class display)
9639
- usage: context.usage
9640
- // Cost will be calculated by parent if it has model registry
9641
- }
9642
- });
9643
- if (existingOnLLMCallComplete) {
9644
- await existingOnLLMCallComplete(context);
9645
- }
9646
- },
9647
- onGadgetExecutionStart: async (context) => {
9648
- onSubagentEvent({
9649
- type: "gadget_call",
9650
- gadgetInvocationId: invocationId,
9651
- depth,
9652
- event: {
9653
- call: {
9654
- invocationId: context.invocationId,
9655
- gadgetName: context.gadgetName,
9656
- parameters: context.parameters
9657
- }
9658
- }
9659
- });
9660
- if (existingOnGadgetExecutionStart) {
9661
- await existingOnGadgetExecutionStart(context);
9662
- }
9663
- },
9664
- onGadgetExecutionComplete: async (context) => {
9665
- onSubagentEvent({
9666
- type: "gadget_result",
9667
- gadgetInvocationId: invocationId,
9668
- depth,
9669
- event: {
9670
- result: {
9671
- invocationId: context.invocationId
9672
- }
9673
- }
9674
- });
9675
- if (existingOnGadgetExecutionComplete) {
9676
- await existingOnGadgetExecutionComplete(context);
9677
- }
9678
- }
9679
- }
9680
- };
9681
- }
10223
+ const hooks = this.hooks;
9682
10224
  if (!this.trailingMessage) {
9683
10225
  return hooks;
9684
10226
  }
@@ -9761,19 +10303,6 @@ ${endPrefix}`
9761
10303
  this.client = new LLMistClass();
9762
10304
  }
9763
10305
  const registry = GadgetRegistry.from(this.gadgets);
9764
- let onSubagentEvent = this.subagentEventCallback;
9765
- if (this.parentContext) {
9766
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
9767
- const existingCallback = this.subagentEventCallback;
9768
- onSubagentEvent = (event) => {
9769
- parentCallback({
9770
- ...event,
9771
- gadgetInvocationId: invocationId,
9772
- depth: event.depth + depth
9773
- });
9774
- existingCallback?.(event);
9775
- };
9776
- }
9777
10306
  return {
9778
10307
  client: this.client,
9779
10308
  model: this.model ?? "openai:gpt-5-nano",
@@ -9792,15 +10321,17 @@ ${endPrefix}`
9792
10321
  gadgetArgPrefix: this.gadgetArgPrefix,
9793
10322
  textOnlyHandler: this.textOnlyHandler,
9794
10323
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9795
- stopOnGadgetError: this.stopOnGadgetError,
9796
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9797
10324
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9798
10325
  gadgetOutputLimit: this.gadgetOutputLimit,
9799
10326
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9800
10327
  compactionConfig: this.compactionConfig,
9801
10328
  signal: this.signal,
9802
10329
  subagentConfig: this.subagentConfig,
9803
- onSubagentEvent
10330
+ onSubagentEvent: this.subagentEventCallback,
10331
+ // Tree context for shared tree model (subagents share parent's tree)
10332
+ parentTree: this.parentContext?.tree,
10333
+ parentNodeId: this.parentContext?.nodeId,
10334
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
9804
10335
  };
9805
10336
  }
9806
10337
  ask(userPrompt) {
@@ -9957,19 +10488,6 @@ ${endPrefix}`
9957
10488
  this.client = new LLMistClass();
9958
10489
  }
9959
10490
  const registry = GadgetRegistry.from(this.gadgets);
9960
- let onSubagentEvent = this.subagentEventCallback;
9961
- if (this.parentContext) {
9962
- const { invocationId, onSubagentEvent: parentCallback, depth } = this.parentContext;
9963
- const existingCallback = this.subagentEventCallback;
9964
- onSubagentEvent = (event) => {
9965
- parentCallback({
9966
- ...event,
9967
- gadgetInvocationId: invocationId,
9968
- depth: event.depth + depth
9969
- });
9970
- existingCallback?.(event);
9971
- };
9972
- }
9973
10491
  const options = {
9974
10492
  client: this.client,
9975
10493
  model: this.model ?? "openai:gpt-5-nano",
@@ -9988,15 +10506,17 @@ ${endPrefix}`
9988
10506
  gadgetArgPrefix: this.gadgetArgPrefix,
9989
10507
  textOnlyHandler: this.textOnlyHandler,
9990
10508
  textWithGadgetsHandler: this.textWithGadgetsHandler,
9991
- stopOnGadgetError: this.stopOnGadgetError,
9992
- canRecoverFromGadgetError: this.canRecoverFromGadgetError,
9993
10509
  defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
9994
10510
  gadgetOutputLimit: this.gadgetOutputLimit,
9995
10511
  gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
9996
10512
  compactionConfig: this.compactionConfig,
9997
10513
  signal: this.signal,
9998
10514
  subagentConfig: this.subagentConfig,
9999
- onSubagentEvent
10515
+ onSubagentEvent: this.subagentEventCallback,
10516
+ // Tree context for shared tree model (subagents share parent's tree)
10517
+ parentTree: this.parentContext?.tree,
10518
+ parentNodeId: this.parentContext?.nodeId,
10519
+ baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
10000
10520
  };
10001
10521
  return new Agent(AGENT_INTERNAL_KEY, options);
10002
10522
  }
@@ -10017,6 +10537,7 @@ __export(index_exports, {
10017
10537
  DEFAULT_HINTS: () => DEFAULT_HINTS,
10018
10538
  DEFAULT_PROMPTS: () => DEFAULT_PROMPTS,
10019
10539
  DEFAULT_SUMMARIZATION_PROMPT: () => DEFAULT_SUMMARIZATION_PROMPT,
10540
+ ExecutionTree: () => ExecutionTree,
10020
10541
  Gadget: () => Gadget,
10021
10542
  GadgetCallParser: () => GadgetCallParser,
10022
10543
  GadgetExecutor: () => GadgetExecutor,
@@ -10063,26 +10584,37 @@ __export(index_exports, {
10063
10584
  detectImageMimeType: () => detectImageMimeType,
10064
10585
  discoverProviderAdapters: () => discoverProviderAdapters,
10065
10586
  extractMessageText: () => extractMessageText,
10587
+ filterByDepth: () => filterByDepth,
10588
+ filterByParent: () => filterByParent,
10589
+ filterRootEvents: () => filterRootEvents,
10066
10590
  getMockManager: () => getMockManager,
10067
10591
  getModelId: () => getModelId,
10068
10592
  getProvider: () => getProvider,
10593
+ groupByParent: () => groupByParent,
10069
10594
  hasProviderPrefix: () => hasProviderPrefix,
10070
10595
  imageFromBase64: () => imageFromBase64,
10071
10596
  imageFromBuffer: () => imageFromBuffer,
10072
10597
  imageFromUrl: () => imageFromUrl,
10073
10598
  isAudioPart: () => isAudioPart,
10074
10599
  isDataUrl: () => isDataUrl,
10600
+ isGadgetEvent: () => isGadgetEvent,
10075
10601
  isImagePart: () => isImagePart,
10602
+ isLLMEvent: () => isLLMEvent,
10603
+ isRootEvent: () => isRootEvent,
10604
+ isSubagentEvent: () => isSubagentEvent,
10076
10605
  isTextPart: () => isTextPart,
10077
10606
  iterationProgressHint: () => iterationProgressHint,
10078
10607
  mockLLM: () => mockLLM,
10079
10608
  normalizeMessageContent: () => normalizeMessageContent,
10080
10609
  parallelGadgetHint: () => parallelGadgetHint,
10081
10610
  parseDataUrl: () => parseDataUrl,
10611
+ resolveConfig: () => resolveConfig,
10082
10612
  resolveHintTemplate: () => resolveHintTemplate,
10083
10613
  resolveModel: () => resolveModel,
10084
10614
  resolvePromptTemplate: () => resolvePromptTemplate,
10085
10615
  resolveRulesTemplate: () => resolveRulesTemplate,
10616
+ resolveSubagentModel: () => resolveSubagentModel,
10617
+ resolveValue: () => resolveValue,
10086
10618
  resultWithAudio: () => resultWithAudio,
10087
10619
  resultWithFile: () => resultWithFile,
10088
10620
  resultWithImage: () => resultWithImage,
@@ -10988,6 +11520,43 @@ init_stream_processor();
10988
11520
 
10989
11521
  // src/index.ts
10990
11522
  init_client();
11523
+ init_execution_tree();
11524
+
11525
+ // src/core/execution-events.ts
11526
+ function isLLMEvent(event) {
11527
+ return event.type.startsWith("llm_call_");
11528
+ }
11529
+ function isGadgetEvent(event) {
11530
+ return event.type.startsWith("gadget_");
11531
+ }
11532
+ function isSubagentEvent(event) {
11533
+ return event.depth > 0;
11534
+ }
11535
+ function isRootEvent(event) {
11536
+ return event.depth === 0;
11537
+ }
11538
+ function filterByDepth(events, depth) {
11539
+ return events.filter((e) => e.depth === depth);
11540
+ }
11541
+ function filterByParent(events, parentId) {
11542
+ return events.filter((e) => e.parentId === parentId);
11543
+ }
11544
+ function filterRootEvents(events) {
11545
+ return filterByDepth(events, 0);
11546
+ }
11547
+ function groupByParent(events) {
11548
+ const groups = /* @__PURE__ */ new Map();
11549
+ for (const event of events) {
11550
+ const parentId = event.parentId;
11551
+ if (!groups.has(parentId)) {
11552
+ groups.set(parentId, []);
11553
+ }
11554
+ groups.get(parentId)?.push(event);
11555
+ }
11556
+ return groups;
11557
+ }
11558
+
11559
+ // src/index.ts
10991
11560
  init_input_content();
10992
11561
  init_messages();
10993
11562
  init_model_registry();
@@ -11169,6 +11738,52 @@ function validateGadgetParams(gadget, params) {
11169
11738
 
11170
11739
  // src/index.ts
11171
11740
  init_logger();
11741
+
11742
+ // src/utils/config-resolver.ts
11743
+ function resolveValue(ctx, gadgetName, options) {
11744
+ const { runtime, subagentKey, parentKey, defaultValue, handleInherit } = options;
11745
+ if (runtime !== void 0) {
11746
+ if (handleInherit && runtime === "inherit") {
11747
+ } else {
11748
+ return runtime;
11749
+ }
11750
+ }
11751
+ if (subagentKey && ctx.subagentConfig) {
11752
+ const subagentCfg = ctx.subagentConfig[gadgetName];
11753
+ if (subagentCfg && subagentKey in subagentCfg) {
11754
+ const value = subagentCfg[subagentKey];
11755
+ if (handleInherit && value === "inherit") {
11756
+ } else if (value !== void 0) {
11757
+ return value;
11758
+ }
11759
+ }
11760
+ }
11761
+ if (parentKey && ctx.agentConfig && parentKey in ctx.agentConfig) {
11762
+ const parentValue = ctx.agentConfig[parentKey];
11763
+ if (parentValue !== void 0) {
11764
+ return parentValue;
11765
+ }
11766
+ }
11767
+ return defaultValue;
11768
+ }
11769
+ function resolveConfig(ctx, gadgetName, config) {
11770
+ const result = {};
11771
+ for (const [key, options] of Object.entries(config)) {
11772
+ result[key] = resolveValue(ctx, gadgetName, options);
11773
+ }
11774
+ return result;
11775
+ }
11776
+ function resolveSubagentModel(ctx, gadgetName, runtimeModel, defaultModel) {
11777
+ return resolveValue(ctx, gadgetName, {
11778
+ runtime: runtimeModel,
11779
+ subagentKey: "model",
11780
+ parentKey: "model",
11781
+ defaultValue: defaultModel,
11782
+ handleInherit: true
11783
+ });
11784
+ }
11785
+
11786
+ // src/index.ts
11172
11787
  init_anthropic();
11173
11788
  init_discovery();
11174
11789
  init_gemini();
@@ -12183,6 +12798,7 @@ init_gadget();
12183
12798
  DEFAULT_HINTS,
12184
12799
  DEFAULT_PROMPTS,
12185
12800
  DEFAULT_SUMMARIZATION_PROMPT,
12801
+ ExecutionTree,
12186
12802
  Gadget,
12187
12803
  GadgetCallParser,
12188
12804
  GadgetExecutor,
@@ -12229,26 +12845,37 @@ init_gadget();
12229
12845
  detectImageMimeType,
12230
12846
  discoverProviderAdapters,
12231
12847
  extractMessageText,
12848
+ filterByDepth,
12849
+ filterByParent,
12850
+ filterRootEvents,
12232
12851
  getMockManager,
12233
12852
  getModelId,
12234
12853
  getProvider,
12854
+ groupByParent,
12235
12855
  hasProviderPrefix,
12236
12856
  imageFromBase64,
12237
12857
  imageFromBuffer,
12238
12858
  imageFromUrl,
12239
12859
  isAudioPart,
12240
12860
  isDataUrl,
12861
+ isGadgetEvent,
12241
12862
  isImagePart,
12863
+ isLLMEvent,
12864
+ isRootEvent,
12865
+ isSubagentEvent,
12242
12866
  isTextPart,
12243
12867
  iterationProgressHint,
12244
12868
  mockLLM,
12245
12869
  normalizeMessageContent,
12246
12870
  parallelGadgetHint,
12247
12871
  parseDataUrl,
12872
+ resolveConfig,
12248
12873
  resolveHintTemplate,
12249
12874
  resolveModel,
12250
12875
  resolvePromptTemplate,
12251
12876
  resolveRulesTemplate,
12877
+ resolveSubagentModel,
12878
+ resolveValue,
12252
12879
  resultWithAudio,
12253
12880
  resultWithFile,
12254
12881
  resultWithImage,