llmist 16.0.2 → 16.0.4
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 +917 -599
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +76 -66
- package/dist/index.d.ts +76 -66
- package/dist/index.js +916 -600
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -658,11 +658,11 @@ var init_execution_tree = __esm({
|
|
|
658
658
|
yield this.eventQueue.shift();
|
|
659
659
|
}
|
|
660
660
|
if (this.isCompleted) break;
|
|
661
|
-
const event = await new Promise((
|
|
661
|
+
const event = await new Promise((resolve2) => {
|
|
662
662
|
if (this.eventQueue.length > 0) {
|
|
663
|
-
|
|
663
|
+
resolve2(this.eventQueue.shift());
|
|
664
664
|
} else {
|
|
665
|
-
this.eventWaiters.push(
|
|
665
|
+
this.eventWaiters.push(resolve2);
|
|
666
666
|
}
|
|
667
667
|
});
|
|
668
668
|
yield event;
|
|
@@ -3978,6 +3978,23 @@ var init_hook_validators = __esm({
|
|
|
3978
3978
|
}
|
|
3979
3979
|
});
|
|
3980
3980
|
|
|
3981
|
+
// src/agent/safe-observe.ts
|
|
3982
|
+
async function safeObserve(fn, logger2, label) {
|
|
3983
|
+
try {
|
|
3984
|
+
await fn();
|
|
3985
|
+
} catch (error) {
|
|
3986
|
+
const message = label ? `Observer error in ${label}:` : "Observer threw error (ignoring)";
|
|
3987
|
+
logger2.error(message, {
|
|
3988
|
+
error: error instanceof Error ? error.message : String(error)
|
|
3989
|
+
});
|
|
3990
|
+
}
|
|
3991
|
+
}
|
|
3992
|
+
var init_safe_observe = __esm({
|
|
3993
|
+
"src/agent/safe-observe.ts"() {
|
|
3994
|
+
"use strict";
|
|
3995
|
+
}
|
|
3996
|
+
});
|
|
3997
|
+
|
|
3981
3998
|
// src/gadgets/registry.ts
|
|
3982
3999
|
var GadgetRegistry;
|
|
3983
4000
|
var init_registry = __esm({
|
|
@@ -4084,6 +4101,62 @@ var init_registry = __esm({
|
|
|
4084
4101
|
});
|
|
4085
4102
|
|
|
4086
4103
|
// src/agent/file-logging.ts
|
|
4104
|
+
function createFileLoggingState() {
|
|
4105
|
+
return {
|
|
4106
|
+
counters: /* @__PURE__ */ new Map(),
|
|
4107
|
+
subagentDirectories: /* @__PURE__ */ new Map(),
|
|
4108
|
+
activeDirectoryByContext: /* @__PURE__ */ new Map()
|
|
4109
|
+
};
|
|
4110
|
+
}
|
|
4111
|
+
function getDefaultState() {
|
|
4112
|
+
if (!defaultState) {
|
|
4113
|
+
defaultState = createFileLoggingState();
|
|
4114
|
+
}
|
|
4115
|
+
return defaultState;
|
|
4116
|
+
}
|
|
4117
|
+
function getNextCounter(state, directory) {
|
|
4118
|
+
const current = state.counters.get(directory) ?? 0;
|
|
4119
|
+
const next = current + 1;
|
|
4120
|
+
state.counters.set(directory, next);
|
|
4121
|
+
return next;
|
|
4122
|
+
}
|
|
4123
|
+
function getContextKey(subagentContext) {
|
|
4124
|
+
if (!subagentContext) return "root:0";
|
|
4125
|
+
return `${subagentContext.parentGadgetInvocationId}:${subagentContext.depth}`;
|
|
4126
|
+
}
|
|
4127
|
+
function resetFileLoggingState() {
|
|
4128
|
+
defaultState = void 0;
|
|
4129
|
+
}
|
|
4130
|
+
function findParentContextKey(state, currentDepth) {
|
|
4131
|
+
const parentDepth = currentDepth - 1;
|
|
4132
|
+
if (parentDepth === 0) return "root:0";
|
|
4133
|
+
for (const [key, _path] of state.activeDirectoryByContext) {
|
|
4134
|
+
if (key.endsWith(`:${parentDepth}`)) {
|
|
4135
|
+
return key;
|
|
4136
|
+
}
|
|
4137
|
+
}
|
|
4138
|
+
return "root:0";
|
|
4139
|
+
}
|
|
4140
|
+
function resolveLoggingDirectory(state, baseDirectory, counterPadding, subagentContext) {
|
|
4141
|
+
const contextKey = getContextKey(subagentContext);
|
|
4142
|
+
if (!subagentContext) {
|
|
4143
|
+
state.activeDirectoryByContext.set(contextKey, baseDirectory);
|
|
4144
|
+
return baseDirectory;
|
|
4145
|
+
}
|
|
4146
|
+
const { parentGadgetInvocationId, depth } = subagentContext;
|
|
4147
|
+
const parentContextKey = findParentContextKey(state, depth);
|
|
4148
|
+
const parentDir = state.activeDirectoryByContext.get(parentContextKey) ?? baseDirectory;
|
|
4149
|
+
const subagentKey = `${parentDir}:${parentGadgetInvocationId}`;
|
|
4150
|
+
let fullPath = state.subagentDirectories.get(subagentKey);
|
|
4151
|
+
if (!fullPath) {
|
|
4152
|
+
const chronoNumber = getNextCounter(state, parentDir);
|
|
4153
|
+
const subdirName = `${formatCallNumber(chronoNumber, counterPadding)}-${parentGadgetInvocationId}`;
|
|
4154
|
+
fullPath = (0, import_node_path3.join)(parentDir, subdirName);
|
|
4155
|
+
state.subagentDirectories.set(subagentKey, fullPath);
|
|
4156
|
+
}
|
|
4157
|
+
state.activeDirectoryByContext.set(contextKey, fullPath);
|
|
4158
|
+
return fullPath;
|
|
4159
|
+
}
|
|
4087
4160
|
function formatLlmRequest(messages) {
|
|
4088
4161
|
const lines = [];
|
|
4089
4162
|
for (const msg of messages) {
|
|
@@ -4100,16 +4173,20 @@ async function writeLogFile(dir, filename, content) {
|
|
|
4100
4173
|
await (0, import_promises2.mkdir)(dir, { recursive: true });
|
|
4101
4174
|
await (0, import_promises2.writeFile)((0, import_node_path3.join)(dir, filename), content, "utf-8");
|
|
4102
4175
|
}
|
|
4103
|
-
function createFileLoggingHooks(options) {
|
|
4176
|
+
function createFileLoggingHooks(options, state = getDefaultState()) {
|
|
4104
4177
|
const {
|
|
4105
|
-
directory,
|
|
4106
4178
|
startingCounter = 1,
|
|
4107
4179
|
counterPadding = 4,
|
|
4108
4180
|
skipSubagents = true,
|
|
4109
4181
|
formatRequest = formatLlmRequest,
|
|
4110
4182
|
onFileWritten
|
|
4111
4183
|
} = options;
|
|
4112
|
-
|
|
4184
|
+
const baseDirectory = (0, import_node_path3.resolve)(options.directory);
|
|
4185
|
+
if (!state.counters.has(baseDirectory)) {
|
|
4186
|
+
state.counters.set(baseDirectory, startingCounter - 1);
|
|
4187
|
+
}
|
|
4188
|
+
let currentCallNumber = 0;
|
|
4189
|
+
let currentDirectory = baseDirectory;
|
|
4113
4190
|
return {
|
|
4114
4191
|
observers: {
|
|
4115
4192
|
/**
|
|
@@ -4119,17 +4196,25 @@ function createFileLoggingHooks(options) {
|
|
|
4119
4196
|
if (skipSubagents && context.subagentContext) {
|
|
4120
4197
|
return;
|
|
4121
4198
|
}
|
|
4122
|
-
|
|
4123
|
-
|
|
4199
|
+
currentDirectory = resolveLoggingDirectory(
|
|
4200
|
+
state,
|
|
4201
|
+
baseDirectory,
|
|
4202
|
+
counterPadding,
|
|
4203
|
+
context.subagentContext
|
|
4204
|
+
);
|
|
4205
|
+
currentCallNumber = getNextCounter(state, currentDirectory);
|
|
4206
|
+
const filename = `${formatCallNumber(currentCallNumber, counterPadding)}.request`;
|
|
4124
4207
|
const content = formatRequest(context.options.messages);
|
|
4125
4208
|
try {
|
|
4126
|
-
await writeLogFile(
|
|
4209
|
+
await writeLogFile(currentDirectory, filename, content);
|
|
4127
4210
|
if (onFileWritten) {
|
|
4128
4211
|
onFileWritten({
|
|
4129
|
-
filePath: (0, import_node_path3.join)(
|
|
4212
|
+
filePath: (0, import_node_path3.join)(currentDirectory, filename),
|
|
4130
4213
|
type: "request",
|
|
4131
|
-
callNumber:
|
|
4132
|
-
contentLength: content.length
|
|
4214
|
+
callNumber: currentCallNumber,
|
|
4215
|
+
contentLength: content.length,
|
|
4216
|
+
parentGadgetInvocationId: context.subagentContext?.parentGadgetInvocationId,
|
|
4217
|
+
depth: context.subagentContext?.depth
|
|
4133
4218
|
});
|
|
4134
4219
|
}
|
|
4135
4220
|
} catch (error) {
|
|
@@ -4143,16 +4228,22 @@ function createFileLoggingHooks(options) {
|
|
|
4143
4228
|
if (skipSubagents && context.subagentContext) {
|
|
4144
4229
|
return;
|
|
4145
4230
|
}
|
|
4146
|
-
|
|
4231
|
+
if (currentCallNumber === 0) {
|
|
4232
|
+
console.warn("[file-logging] Skipping response write: no matching request recorded");
|
|
4233
|
+
return;
|
|
4234
|
+
}
|
|
4235
|
+
const filename = `${formatCallNumber(currentCallNumber, counterPadding)}.response`;
|
|
4147
4236
|
const content = context.rawResponse;
|
|
4148
4237
|
try {
|
|
4149
|
-
await writeLogFile(
|
|
4238
|
+
await writeLogFile(currentDirectory, filename, content);
|
|
4150
4239
|
if (onFileWritten) {
|
|
4151
4240
|
onFileWritten({
|
|
4152
|
-
filePath: (0, import_node_path3.join)(
|
|
4241
|
+
filePath: (0, import_node_path3.join)(currentDirectory, filename),
|
|
4153
4242
|
type: "response",
|
|
4154
|
-
callNumber:
|
|
4155
|
-
contentLength: content.length
|
|
4243
|
+
callNumber: currentCallNumber,
|
|
4244
|
+
contentLength: content.length,
|
|
4245
|
+
parentGadgetInvocationId: context.subagentContext?.parentGadgetInvocationId,
|
|
4246
|
+
depth: context.subagentContext?.depth
|
|
4156
4247
|
});
|
|
4157
4248
|
}
|
|
4158
4249
|
} catch (error) {
|
|
@@ -4169,7 +4260,7 @@ function getEnvFileLoggingHooks() {
|
|
|
4169
4260
|
}
|
|
4170
4261
|
return createFileLoggingHooks({ directory });
|
|
4171
4262
|
}
|
|
4172
|
-
var import_promises2, import_node_path3, ENV_LOG_RAW_DIRECTORY;
|
|
4263
|
+
var import_promises2, import_node_path3, defaultState, ENV_LOG_RAW_DIRECTORY;
|
|
4173
4264
|
var init_file_logging = __esm({
|
|
4174
4265
|
"src/agent/file-logging.ts"() {
|
|
4175
4266
|
"use strict";
|
|
@@ -4358,6 +4449,8 @@ var init_hook_presets = __esm({
|
|
|
4358
4449
|
/**
|
|
4359
4450
|
* Tracks cumulative token usage across all LLM calls.
|
|
4360
4451
|
*
|
|
4452
|
+
* @public
|
|
4453
|
+
*
|
|
4361
4454
|
* **Output:**
|
|
4362
4455
|
* - Per-call token count with 📊 emoji
|
|
4363
4456
|
* - Cumulative total across all calls
|
|
@@ -4620,6 +4713,8 @@ var init_hook_presets = __esm({
|
|
|
4620
4713
|
/**
|
|
4621
4714
|
* Logs detailed error information for debugging and troubleshooting.
|
|
4622
4715
|
*
|
|
4716
|
+
* @public
|
|
4717
|
+
*
|
|
4623
4718
|
* **Output:**
|
|
4624
4719
|
* - LLM errors with ❌ emoji, including model and recovery status
|
|
4625
4720
|
* - Gadget errors with full context (parameters, error message)
|
|
@@ -4698,6 +4793,8 @@ var init_hook_presets = __esm({
|
|
|
4698
4793
|
/**
|
|
4699
4794
|
* Tracks context compaction events.
|
|
4700
4795
|
*
|
|
4796
|
+
* @public
|
|
4797
|
+
*
|
|
4701
4798
|
* **Output:**
|
|
4702
4799
|
* - Compaction events with 🗜️ emoji
|
|
4703
4800
|
* - Strategy name, tokens before/after, and savings
|
|
@@ -4811,6 +4908,8 @@ var init_hook_presets = __esm({
|
|
|
4811
4908
|
/**
|
|
4812
4909
|
* Returns empty hook configuration for clean output without any logging.
|
|
4813
4910
|
*
|
|
4911
|
+
* @public
|
|
4912
|
+
*
|
|
4814
4913
|
* **Output:**
|
|
4815
4914
|
* - None. Returns {} (empty object).
|
|
4816
4915
|
*
|
|
@@ -4968,6 +5067,8 @@ var init_hook_presets = __esm({
|
|
|
4968
5067
|
/**
|
|
4969
5068
|
* Composite preset combining logging, timing, tokenTracking, and errorLogging.
|
|
4970
5069
|
*
|
|
5070
|
+
* @public
|
|
5071
|
+
*
|
|
4971
5072
|
* This is the recommended preset for development and initial production deployments,
|
|
4972
5073
|
* providing comprehensive observability with a single method call.
|
|
4973
5074
|
*
|
|
@@ -11918,7 +12019,9 @@ ${endPrefix}`
|
|
|
11918
12019
|
*/
|
|
11919
12020
|
/**
|
|
11920
12021
|
* Build AgentOptions with the given user prompt.
|
|
11921
|
-
* Centralizes options construction for ask(), askWithImage(), and
|
|
12022
|
+
* Centralizes options construction for ask(), askWithImage(), askWithContent(), and build().
|
|
12023
|
+
*
|
|
12024
|
+
* @param userPrompt - Optional user prompt (omitted for build() which has no prompt)
|
|
11922
12025
|
*/
|
|
11923
12026
|
buildAgentOptions(userPrompt) {
|
|
11924
12027
|
if (!this.client) {
|
|
@@ -11960,7 +12063,12 @@ ${endPrefix}`
|
|
|
11960
12063
|
// Tree context for shared tree model (subagents share parent's tree)
|
|
11961
12064
|
parentTree: this.parentContext?.tree,
|
|
11962
12065
|
parentNodeId: this.parentContext?.nodeId,
|
|
11963
|
-
baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0
|
|
12066
|
+
baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0,
|
|
12067
|
+
// Parent observer hooks for subagent visibility
|
|
12068
|
+
parentObservers: this.parentObservers,
|
|
12069
|
+
// Shared rate limit tracker and retry config (for coordinated limits across subagents)
|
|
12070
|
+
sharedRateLimitTracker: this.sharedRateLimitTracker,
|
|
12071
|
+
sharedRetryConfig: this.sharedRetryConfig
|
|
11964
12072
|
};
|
|
11965
12073
|
}
|
|
11966
12074
|
ask(userPrompt) {
|
|
@@ -12112,52 +12220,7 @@ ${endPrefix}`
|
|
|
12112
12220
|
* ```
|
|
12113
12221
|
*/
|
|
12114
12222
|
build() {
|
|
12115
|
-
|
|
12116
|
-
const { LLMist: LLMistClass } = (init_client(), __toCommonJS(client_exports));
|
|
12117
|
-
this.client = new LLMistClass();
|
|
12118
|
-
}
|
|
12119
|
-
const registry = GadgetRegistry.from(this.gadgets);
|
|
12120
|
-
const options = {
|
|
12121
|
-
client: this.client,
|
|
12122
|
-
model: this.model ?? "openai:gpt-5-nano",
|
|
12123
|
-
systemPrompt: this.systemPrompt,
|
|
12124
|
-
// No userPrompt - agent.run() will throw if called directly
|
|
12125
|
-
registry,
|
|
12126
|
-
maxIterations: this.maxIterations,
|
|
12127
|
-
budget: this.budget,
|
|
12128
|
-
temperature: this.temperature,
|
|
12129
|
-
logger: this.logger,
|
|
12130
|
-
hooks: this.composeHooks(),
|
|
12131
|
-
promptConfig: this.promptConfig,
|
|
12132
|
-
initialMessages: this.initialMessages,
|
|
12133
|
-
requestHumanInput: this.requestHumanInput,
|
|
12134
|
-
gadgetStartPrefix: this.gadgetStartPrefix,
|
|
12135
|
-
gadgetEndPrefix: this.gadgetEndPrefix,
|
|
12136
|
-
gadgetArgPrefix: this.gadgetArgPrefix,
|
|
12137
|
-
textOnlyHandler: this.textOnlyHandler,
|
|
12138
|
-
textWithGadgetsHandler: this.textWithGadgetsHandler,
|
|
12139
|
-
defaultGadgetTimeoutMs: this.defaultGadgetTimeoutMs,
|
|
12140
|
-
gadgetExecutionMode: this.gadgetExecutionMode,
|
|
12141
|
-
maxGadgetsPerResponse: this.maxGadgetsPerResponse,
|
|
12142
|
-
gadgetOutputLimit: this.gadgetOutputLimit,
|
|
12143
|
-
gadgetOutputLimitPercent: this.gadgetOutputLimitPercent,
|
|
12144
|
-
compactionConfig: this.compactionConfig,
|
|
12145
|
-
retryConfig: this.retryConfig,
|
|
12146
|
-
rateLimitConfig: this.rateLimitConfig,
|
|
12147
|
-
signal: this.signal,
|
|
12148
|
-
reasoning: this.reasoningConfig,
|
|
12149
|
-
caching: this.cachingConfig,
|
|
12150
|
-
subagentConfig: this.subagentConfig,
|
|
12151
|
-
// Tree context for shared tree model (subagents share parent's tree)
|
|
12152
|
-
parentTree: this.parentContext?.tree,
|
|
12153
|
-
parentNodeId: this.parentContext?.nodeId,
|
|
12154
|
-
baseDepth: this.parentContext ? (this.parentContext.depth ?? 0) + 1 : 0,
|
|
12155
|
-
// Parent observer hooks for subagent visibility
|
|
12156
|
-
parentObservers: this.parentObservers,
|
|
12157
|
-
// Shared rate limit tracker and retry config (for coordinated limits across subagents)
|
|
12158
|
-
sharedRateLimitTracker: this.sharedRateLimitTracker,
|
|
12159
|
-
sharedRetryConfig: this.sharedRetryConfig
|
|
12160
|
-
};
|
|
12223
|
+
const options = this.buildAgentOptions();
|
|
12161
12224
|
return new Agent(AGENT_INTERNAL_KEY, options);
|
|
12162
12225
|
}
|
|
12163
12226
|
};
|
|
@@ -13445,137 +13508,549 @@ var init_executor = __esm({
|
|
|
13445
13508
|
}
|
|
13446
13509
|
});
|
|
13447
13510
|
|
|
13448
|
-
// src/agent/
|
|
13449
|
-
|
|
13450
|
-
|
|
13451
|
-
|
|
13452
|
-
|
|
13453
|
-
|
|
13454
|
-
|
|
13455
|
-
|
|
13456
|
-
|
|
13457
|
-
|
|
13458
|
-
|
|
13459
|
-
|
|
13460
|
-
|
|
13461
|
-
|
|
13462
|
-
|
|
13463
|
-
|
|
13464
|
-
|
|
13465
|
-
|
|
13466
|
-
|
|
13467
|
-
|
|
13468
|
-
|
|
13469
|
-
|
|
13470
|
-
}
|
|
13471
|
-
currentId = node.parentId;
|
|
13472
|
-
}
|
|
13473
|
-
return 0;
|
|
13474
|
-
}
|
|
13475
|
-
function buildSubagentContext(tree, event) {
|
|
13476
|
-
const parentGadgetInvocationId = findParentGadgetInvocationId(tree, event.nodeId);
|
|
13477
|
-
if (!parentGadgetInvocationId) {
|
|
13478
|
-
return void 0;
|
|
13479
|
-
}
|
|
13480
|
-
return {
|
|
13481
|
-
parentGadgetInvocationId,
|
|
13482
|
-
depth: event.depth
|
|
13483
|
-
};
|
|
13484
|
-
}
|
|
13485
|
-
function getSubagentContextForNode(tree, nodeId) {
|
|
13486
|
-
const node = tree.getNode(nodeId);
|
|
13487
|
-
if (!node) return void 0;
|
|
13488
|
-
const parentGadgetInvocationId = findParentGadgetInvocationId(tree, nodeId);
|
|
13489
|
-
if (!parentGadgetInvocationId) {
|
|
13490
|
-
return void 0;
|
|
13491
|
-
}
|
|
13492
|
-
return {
|
|
13493
|
-
parentGadgetInvocationId,
|
|
13494
|
-
depth: node.depth
|
|
13495
|
-
};
|
|
13496
|
-
}
|
|
13497
|
-
async function safeObserve(fn, logger2, eventType) {
|
|
13498
|
-
try {
|
|
13499
|
-
await fn();
|
|
13500
|
-
} catch (error) {
|
|
13501
|
-
logger2.warn(`Observer error in ${eventType}:`, error);
|
|
13502
|
-
}
|
|
13503
|
-
}
|
|
13504
|
-
function chainObserverCall(chainMap, key, fn, logger2, eventType, cleanup = false) {
|
|
13505
|
-
const previousPromise = chainMap.get(key) ?? Promise.resolve();
|
|
13506
|
-
const newPromise = previousPromise.then(() => safeObserve(fn, logger2, eventType));
|
|
13507
|
-
chainMap.set(key, newPromise);
|
|
13508
|
-
if (cleanup) {
|
|
13509
|
-
newPromise.finally(() => chainMap.delete(key));
|
|
13510
|
-
}
|
|
13511
|
-
}
|
|
13512
|
-
function bridgeTreeToHooks(tree, hooks, logger2) {
|
|
13513
|
-
const gadgetPromiseChains = /* @__PURE__ */ new Map();
|
|
13514
|
-
const llmPromiseChains = /* @__PURE__ */ new Map();
|
|
13515
|
-
return tree.onAll((event) => {
|
|
13516
|
-
const subagentContext = buildSubagentContext(tree, event);
|
|
13517
|
-
switch (event.type) {
|
|
13518
|
-
// =================================================================
|
|
13519
|
-
// GADGET EVENTS - Bridged for subagent visibility
|
|
13520
|
-
// =================================================================
|
|
13521
|
-
// When a subagent executes gadgets, these events propagate through
|
|
13522
|
-
// the shared tree to the parent's hooks.
|
|
13523
|
-
// Only bridged for subagent events (depth > 0) to avoid double-calling
|
|
13524
|
-
// root agent events which are handled directly in stream-processor.ts
|
|
13525
|
-
case "gadget_start": {
|
|
13526
|
-
if (subagentContext && hooks.observers?.onGadgetExecutionStart) {
|
|
13527
|
-
const gadgetEvent = event;
|
|
13528
|
-
const gadgetNode = tree.getNode(event.nodeId);
|
|
13529
|
-
const context = {
|
|
13530
|
-
iteration: getIterationFromTree(tree, event.nodeId),
|
|
13531
|
-
gadgetName: gadgetEvent.name,
|
|
13532
|
-
invocationId: gadgetEvent.invocationId,
|
|
13533
|
-
parameters: gadgetNode?.parameters ?? {},
|
|
13534
|
-
logger: logger2,
|
|
13535
|
-
subagentContext
|
|
13536
|
-
};
|
|
13537
|
-
chainObserverCall(
|
|
13538
|
-
gadgetPromiseChains,
|
|
13539
|
-
gadgetEvent.invocationId,
|
|
13540
|
-
() => hooks.observers?.onGadgetExecutionStart?.(context),
|
|
13541
|
-
logger2,
|
|
13542
|
-
"onGadgetExecutionStart",
|
|
13543
|
-
false
|
|
13544
|
-
// Don't cleanup - wait for completion event
|
|
13545
|
-
);
|
|
13546
|
-
}
|
|
13547
|
-
break;
|
|
13511
|
+
// src/agent/gadget-concurrency-manager.ts
|
|
13512
|
+
var GadgetConcurrencyManager;
|
|
13513
|
+
var init_gadget_concurrency_manager = __esm({
|
|
13514
|
+
"src/agent/gadget-concurrency-manager.ts"() {
|
|
13515
|
+
"use strict";
|
|
13516
|
+
init_logger();
|
|
13517
|
+
GadgetConcurrencyManager = class {
|
|
13518
|
+
registry;
|
|
13519
|
+
subagentConfig;
|
|
13520
|
+
logger;
|
|
13521
|
+
/** Track active execution count per gadget name */
|
|
13522
|
+
activeCountByGadget = /* @__PURE__ */ new Map();
|
|
13523
|
+
/** Queue of gadgets waiting for a concurrency slot (per gadget name) */
|
|
13524
|
+
concurrencyQueue = /* @__PURE__ */ new Map();
|
|
13525
|
+
/** All active gadget promises, keyed by invocationId */
|
|
13526
|
+
inFlightExecutions = /* @__PURE__ */ new Map();
|
|
13527
|
+
/** Queue of exclusive gadgets deferred until in-flight gadgets complete */
|
|
13528
|
+
exclusiveQueue = [];
|
|
13529
|
+
constructor(options) {
|
|
13530
|
+
this.registry = options.registry;
|
|
13531
|
+
this.subagentConfig = options.subagentConfig;
|
|
13532
|
+
this.logger = options.logger ?? createLogger({ name: "llmist:gadget-concurrency-manager" });
|
|
13548
13533
|
}
|
|
13549
|
-
|
|
13550
|
-
|
|
13551
|
-
|
|
13552
|
-
|
|
13553
|
-
|
|
13554
|
-
|
|
13555
|
-
|
|
13556
|
-
|
|
13557
|
-
|
|
13558
|
-
|
|
13559
|
-
|
|
13560
|
-
|
|
13561
|
-
|
|
13562
|
-
|
|
13563
|
-
|
|
13564
|
-
|
|
13565
|
-
|
|
13566
|
-
|
|
13567
|
-
|
|
13568
|
-
|
|
13569
|
-
|
|
13570
|
-
|
|
13571
|
-
|
|
13572
|
-
|
|
13534
|
+
// ==========================================================================
|
|
13535
|
+
// Concurrency limit resolution
|
|
13536
|
+
// ==========================================================================
|
|
13537
|
+
/**
|
|
13538
|
+
* Get the effective concurrency limit for a gadget.
|
|
13539
|
+
* Uses "most restrictive wins" strategy: the lowest non-zero value from
|
|
13540
|
+
* external config (SubagentConfig) and gadget's intrinsic maxConcurrent.
|
|
13541
|
+
*
|
|
13542
|
+
* This ensures gadget authors can set safety floors (e.g., maxConcurrent: 1
|
|
13543
|
+
* for file writers) that cannot be weakened by external configuration.
|
|
13544
|
+
*
|
|
13545
|
+
* @returns 0 if unlimited, otherwise the effective limit
|
|
13546
|
+
*/
|
|
13547
|
+
getConcurrencyLimit(gadgetName) {
|
|
13548
|
+
const configLimit = this.subagentConfig?.[gadgetName]?.maxConcurrent;
|
|
13549
|
+
const gadget = this.registry.get(gadgetName);
|
|
13550
|
+
const gadgetLimit = gadget?.maxConcurrent;
|
|
13551
|
+
const config = configLimit || Number.POSITIVE_INFINITY;
|
|
13552
|
+
const intrinsic = gadgetLimit || Number.POSITIVE_INFINITY;
|
|
13553
|
+
const effective = Math.min(config, intrinsic);
|
|
13554
|
+
return effective === Number.POSITIVE_INFINITY ? 0 : effective;
|
|
13555
|
+
}
|
|
13556
|
+
// ==========================================================================
|
|
13557
|
+
// Concurrency checks
|
|
13558
|
+
// ==========================================================================
|
|
13559
|
+
/**
|
|
13560
|
+
* Check whether a gadget call can start immediately given current concurrency state.
|
|
13561
|
+
* Returns false if:
|
|
13562
|
+
* - The gadget is exclusive and other gadgets are in-flight
|
|
13563
|
+
* - The gadget has a concurrency limit and it is already reached
|
|
13564
|
+
*
|
|
13565
|
+
* Does NOT modify any state.
|
|
13566
|
+
*/
|
|
13567
|
+
canStart(call) {
|
|
13568
|
+
const gadget = this.registry.get(call.gadgetName);
|
|
13569
|
+
if (gadget?.exclusive && this.inFlightExecutions.size > 0) {
|
|
13570
|
+
return false;
|
|
13573
13571
|
}
|
|
13574
|
-
|
|
13572
|
+
const limit = this.getConcurrencyLimit(call.gadgetName);
|
|
13573
|
+
if (limit > 0) {
|
|
13574
|
+
const activeCount = this.activeCountByGadget.get(call.gadgetName) ?? 0;
|
|
13575
|
+
if (activeCount >= limit) {
|
|
13576
|
+
return false;
|
|
13577
|
+
}
|
|
13578
|
+
}
|
|
13579
|
+
return true;
|
|
13575
13580
|
}
|
|
13576
|
-
|
|
13577
|
-
|
|
13578
|
-
|
|
13581
|
+
/**
|
|
13582
|
+
* Check whether a gadget is marked as exclusive.
|
|
13583
|
+
*/
|
|
13584
|
+
isExclusive(gadgetName) {
|
|
13585
|
+
const gadget = this.registry.get(gadgetName);
|
|
13586
|
+
return gadget?.exclusive === true;
|
|
13587
|
+
}
|
|
13588
|
+
/**
|
|
13589
|
+
* Get the current count of in-flight (actively executing) gadgets.
|
|
13590
|
+
*/
|
|
13591
|
+
get inFlightCount() {
|
|
13592
|
+
return this.inFlightExecutions.size;
|
|
13593
|
+
}
|
|
13594
|
+
/**
|
|
13595
|
+
* Get the total count of actively executing gadgets across all gadget types.
|
|
13596
|
+
* Used to know when all work is truly complete.
|
|
13597
|
+
*/
|
|
13598
|
+
getTotalActiveGadgetCount() {
|
|
13599
|
+
let total = 0;
|
|
13600
|
+
for (const count of this.activeCountByGadget.values()) {
|
|
13601
|
+
total += count;
|
|
13602
|
+
}
|
|
13603
|
+
return total;
|
|
13604
|
+
}
|
|
13605
|
+
/**
|
|
13606
|
+
* Check if there are any gadgets waiting in concurrency queues.
|
|
13607
|
+
*/
|
|
13608
|
+
hasQueuedGadgets() {
|
|
13609
|
+
for (const queue of this.concurrencyQueue.values()) {
|
|
13610
|
+
if (queue.length > 0) return true;
|
|
13611
|
+
}
|
|
13612
|
+
return false;
|
|
13613
|
+
}
|
|
13614
|
+
/**
|
|
13615
|
+
* Get total count of queued gadgets across all queues.
|
|
13616
|
+
*/
|
|
13617
|
+
getQueuedGadgetCount() {
|
|
13618
|
+
let count = 0;
|
|
13619
|
+
for (const queue of this.concurrencyQueue.values()) {
|
|
13620
|
+
count += queue.length;
|
|
13621
|
+
}
|
|
13622
|
+
return count;
|
|
13623
|
+
}
|
|
13624
|
+
/**
|
|
13625
|
+
* Check if there are exclusive gadgets waiting.
|
|
13626
|
+
*/
|
|
13627
|
+
get hasExclusiveQueued() {
|
|
13628
|
+
return this.exclusiveQueue.length > 0;
|
|
13629
|
+
}
|
|
13630
|
+
/**
|
|
13631
|
+
* Drain and return all exclusive gadgets from the queue.
|
|
13632
|
+
* The caller is responsible for executing them.
|
|
13633
|
+
*/
|
|
13634
|
+
drainExclusiveQueue() {
|
|
13635
|
+
const queue = this.exclusiveQueue;
|
|
13636
|
+
this.exclusiveQueue = [];
|
|
13637
|
+
return queue;
|
|
13638
|
+
}
|
|
13639
|
+
// ==========================================================================
|
|
13640
|
+
// State mutation
|
|
13641
|
+
// ==========================================================================
|
|
13642
|
+
/**
|
|
13643
|
+
* Track an in-flight gadget execution.
|
|
13644
|
+
* Increments the active count for the gadget name and registers the promise.
|
|
13645
|
+
*
|
|
13646
|
+
* @param invocationId - Unique ID for this execution
|
|
13647
|
+
* @param gadgetName - Name of the gadget being executed
|
|
13648
|
+
* @param promise - The execution promise to track
|
|
13649
|
+
*/
|
|
13650
|
+
trackExecution(invocationId, gadgetName, promise) {
|
|
13651
|
+
const currentCount = this.activeCountByGadget.get(gadgetName) ?? 0;
|
|
13652
|
+
this.activeCountByGadget.set(gadgetName, currentCount + 1);
|
|
13653
|
+
this.inFlightExecutions.set(invocationId, promise);
|
|
13654
|
+
}
|
|
13655
|
+
/**
|
|
13656
|
+
* Called when a gadget execution completes.
|
|
13657
|
+
* Decrements active count and triggers queue processing if a slot opened up.
|
|
13658
|
+
*
|
|
13659
|
+
* @param gadgetName - Name of the gadget that completed
|
|
13660
|
+
* @returns The next queued gadget call for this gadget, if one was promoted, otherwise null
|
|
13661
|
+
*/
|
|
13662
|
+
onComplete(gadgetName) {
|
|
13663
|
+
const newCount = (this.activeCountByGadget.get(gadgetName) ?? 1) - 1;
|
|
13664
|
+
this.activeCountByGadget.set(gadgetName, newCount);
|
|
13665
|
+
return this.promoteFromQueue(gadgetName);
|
|
13666
|
+
}
|
|
13667
|
+
/**
|
|
13668
|
+
* Queue a gadget for later execution due to a concurrency limit being reached.
|
|
13669
|
+
*
|
|
13670
|
+
* @param call - The gadget call to defer
|
|
13671
|
+
*/
|
|
13672
|
+
queueForLater(call) {
|
|
13673
|
+
this.logger.debug("Gadget queued due to concurrency limit", {
|
|
13674
|
+
gadgetName: call.gadgetName,
|
|
13675
|
+
invocationId: call.invocationId,
|
|
13676
|
+
activeCount: this.activeCountByGadget.get(call.gadgetName) ?? 0,
|
|
13677
|
+
limit: this.getConcurrencyLimit(call.gadgetName)
|
|
13678
|
+
});
|
|
13679
|
+
const queue = this.concurrencyQueue.get(call.gadgetName) ?? [];
|
|
13680
|
+
queue.push(call);
|
|
13681
|
+
this.concurrencyQueue.set(call.gadgetName, queue);
|
|
13682
|
+
}
|
|
13683
|
+
/**
|
|
13684
|
+
* Queue a gadget for exclusive execution (after all in-flight complete).
|
|
13685
|
+
*
|
|
13686
|
+
* @param call - The exclusive gadget call to defer
|
|
13687
|
+
*/
|
|
13688
|
+
queueExclusive(call) {
|
|
13689
|
+
this.logger.debug("Deferring exclusive gadget until in-flight gadgets complete", {
|
|
13690
|
+
gadgetName: call.gadgetName,
|
|
13691
|
+
invocationId: call.invocationId,
|
|
13692
|
+
inFlightCount: this.inFlightExecutions.size
|
|
13693
|
+
});
|
|
13694
|
+
this.exclusiveQueue.push(call);
|
|
13695
|
+
}
|
|
13696
|
+
/**
|
|
13697
|
+
* Clear the inFlightExecutions map after all promises have completed.
|
|
13698
|
+
* Called after waitForAll resolves.
|
|
13699
|
+
*/
|
|
13700
|
+
clearInFlight() {
|
|
13701
|
+
this.inFlightExecutions.clear();
|
|
13702
|
+
}
|
|
13703
|
+
// ==========================================================================
|
|
13704
|
+
// Waiting
|
|
13705
|
+
// ==========================================================================
|
|
13706
|
+
/**
|
|
13707
|
+
* Wait for all currently in-flight gadget executions to complete.
|
|
13708
|
+
* Resolves when the Promise.all of all tracked promises resolves.
|
|
13709
|
+
*
|
|
13710
|
+
* Note: new executions may be started during waiting (from the queue).
|
|
13711
|
+
* Callers should loop until inFlightCount === 0 AND hasQueuedGadgets() === false.
|
|
13712
|
+
*/
|
|
13713
|
+
async waitForAll() {
|
|
13714
|
+
if (this.inFlightExecutions.size === 0) return;
|
|
13715
|
+
await Promise.all(this.inFlightExecutions.values());
|
|
13716
|
+
}
|
|
13717
|
+
/**
|
|
13718
|
+
* Get a promise that resolves when all current in-flight executions complete.
|
|
13719
|
+
* Returns a resolved promise if no executions are in-flight.
|
|
13720
|
+
*/
|
|
13721
|
+
getAllDonePromise() {
|
|
13722
|
+
if (this.inFlightExecutions.size === 0) {
|
|
13723
|
+
return Promise.resolve("done");
|
|
13724
|
+
}
|
|
13725
|
+
return Promise.all(this.inFlightExecutions.values()).then(() => "done");
|
|
13726
|
+
}
|
|
13727
|
+
// ==========================================================================
|
|
13728
|
+
// Private helpers
|
|
13729
|
+
// ==========================================================================
|
|
13730
|
+
/**
|
|
13731
|
+
* Check the concurrency queue for a gadget name and promote the next queued
|
|
13732
|
+
* call if a slot is available.
|
|
13733
|
+
*
|
|
13734
|
+
* @param gadgetName - The gadget name to check
|
|
13735
|
+
* @returns The promoted call, or null if queue is empty or limit still reached
|
|
13736
|
+
*/
|
|
13737
|
+
promoteFromQueue(gadgetName) {
|
|
13738
|
+
const queue = this.concurrencyQueue.get(gadgetName);
|
|
13739
|
+
if (!queue || queue.length === 0) return null;
|
|
13740
|
+
const limit = this.getConcurrencyLimit(gadgetName);
|
|
13741
|
+
const activeCount = this.activeCountByGadget.get(gadgetName) ?? 0;
|
|
13742
|
+
if (limit === 0 || activeCount < limit) {
|
|
13743
|
+
const nextCall = queue.shift();
|
|
13744
|
+
this.logger.debug("Processing queued gadget", {
|
|
13745
|
+
gadgetName,
|
|
13746
|
+
invocationId: nextCall.invocationId,
|
|
13747
|
+
remainingInQueue: queue.length
|
|
13748
|
+
});
|
|
13749
|
+
return nextCall;
|
|
13750
|
+
}
|
|
13751
|
+
return null;
|
|
13752
|
+
}
|
|
13753
|
+
};
|
|
13754
|
+
}
|
|
13755
|
+
});
|
|
13756
|
+
|
|
13757
|
+
// src/agent/gadget-dependency-resolver.ts
|
|
13758
|
+
var GadgetDependencyResolver;
|
|
13759
|
+
var init_gadget_dependency_resolver = __esm({
|
|
13760
|
+
"src/agent/gadget-dependency-resolver.ts"() {
|
|
13761
|
+
"use strict";
|
|
13762
|
+
GadgetDependencyResolver = class {
|
|
13763
|
+
/** Gadgets waiting for their dependencies to complete */
|
|
13764
|
+
gadgetsAwaitingDependencies = /* @__PURE__ */ new Map();
|
|
13765
|
+
/** Completed gadget results, keyed by invocation ID */
|
|
13766
|
+
completedResults = /* @__PURE__ */ new Map();
|
|
13767
|
+
/** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
|
|
13768
|
+
failedInvocations = /* @__PURE__ */ new Set();
|
|
13769
|
+
/** Invocation IDs completed in previous iterations (read-only) */
|
|
13770
|
+
priorCompletedInvocations;
|
|
13771
|
+
/** Invocation IDs that failed in previous iterations (read-only) */
|
|
13772
|
+
priorFailedInvocations;
|
|
13773
|
+
constructor(options = {}) {
|
|
13774
|
+
this.priorCompletedInvocations = options.priorCompletedInvocations ?? /* @__PURE__ */ new Set();
|
|
13775
|
+
this.priorFailedInvocations = options.priorFailedInvocations ?? /* @__PURE__ */ new Set();
|
|
13776
|
+
}
|
|
13777
|
+
// ==========================================================================
|
|
13778
|
+
// State mutation
|
|
13779
|
+
// ==========================================================================
|
|
13780
|
+
/**
|
|
13781
|
+
* Queue a gadget call that is waiting for one or more dependencies to complete.
|
|
13782
|
+
* Call this when a gadget's dependencies are not yet all satisfied.
|
|
13783
|
+
*
|
|
13784
|
+
* @param call - The parsed gadget call to defer
|
|
13785
|
+
*/
|
|
13786
|
+
addPending(call) {
|
|
13787
|
+
this.gadgetsAwaitingDependencies.set(call.invocationId, call);
|
|
13788
|
+
}
|
|
13789
|
+
/**
|
|
13790
|
+
* Record that a gadget completed successfully.
|
|
13791
|
+
* This may unblock other gadgets that depend on this invocation.
|
|
13792
|
+
*
|
|
13793
|
+
* Also marks as failed if the result contains an error.
|
|
13794
|
+
*
|
|
13795
|
+
* @param result - The completed gadget execution result
|
|
13796
|
+
*/
|
|
13797
|
+
markComplete(result) {
|
|
13798
|
+
this.completedResults.set(result.invocationId, result);
|
|
13799
|
+
if (result.error) {
|
|
13800
|
+
this.failedInvocations.add(result.invocationId);
|
|
13801
|
+
}
|
|
13802
|
+
}
|
|
13803
|
+
/**
|
|
13804
|
+
* Mark an invocation ID as failed without recording a full result.
|
|
13805
|
+
* Use this for gadgets that are skipped before execution (e.g., limit exceeded,
|
|
13806
|
+
* self-referential dependency, dependency skip).
|
|
13807
|
+
*
|
|
13808
|
+
* @param invocationId - The invocation ID to mark as failed
|
|
13809
|
+
*/
|
|
13810
|
+
markFailed(invocationId) {
|
|
13811
|
+
this.failedInvocations.add(invocationId);
|
|
13812
|
+
}
|
|
13813
|
+
/**
|
|
13814
|
+
* Remove a gadget from the pending queue (called just before execution).
|
|
13815
|
+
*
|
|
13816
|
+
* @param invocationId - The invocation ID to remove from pending
|
|
13817
|
+
*/
|
|
13818
|
+
removePending(invocationId) {
|
|
13819
|
+
this.gadgetsAwaitingDependencies.delete(invocationId);
|
|
13820
|
+
}
|
|
13821
|
+
/**
|
|
13822
|
+
* Clear all remaining pending gadgets (e.g., after handling unresolvable deps).
|
|
13823
|
+
*/
|
|
13824
|
+
clearPending() {
|
|
13825
|
+
this.gadgetsAwaitingDependencies.clear();
|
|
13826
|
+
}
|
|
13827
|
+
// ==========================================================================
|
|
13828
|
+
// Queries
|
|
13829
|
+
// ==========================================================================
|
|
13830
|
+
/**
|
|
13831
|
+
* Get all gadget calls currently waiting for dependencies.
|
|
13832
|
+
* Returns entries as [invocationId, call] pairs.
|
|
13833
|
+
*/
|
|
13834
|
+
getPendingEntries() {
|
|
13835
|
+
return Array.from(this.gadgetsAwaitingDependencies.entries());
|
|
13836
|
+
}
|
|
13837
|
+
/**
|
|
13838
|
+
* Get the number of gadgets currently waiting for dependencies.
|
|
13839
|
+
*/
|
|
13840
|
+
get pendingCount() {
|
|
13841
|
+
return this.gadgetsAwaitingDependencies.size;
|
|
13842
|
+
}
|
|
13843
|
+
/**
|
|
13844
|
+
* Check whether a given invocation ID has been completed successfully
|
|
13845
|
+
* (either in this iteration or a prior one).
|
|
13846
|
+
*/
|
|
13847
|
+
isCompleted(invocationId) {
|
|
13848
|
+
return this.completedResults.has(invocationId) || this.priorCompletedInvocations.has(invocationId);
|
|
13849
|
+
}
|
|
13850
|
+
/**
|
|
13851
|
+
* Check whether a given invocation ID has failed
|
|
13852
|
+
* (either in this iteration or a prior one).
|
|
13853
|
+
*/
|
|
13854
|
+
isFailed(invocationId) {
|
|
13855
|
+
return this.failedInvocations.has(invocationId) || this.priorFailedInvocations.has(invocationId);
|
|
13856
|
+
}
|
|
13857
|
+
/**
|
|
13858
|
+
* Get the execution result for a completed invocation, if available.
|
|
13859
|
+
* Only returns results from the current iteration; prior iterations
|
|
13860
|
+
* are tracked by ID only.
|
|
13861
|
+
*/
|
|
13862
|
+
getCompletedResult(invocationId) {
|
|
13863
|
+
return this.completedResults.get(invocationId);
|
|
13864
|
+
}
|
|
13865
|
+
/**
|
|
13866
|
+
* Check if all dependencies for a gadget call are satisfied.
|
|
13867
|
+
* A dependency is satisfied if it completed in this or a prior iteration.
|
|
13868
|
+
*
|
|
13869
|
+
* @param call - The gadget call whose dependencies to check
|
|
13870
|
+
* @returns true if all deps are satisfied, false if any are still pending
|
|
13871
|
+
*/
|
|
13872
|
+
isAllSatisfied(call) {
|
|
13873
|
+
return call.dependencies.every((dep) => this.isCompleted(dep));
|
|
13874
|
+
}
|
|
13875
|
+
/**
|
|
13876
|
+
* Find the first failed dependency for a gadget call, if any.
|
|
13877
|
+
* A dependency is considered failed if it failed in this or a prior iteration.
|
|
13878
|
+
*
|
|
13879
|
+
* @param call - The gadget call to check
|
|
13880
|
+
* @returns The invocation ID of the failed dependency, or undefined if none
|
|
13881
|
+
*/
|
|
13882
|
+
getFailedDependency(call) {
|
|
13883
|
+
return call.dependencies.find((dep) => this.isFailed(dep));
|
|
13884
|
+
}
|
|
13885
|
+
/**
|
|
13886
|
+
* Separate the pending gadgets into two groups:
|
|
13887
|
+
* - Those ready to execute (all deps satisfied)
|
|
13888
|
+
* - Those ready to skip (at least one dep has failed)
|
|
13889
|
+
*
|
|
13890
|
+
* Gadgets that are neither ready nor skippable remain pending.
|
|
13891
|
+
*
|
|
13892
|
+
* @returns Object with `readyToExecute` and `readyToSkip` arrays
|
|
13893
|
+
*/
|
|
13894
|
+
getReadyCalls() {
|
|
13895
|
+
const readyToExecute = [];
|
|
13896
|
+
const readyToSkip = [];
|
|
13897
|
+
for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
13898
|
+
const failedDep = this.getFailedDependency(call);
|
|
13899
|
+
if (failedDep) {
|
|
13900
|
+
readyToSkip.push({ call, failedDep });
|
|
13901
|
+
continue;
|
|
13902
|
+
}
|
|
13903
|
+
if (this.isAllSatisfied(call)) {
|
|
13904
|
+
readyToExecute.push(call);
|
|
13905
|
+
}
|
|
13906
|
+
}
|
|
13907
|
+
return { readyToExecute, readyToSkip };
|
|
13908
|
+
}
|
|
13909
|
+
// ==========================================================================
|
|
13910
|
+
// Cross-iteration accessors (for Agent to accumulate state)
|
|
13911
|
+
// ==========================================================================
|
|
13912
|
+
/**
|
|
13913
|
+
* Get all invocation IDs that completed successfully in this iteration.
|
|
13914
|
+
* Used by Agent to accumulate completed IDs across iterations.
|
|
13915
|
+
*/
|
|
13916
|
+
getCompletedInvocationIds() {
|
|
13917
|
+
return new Set(this.completedResults.keys());
|
|
13918
|
+
}
|
|
13919
|
+
/**
|
|
13920
|
+
* Get all invocation IDs that failed in this iteration.
|
|
13921
|
+
* Used by Agent to accumulate failed IDs across iterations.
|
|
13922
|
+
*/
|
|
13923
|
+
getFailedInvocationIds() {
|
|
13924
|
+
return new Set(this.failedInvocations);
|
|
13925
|
+
}
|
|
13926
|
+
};
|
|
13927
|
+
}
|
|
13928
|
+
});
|
|
13929
|
+
|
|
13930
|
+
// src/agent/tree-hook-bridge.ts
|
|
13931
|
+
function findParentGadgetInvocationId(tree, nodeId) {
|
|
13932
|
+
let currentId = nodeId;
|
|
13933
|
+
while (currentId) {
|
|
13934
|
+
const node = tree.getNode(currentId);
|
|
13935
|
+
if (!node) break;
|
|
13936
|
+
currentId = node.parentId;
|
|
13937
|
+
if (!currentId) break;
|
|
13938
|
+
const parentNode = tree.getNode(currentId);
|
|
13939
|
+
if (parentNode?.type === "gadget") {
|
|
13940
|
+
return parentNode.invocationId;
|
|
13941
|
+
}
|
|
13942
|
+
}
|
|
13943
|
+
return void 0;
|
|
13944
|
+
}
|
|
13945
|
+
function getIterationFromTree(tree, nodeId) {
|
|
13946
|
+
let currentId = nodeId;
|
|
13947
|
+
while (currentId) {
|
|
13948
|
+
const node = tree.getNode(currentId);
|
|
13949
|
+
if (!node) break;
|
|
13950
|
+
if (node.type === "llm_call") {
|
|
13951
|
+
return node.iteration;
|
|
13952
|
+
}
|
|
13953
|
+
currentId = node.parentId;
|
|
13954
|
+
}
|
|
13955
|
+
return 0;
|
|
13956
|
+
}
|
|
13957
|
+
function buildSubagentContext(tree, event) {
|
|
13958
|
+
const parentGadgetInvocationId = findParentGadgetInvocationId(tree, event.nodeId);
|
|
13959
|
+
if (!parentGadgetInvocationId) {
|
|
13960
|
+
return void 0;
|
|
13961
|
+
}
|
|
13962
|
+
return {
|
|
13963
|
+
parentGadgetInvocationId,
|
|
13964
|
+
depth: event.depth
|
|
13965
|
+
};
|
|
13966
|
+
}
|
|
13967
|
+
function getSubagentContextForNode(tree, nodeId) {
|
|
13968
|
+
const node = tree.getNode(nodeId);
|
|
13969
|
+
if (!node) return void 0;
|
|
13970
|
+
const parentGadgetInvocationId = findParentGadgetInvocationId(tree, nodeId);
|
|
13971
|
+
if (!parentGadgetInvocationId) {
|
|
13972
|
+
return void 0;
|
|
13973
|
+
}
|
|
13974
|
+
return {
|
|
13975
|
+
parentGadgetInvocationId,
|
|
13976
|
+
depth: node.depth
|
|
13977
|
+
};
|
|
13978
|
+
}
|
|
13979
|
+
function chainObserverCall(chainMap, key, fn, logger2, eventType, cleanup = false) {
|
|
13980
|
+
const previousPromise = chainMap.get(key) ?? Promise.resolve();
|
|
13981
|
+
const newPromise = previousPromise.then(() => safeObserve(fn, logger2, eventType));
|
|
13982
|
+
chainMap.set(key, newPromise);
|
|
13983
|
+
if (cleanup) {
|
|
13984
|
+
newPromise.finally(() => chainMap.delete(key));
|
|
13985
|
+
}
|
|
13986
|
+
}
|
|
13987
|
+
function bridgeTreeToHooks(tree, hooks, logger2) {
|
|
13988
|
+
const gadgetPromiseChains = /* @__PURE__ */ new Map();
|
|
13989
|
+
const llmPromiseChains = /* @__PURE__ */ new Map();
|
|
13990
|
+
return tree.onAll((event) => {
|
|
13991
|
+
const subagentContext = buildSubagentContext(tree, event);
|
|
13992
|
+
switch (event.type) {
|
|
13993
|
+
// =================================================================
|
|
13994
|
+
// GADGET EVENTS - Bridged for subagent visibility
|
|
13995
|
+
// =================================================================
|
|
13996
|
+
// When a subagent executes gadgets, these events propagate through
|
|
13997
|
+
// the shared tree to the parent's hooks.
|
|
13998
|
+
// Only bridged for subagent events (depth > 0) to avoid double-calling
|
|
13999
|
+
// root agent events which are handled directly in stream-processor.ts
|
|
14000
|
+
case "gadget_start": {
|
|
14001
|
+
if (subagentContext && hooks.observers?.onGadgetExecutionStart) {
|
|
14002
|
+
const gadgetEvent = event;
|
|
14003
|
+
const gadgetNode = tree.getNode(event.nodeId);
|
|
14004
|
+
const context = {
|
|
14005
|
+
iteration: getIterationFromTree(tree, event.nodeId),
|
|
14006
|
+
gadgetName: gadgetEvent.name,
|
|
14007
|
+
invocationId: gadgetEvent.invocationId,
|
|
14008
|
+
parameters: gadgetNode?.parameters ?? {},
|
|
14009
|
+
logger: logger2,
|
|
14010
|
+
subagentContext
|
|
14011
|
+
};
|
|
14012
|
+
chainObserverCall(
|
|
14013
|
+
gadgetPromiseChains,
|
|
14014
|
+
gadgetEvent.invocationId,
|
|
14015
|
+
() => hooks.observers?.onGadgetExecutionStart?.(context),
|
|
14016
|
+
logger2,
|
|
14017
|
+
"onGadgetExecutionStart",
|
|
14018
|
+
false
|
|
14019
|
+
// Don't cleanup - wait for completion event
|
|
14020
|
+
);
|
|
14021
|
+
}
|
|
14022
|
+
break;
|
|
14023
|
+
}
|
|
14024
|
+
case "gadget_complete": {
|
|
14025
|
+
if (subagentContext && hooks.observers?.onGadgetExecutionComplete) {
|
|
14026
|
+
const gadgetEvent = event;
|
|
14027
|
+
const gadgetNode = tree.getNode(event.nodeId);
|
|
14028
|
+
const context = {
|
|
14029
|
+
iteration: getIterationFromTree(tree, event.nodeId),
|
|
14030
|
+
gadgetName: gadgetEvent.name,
|
|
14031
|
+
invocationId: gadgetEvent.invocationId,
|
|
14032
|
+
parameters: gadgetNode?.parameters ?? {},
|
|
14033
|
+
finalResult: gadgetEvent.result,
|
|
14034
|
+
executionTimeMs: gadgetEvent.executionTimeMs,
|
|
14035
|
+
cost: gadgetEvent.cost,
|
|
14036
|
+
logger: logger2,
|
|
14037
|
+
subagentContext
|
|
14038
|
+
};
|
|
14039
|
+
chainObserverCall(
|
|
14040
|
+
gadgetPromiseChains,
|
|
14041
|
+
gadgetEvent.invocationId,
|
|
14042
|
+
() => hooks.observers?.onGadgetExecutionComplete?.(context),
|
|
14043
|
+
logger2,
|
|
14044
|
+
"onGadgetExecutionComplete",
|
|
14045
|
+
true
|
|
14046
|
+
// Cleanup after completion
|
|
14047
|
+
);
|
|
14048
|
+
}
|
|
14049
|
+
break;
|
|
14050
|
+
}
|
|
14051
|
+
case "gadget_error": {
|
|
14052
|
+
if (subagentContext && hooks.observers?.onGadgetExecutionComplete) {
|
|
14053
|
+
const gadgetEvent = event;
|
|
13579
14054
|
const gadgetNode = tree.getNode(event.nodeId);
|
|
13580
14055
|
const context = {
|
|
13581
14056
|
iteration: getIterationFromTree(tree, event.nodeId),
|
|
@@ -13724,6 +14199,82 @@ function bridgeTreeToHooks(tree, hooks, logger2) {
|
|
|
13724
14199
|
var init_tree_hook_bridge = __esm({
|
|
13725
14200
|
"src/agent/tree-hook-bridge.ts"() {
|
|
13726
14201
|
"use strict";
|
|
14202
|
+
init_safe_observe();
|
|
14203
|
+
}
|
|
14204
|
+
});
|
|
14205
|
+
|
|
14206
|
+
// src/agent/observer-notifier.ts
|
|
14207
|
+
async function notifyGadgetSkipped(ctx) {
|
|
14208
|
+
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
14209
|
+
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
14210
|
+
const context = {
|
|
14211
|
+
iteration: ctx.iteration,
|
|
14212
|
+
gadgetName: ctx.gadgetName,
|
|
14213
|
+
invocationId: ctx.invocationId,
|
|
14214
|
+
parameters: ctx.parameters,
|
|
14215
|
+
failedDependency: ctx.failedDependency,
|
|
14216
|
+
failedDependencyError: ctx.failedDependencyError,
|
|
14217
|
+
logger: ctx.logger,
|
|
14218
|
+
subagentContext
|
|
14219
|
+
};
|
|
14220
|
+
if (ctx.hooks?.onGadgetSkipped) {
|
|
14221
|
+
const hookFn = ctx.hooks.onGadgetSkipped;
|
|
14222
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14223
|
+
}
|
|
14224
|
+
if (ctx.parentObservers?.onGadgetSkipped) {
|
|
14225
|
+
const hookFn = ctx.parentObservers.onGadgetSkipped;
|
|
14226
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14227
|
+
}
|
|
14228
|
+
}
|
|
14229
|
+
async function notifyGadgetStart(ctx) {
|
|
14230
|
+
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
14231
|
+
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
14232
|
+
const context = {
|
|
14233
|
+
iteration: ctx.iteration,
|
|
14234
|
+
gadgetName: ctx.gadgetName,
|
|
14235
|
+
invocationId: ctx.invocationId,
|
|
14236
|
+
parameters: ctx.parameters,
|
|
14237
|
+
logger: ctx.logger,
|
|
14238
|
+
subagentContext
|
|
14239
|
+
};
|
|
14240
|
+
if (ctx.hooks?.onGadgetExecutionStart) {
|
|
14241
|
+
const hookFn = ctx.hooks.onGadgetExecutionStart;
|
|
14242
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14243
|
+
}
|
|
14244
|
+
if (ctx.parentObservers?.onGadgetExecutionStart) {
|
|
14245
|
+
const hookFn = ctx.parentObservers.onGadgetExecutionStart;
|
|
14246
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14247
|
+
}
|
|
14248
|
+
}
|
|
14249
|
+
async function notifyGadgetComplete(ctx) {
|
|
14250
|
+
const gadgetNode = ctx.tree?.getNodeByInvocationId(ctx.invocationId);
|
|
14251
|
+
const subagentContext = ctx.tree && gadgetNode ? getSubagentContextForNode(ctx.tree, gadgetNode.id) : void 0;
|
|
14252
|
+
const context = {
|
|
14253
|
+
iteration: ctx.iteration,
|
|
14254
|
+
gadgetName: ctx.gadgetName,
|
|
14255
|
+
invocationId: ctx.invocationId,
|
|
14256
|
+
parameters: ctx.parameters,
|
|
14257
|
+
finalResult: ctx.finalResult,
|
|
14258
|
+
error: ctx.error,
|
|
14259
|
+
executionTimeMs: ctx.executionTimeMs,
|
|
14260
|
+
cost: ctx.cost,
|
|
14261
|
+
logger: ctx.logger,
|
|
14262
|
+
subagentContext
|
|
14263
|
+
};
|
|
14264
|
+
if (ctx.hooks?.onGadgetExecutionComplete) {
|
|
14265
|
+
const hookFn = ctx.hooks.onGadgetExecutionComplete;
|
|
14266
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14267
|
+
}
|
|
14268
|
+
if (ctx.parentObservers?.onGadgetExecutionComplete) {
|
|
14269
|
+
const hookFn = ctx.parentObservers.onGadgetExecutionComplete;
|
|
14270
|
+
await safeObserve(() => hookFn(context), ctx.logger);
|
|
14271
|
+
}
|
|
14272
|
+
}
|
|
14273
|
+
var init_observer_notifier = __esm({
|
|
14274
|
+
"src/agent/observer-notifier.ts"() {
|
|
14275
|
+
"use strict";
|
|
14276
|
+
init_safe_observe();
|
|
14277
|
+
init_tree_hook_bridge();
|
|
13727
14278
|
}
|
|
13728
14279
|
});
|
|
13729
14280
|
|
|
@@ -13735,11 +14286,13 @@ var init_stream_processor = __esm({
|
|
|
13735
14286
|
init_executor();
|
|
13736
14287
|
init_parser();
|
|
13737
14288
|
init_logger();
|
|
14289
|
+
init_gadget_concurrency_manager();
|
|
14290
|
+
init_gadget_dependency_resolver();
|
|
13738
14291
|
init_hook_validators();
|
|
13739
|
-
|
|
14292
|
+
init_observer_notifier();
|
|
14293
|
+
init_safe_observe();
|
|
13740
14294
|
StreamProcessor = class {
|
|
13741
14295
|
iteration;
|
|
13742
|
-
registry;
|
|
13743
14296
|
hooks;
|
|
13744
14297
|
logger;
|
|
13745
14298
|
parser;
|
|
@@ -13751,33 +14304,12 @@ var init_stream_processor = __esm({
|
|
|
13751
14304
|
// Gadget execution mode
|
|
13752
14305
|
gadgetExecutionMode;
|
|
13753
14306
|
responseText = "";
|
|
13754
|
-
|
|
13755
|
-
|
|
13756
|
-
|
|
13757
|
-
|
|
13758
|
-
/** Completed gadget results, keyed by invocation ID */
|
|
13759
|
-
completedResults = /* @__PURE__ */ new Map();
|
|
13760
|
-
/** Invocation IDs of gadgets that have failed (error or skipped due to dependency) */
|
|
13761
|
-
failedInvocations = /* @__PURE__ */ new Set();
|
|
13762
|
-
/** Promises for independent gadgets currently executing (fire-and-forget) */
|
|
13763
|
-
inFlightExecutions = /* @__PURE__ */ new Map();
|
|
14307
|
+
// Dependency resolution is delegated to GadgetDependencyResolver
|
|
14308
|
+
dependencyResolver;
|
|
14309
|
+
// Concurrency management is delegated to GadgetConcurrencyManager
|
|
14310
|
+
concurrencyManager;
|
|
13764
14311
|
/** Queue of completed gadget results ready to be yielded (for real-time streaming) */
|
|
13765
14312
|
completedResultsQueue = [];
|
|
13766
|
-
// Concurrency limiting
|
|
13767
|
-
/** Subagent configuration map for checking maxConcurrent limits */
|
|
13768
|
-
subagentConfig;
|
|
13769
|
-
/** Track active execution count per gadget name */
|
|
13770
|
-
activeCountByGadget = /* @__PURE__ */ new Map();
|
|
13771
|
-
/** Queue of gadgets waiting for a concurrency slot (per gadget name) */
|
|
13772
|
-
concurrencyQueue = /* @__PURE__ */ new Map();
|
|
13773
|
-
// Exclusive gadget support
|
|
13774
|
-
/** Queue of exclusive gadgets deferred until in-flight gadgets complete */
|
|
13775
|
-
exclusiveQueue = [];
|
|
13776
|
-
// Cross-iteration dependency tracking
|
|
13777
|
-
/** Invocation IDs completed in previous iterations (read-only reference from Agent) */
|
|
13778
|
-
priorCompletedInvocations;
|
|
13779
|
-
/** Invocation IDs that failed in previous iterations (read-only reference from Agent) */
|
|
13780
|
-
priorFailedInvocations;
|
|
13781
14313
|
// Parent observer hooks for subagent visibility
|
|
13782
14314
|
parentObservers;
|
|
13783
14315
|
// Gadget limiting per response
|
|
@@ -13786,16 +14318,21 @@ var init_stream_processor = __esm({
|
|
|
13786
14318
|
limitExceeded = false;
|
|
13787
14319
|
constructor(options) {
|
|
13788
14320
|
this.iteration = options.iteration;
|
|
13789
|
-
this.registry = options.registry;
|
|
13790
14321
|
this.hooks = options.hooks ?? {};
|
|
13791
14322
|
this.logger = options.logger ?? createLogger({ name: "llmist:stream-processor" });
|
|
13792
14323
|
this.tree = options.tree;
|
|
13793
14324
|
this.parentNodeId = options.parentNodeId ?? null;
|
|
13794
14325
|
this.baseDepth = options.baseDepth ?? 0;
|
|
13795
14326
|
this.gadgetExecutionMode = options.gadgetExecutionMode ?? "parallel";
|
|
13796
|
-
this.
|
|
13797
|
-
|
|
13798
|
-
|
|
14327
|
+
this.dependencyResolver = new GadgetDependencyResolver({
|
|
14328
|
+
priorCompletedInvocations: options.priorCompletedInvocations,
|
|
14329
|
+
priorFailedInvocations: options.priorFailedInvocations
|
|
14330
|
+
});
|
|
14331
|
+
this.concurrencyManager = new GadgetConcurrencyManager({
|
|
14332
|
+
registry: options.registry,
|
|
14333
|
+
subagentConfig: options.subagentConfig,
|
|
14334
|
+
logger: this.logger.getSubLogger({ name: "concurrency" })
|
|
14335
|
+
});
|
|
13799
14336
|
this.parentObservers = options.parentObservers;
|
|
13800
14337
|
this.maxGadgetsPerResponse = options.maxGadgetsPerResponse ?? 0;
|
|
13801
14338
|
this.parser = new GadgetCallParser({
|
|
@@ -14037,7 +14574,7 @@ var init_stream_processor = __esm({
|
|
|
14037
14574
|
gadgetName: call.gadgetName,
|
|
14038
14575
|
invocationId: call.invocationId
|
|
14039
14576
|
});
|
|
14040
|
-
this.
|
|
14577
|
+
this.dependencyResolver.markFailed(call.invocationId);
|
|
14041
14578
|
const errorMessage = `Gadget "${call.invocationId}" cannot depend on itself (self-referential dependency)`;
|
|
14042
14579
|
const skipEvent = {
|
|
14043
14580
|
type: "gadget_skipped",
|
|
@@ -14048,39 +14585,21 @@ var init_stream_processor = __esm({
|
|
|
14048
14585
|
failedDependencyError: errorMessage
|
|
14049
14586
|
};
|
|
14050
14587
|
yield skipEvent;
|
|
14051
|
-
|
|
14052
|
-
|
|
14053
|
-
|
|
14054
|
-
|
|
14055
|
-
|
|
14056
|
-
|
|
14057
|
-
|
|
14058
|
-
|
|
14059
|
-
|
|
14060
|
-
|
|
14061
|
-
|
|
14062
|
-
|
|
14063
|
-
};
|
|
14064
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(context));
|
|
14065
|
-
}
|
|
14066
|
-
if (this.parentObservers?.onGadgetSkipped) {
|
|
14067
|
-
const context = {
|
|
14068
|
-
iteration: this.iteration,
|
|
14069
|
-
gadgetName: call.gadgetName,
|
|
14070
|
-
invocationId: call.invocationId,
|
|
14071
|
-
parameters: call.parameters ?? {},
|
|
14072
|
-
failedDependency: call.invocationId,
|
|
14073
|
-
failedDependencyError: errorMessage,
|
|
14074
|
-
logger: this.logger,
|
|
14075
|
-
subagentContext: skippedSubagentContext
|
|
14076
|
-
};
|
|
14077
|
-
await this.safeObserve(() => this.parentObservers.onGadgetSkipped(context));
|
|
14078
|
-
}
|
|
14588
|
+
await notifyGadgetSkipped({
|
|
14589
|
+
tree: this.tree,
|
|
14590
|
+
hooks: this.hooks.observers,
|
|
14591
|
+
parentObservers: this.parentObservers,
|
|
14592
|
+
logger: this.logger,
|
|
14593
|
+
iteration: this.iteration,
|
|
14594
|
+
gadgetName: call.gadgetName,
|
|
14595
|
+
invocationId: call.invocationId,
|
|
14596
|
+
parameters: call.parameters ?? {},
|
|
14597
|
+
failedDependency: call.invocationId,
|
|
14598
|
+
failedDependencyError: errorMessage
|
|
14599
|
+
});
|
|
14079
14600
|
return;
|
|
14080
14601
|
}
|
|
14081
|
-
const failedDep =
|
|
14082
|
-
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
14083
|
-
);
|
|
14602
|
+
const failedDep = this.dependencyResolver.getFailedDependency(call);
|
|
14084
14603
|
if (failedDep) {
|
|
14085
14604
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
14086
14605
|
for (const evt of skipEvents) {
|
|
@@ -14088,16 +14607,16 @@ var init_stream_processor = __esm({
|
|
|
14088
14607
|
}
|
|
14089
14608
|
return;
|
|
14090
14609
|
}
|
|
14091
|
-
|
|
14092
|
-
|
|
14093
|
-
|
|
14094
|
-
|
|
14610
|
+
if (!this.dependencyResolver.isAllSatisfied(call)) {
|
|
14611
|
+
const unsatisfied = call.dependencies.filter(
|
|
14612
|
+
(dep) => !this.dependencyResolver.isCompleted(dep)
|
|
14613
|
+
);
|
|
14095
14614
|
this.logger.debug("Queueing gadget for later - waiting on dependencies", {
|
|
14096
14615
|
gadgetName: call.gadgetName,
|
|
14097
14616
|
invocationId: call.invocationId,
|
|
14098
14617
|
waitingOn: unsatisfied
|
|
14099
14618
|
});
|
|
14100
|
-
this.
|
|
14619
|
+
this.dependencyResolver.addPending(call);
|
|
14101
14620
|
return;
|
|
14102
14621
|
}
|
|
14103
14622
|
const limitCheckGen2 = this.checkGadgetLimitExceeded(call);
|
|
@@ -14126,28 +14645,12 @@ var init_stream_processor = __esm({
|
|
|
14126
14645
|
if (limitResult.value === true) {
|
|
14127
14646
|
return;
|
|
14128
14647
|
}
|
|
14129
|
-
|
|
14130
|
-
|
|
14131
|
-
this.logger.debug("Deferring exclusive gadget until in-flight gadgets complete", {
|
|
14132
|
-
gadgetName: call.gadgetName,
|
|
14133
|
-
invocationId: call.invocationId,
|
|
14134
|
-
inFlightCount: this.inFlightExecutions.size
|
|
14135
|
-
});
|
|
14136
|
-
this.exclusiveQueue.push(call);
|
|
14648
|
+
if (this.concurrencyManager.isExclusive(call.gadgetName) && this.concurrencyManager.inFlightCount > 0) {
|
|
14649
|
+
this.concurrencyManager.queueExclusive(call);
|
|
14137
14650
|
return;
|
|
14138
14651
|
}
|
|
14139
|
-
|
|
14140
|
-
|
|
14141
|
-
if (limit > 0 && activeCount >= limit) {
|
|
14142
|
-
this.logger.debug("Gadget queued due to concurrency limit", {
|
|
14143
|
-
gadgetName: call.gadgetName,
|
|
14144
|
-
invocationId: call.invocationId,
|
|
14145
|
-
activeCount,
|
|
14146
|
-
limit
|
|
14147
|
-
});
|
|
14148
|
-
const queue = this.concurrencyQueue.get(call.gadgetName) ?? [];
|
|
14149
|
-
queue.push(call);
|
|
14150
|
-
this.concurrencyQueue.set(call.gadgetName, queue);
|
|
14652
|
+
if (!this.concurrencyManager.canStart(call)) {
|
|
14653
|
+
this.concurrencyManager.queueForLater(call);
|
|
14151
14654
|
return;
|
|
14152
14655
|
}
|
|
14153
14656
|
if (this.gadgetExecutionMode === "sequential") {
|
|
@@ -14158,57 +14661,19 @@ var init_stream_processor = __esm({
|
|
|
14158
14661
|
this.startGadgetWithConcurrencyTracking(call);
|
|
14159
14662
|
}
|
|
14160
14663
|
}
|
|
14161
|
-
/**
|
|
14162
|
-
* Get the effective concurrency limit for a gadget.
|
|
14163
|
-
* Uses "most restrictive wins" strategy: the lowest non-zero value from
|
|
14164
|
-
* external config (SubagentConfig) and gadget's intrinsic maxConcurrent.
|
|
14165
|
-
*
|
|
14166
|
-
* This ensures gadget authors can set safety floors (e.g., maxConcurrent: 1
|
|
14167
|
-
* for file writers) that cannot be weakened by external configuration.
|
|
14168
|
-
*
|
|
14169
|
-
* @returns 0 if unlimited, otherwise the effective limit
|
|
14170
|
-
*/
|
|
14171
|
-
getConcurrencyLimit(gadgetName) {
|
|
14172
|
-
const configLimit = this.subagentConfig?.[gadgetName]?.maxConcurrent;
|
|
14173
|
-
const gadget = this.registry.get(gadgetName);
|
|
14174
|
-
const gadgetLimit = gadget?.maxConcurrent;
|
|
14175
|
-
const config = configLimit || Number.POSITIVE_INFINITY;
|
|
14176
|
-
const intrinsic = gadgetLimit || Number.POSITIVE_INFINITY;
|
|
14177
|
-
const effective = Math.min(config, intrinsic);
|
|
14178
|
-
return effective === Number.POSITIVE_INFINITY ? 0 : effective;
|
|
14179
|
-
}
|
|
14180
14664
|
/**
|
|
14181
14665
|
* Start a gadget execution with concurrency tracking.
|
|
14182
|
-
*
|
|
14666
|
+
* Delegates tracking to GadgetConcurrencyManager; schedules queue processing on completion.
|
|
14183
14667
|
*/
|
|
14184
14668
|
startGadgetWithConcurrencyTracking(call) {
|
|
14185
14669
|
const gadgetName = call.gadgetName;
|
|
14186
|
-
const currentCount = this.activeCountByGadget.get(gadgetName) ?? 0;
|
|
14187
|
-
this.activeCountByGadget.set(gadgetName, currentCount + 1);
|
|
14188
14670
|
const executionPromise = this.executeGadgetAndCollect(call).finally(() => {
|
|
14189
|
-
const
|
|
14190
|
-
|
|
14191
|
-
|
|
14671
|
+
const nextCall = this.concurrencyManager.onComplete(gadgetName);
|
|
14672
|
+
if (nextCall) {
|
|
14673
|
+
this.startGadgetWithConcurrencyTracking(nextCall);
|
|
14674
|
+
}
|
|
14192
14675
|
});
|
|
14193
|
-
this.
|
|
14194
|
-
}
|
|
14195
|
-
/**
|
|
14196
|
-
* Process the next queued gadget for a given gadget name if a slot is available.
|
|
14197
|
-
*/
|
|
14198
|
-
processQueuedGadget(gadgetName) {
|
|
14199
|
-
const queue = this.concurrencyQueue.get(gadgetName);
|
|
14200
|
-
if (!queue || queue.length === 0) return;
|
|
14201
|
-
const limit = this.getConcurrencyLimit(gadgetName);
|
|
14202
|
-
const activeCount = this.activeCountByGadget.get(gadgetName) ?? 0;
|
|
14203
|
-
if (limit === 0 || activeCount < limit) {
|
|
14204
|
-
const nextCall = queue.shift();
|
|
14205
|
-
this.logger.debug("Processing queued gadget", {
|
|
14206
|
-
gadgetName,
|
|
14207
|
-
invocationId: nextCall.invocationId,
|
|
14208
|
-
remainingInQueue: queue.length
|
|
14209
|
-
});
|
|
14210
|
-
this.startGadgetWithConcurrencyTracking(nextCall);
|
|
14211
|
-
}
|
|
14676
|
+
this.concurrencyManager.trackExecution(call.invocationId, gadgetName, executionPromise);
|
|
14212
14677
|
}
|
|
14213
14678
|
/**
|
|
14214
14679
|
* Execute a gadget through the full hook lifecycle and yield events.
|
|
@@ -14263,30 +14728,16 @@ var init_stream_processor = __esm({
|
|
|
14263
14728
|
this.tree.startGadget(gadgetNode.id);
|
|
14264
14729
|
}
|
|
14265
14730
|
}
|
|
14266
|
-
|
|
14267
|
-
|
|
14268
|
-
|
|
14269
|
-
|
|
14270
|
-
|
|
14271
|
-
|
|
14272
|
-
|
|
14273
|
-
|
|
14274
|
-
|
|
14275
|
-
|
|
14276
|
-
};
|
|
14277
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetExecutionStart(context));
|
|
14278
|
-
}
|
|
14279
|
-
if (this.parentObservers?.onGadgetExecutionStart) {
|
|
14280
|
-
const context = {
|
|
14281
|
-
iteration: this.iteration,
|
|
14282
|
-
gadgetName: call.gadgetName,
|
|
14283
|
-
invocationId: call.invocationId,
|
|
14284
|
-
parameters,
|
|
14285
|
-
logger: this.logger,
|
|
14286
|
-
subagentContext: gadgetStartSubagentContext
|
|
14287
|
-
};
|
|
14288
|
-
await this.safeObserve(() => this.parentObservers.onGadgetExecutionStart(context));
|
|
14289
|
-
}
|
|
14731
|
+
await notifyGadgetStart({
|
|
14732
|
+
tree: this.tree,
|
|
14733
|
+
hooks: this.hooks.observers,
|
|
14734
|
+
parentObservers: this.parentObservers,
|
|
14735
|
+
logger: this.logger,
|
|
14736
|
+
iteration: this.iteration,
|
|
14737
|
+
gadgetName: call.gadgetName,
|
|
14738
|
+
invocationId: call.invocationId,
|
|
14739
|
+
parameters
|
|
14740
|
+
});
|
|
14290
14741
|
let result;
|
|
14291
14742
|
if (shouldSkip) {
|
|
14292
14743
|
result = {
|
|
@@ -14299,7 +14750,7 @@ var init_stream_processor = __esm({
|
|
|
14299
14750
|
} else {
|
|
14300
14751
|
result = await this.executor.execute(call);
|
|
14301
14752
|
}
|
|
14302
|
-
if (result.result && this.hooks.interceptors?.interceptGadgetResult) {
|
|
14753
|
+
if ((result.result || result.error) && this.hooks.interceptors?.interceptGadgetResult) {
|
|
14303
14754
|
const context = {
|
|
14304
14755
|
iteration: this.iteration,
|
|
14305
14756
|
gadgetName: result.gadgetName,
|
|
@@ -14308,7 +14759,12 @@ var init_stream_processor = __esm({
|
|
|
14308
14759
|
executionTimeMs: result.executionTimeMs,
|
|
14309
14760
|
logger: this.logger
|
|
14310
14761
|
};
|
|
14311
|
-
|
|
14762
|
+
if (result.result) {
|
|
14763
|
+
result.result = this.hooks.interceptors.interceptGadgetResult(result.result, context);
|
|
14764
|
+
}
|
|
14765
|
+
if (result.error) {
|
|
14766
|
+
result.error = this.hooks.interceptors.interceptGadgetResult(result.error, context);
|
|
14767
|
+
}
|
|
14312
14768
|
}
|
|
14313
14769
|
if (this.hooks.controllers?.afterGadgetExecution) {
|
|
14314
14770
|
const context = {
|
|
@@ -14355,42 +14811,21 @@ var init_stream_processor = __esm({
|
|
|
14355
14811
|
}
|
|
14356
14812
|
}
|
|
14357
14813
|
}
|
|
14358
|
-
|
|
14359
|
-
|
|
14360
|
-
|
|
14361
|
-
|
|
14362
|
-
|
|
14363
|
-
|
|
14364
|
-
|
|
14365
|
-
|
|
14366
|
-
|
|
14367
|
-
|
|
14368
|
-
|
|
14369
|
-
|
|
14370
|
-
|
|
14371
|
-
|
|
14372
|
-
|
|
14373
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetExecutionComplete(context));
|
|
14374
|
-
}
|
|
14375
|
-
if (this.parentObservers?.onGadgetExecutionComplete) {
|
|
14376
|
-
const context = {
|
|
14377
|
-
iteration: this.iteration,
|
|
14378
|
-
gadgetName: result.gadgetName,
|
|
14379
|
-
invocationId: result.invocationId,
|
|
14380
|
-
parameters,
|
|
14381
|
-
finalResult: result.result,
|
|
14382
|
-
error: result.error,
|
|
14383
|
-
executionTimeMs: result.executionTimeMs,
|
|
14384
|
-
cost: result.cost,
|
|
14385
|
-
logger: this.logger,
|
|
14386
|
-
subagentContext: gadgetCompleteSubagentContext
|
|
14387
|
-
};
|
|
14388
|
-
await this.safeObserve(() => this.parentObservers.onGadgetExecutionComplete(context));
|
|
14389
|
-
}
|
|
14390
|
-
this.completedResults.set(result.invocationId, result);
|
|
14391
|
-
if (result.error) {
|
|
14392
|
-
this.failedInvocations.add(result.invocationId);
|
|
14393
|
-
}
|
|
14814
|
+
await notifyGadgetComplete({
|
|
14815
|
+
tree: this.tree,
|
|
14816
|
+
hooks: this.hooks.observers,
|
|
14817
|
+
parentObservers: this.parentObservers,
|
|
14818
|
+
logger: this.logger,
|
|
14819
|
+
iteration: this.iteration,
|
|
14820
|
+
gadgetName: result.gadgetName,
|
|
14821
|
+
invocationId: result.invocationId,
|
|
14822
|
+
parameters,
|
|
14823
|
+
finalResult: result.result,
|
|
14824
|
+
error: result.error,
|
|
14825
|
+
executionTimeMs: result.executionTimeMs,
|
|
14826
|
+
cost: result.cost
|
|
14827
|
+
});
|
|
14828
|
+
this.dependencyResolver.markComplete(result);
|
|
14394
14829
|
yield { type: "gadget_result", result };
|
|
14395
14830
|
}
|
|
14396
14831
|
/**
|
|
@@ -14422,77 +14857,45 @@ var init_stream_processor = __esm({
|
|
|
14422
14857
|
* Clears the inFlightExecutions map after all gadgets complete.
|
|
14423
14858
|
*/
|
|
14424
14859
|
async *waitForInFlightExecutions() {
|
|
14425
|
-
if (this.
|
|
14860
|
+
if (this.concurrencyManager.inFlightCount === 0 && !this.concurrencyManager.hasQueuedGadgets() && !this.concurrencyManager.hasExclusiveQueued) {
|
|
14426
14861
|
return;
|
|
14427
14862
|
}
|
|
14428
14863
|
this.logger.debug("Waiting for in-flight gadget executions", {
|
|
14429
|
-
count: this.
|
|
14430
|
-
|
|
14431
|
-
queuedCount: this.getQueuedGadgetCount()
|
|
14864
|
+
count: this.concurrencyManager.inFlightCount,
|
|
14865
|
+
queuedCount: this.concurrencyManager.getQueuedGadgetCount()
|
|
14432
14866
|
});
|
|
14433
14867
|
const POLL_INTERVAL_MS = 100;
|
|
14434
|
-
while (this.
|
|
14435
|
-
const allDone = this.
|
|
14868
|
+
while (this.concurrencyManager.inFlightCount > 0 || this.concurrencyManager.hasQueuedGadgets()) {
|
|
14869
|
+
const allDone = this.concurrencyManager.getAllDonePromise();
|
|
14436
14870
|
const result = await Promise.race([
|
|
14437
14871
|
allDone,
|
|
14438
|
-
new Promise((
|
|
14872
|
+
new Promise((resolve2) => setTimeout(() => resolve2("poll"), POLL_INTERVAL_MS))
|
|
14439
14873
|
]);
|
|
14440
14874
|
yield* this.drainCompletedResults();
|
|
14441
|
-
if (result === "done" && this.getTotalActiveGadgetCount() === 0 && !this.hasQueuedGadgets()) {
|
|
14875
|
+
if (result === "done" && this.concurrencyManager.getTotalActiveGadgetCount() === 0 && !this.concurrencyManager.hasQueuedGadgets()) {
|
|
14442
14876
|
break;
|
|
14443
14877
|
}
|
|
14444
14878
|
}
|
|
14445
|
-
this.
|
|
14446
|
-
if (this.
|
|
14879
|
+
this.concurrencyManager.clearInFlight();
|
|
14880
|
+
if (this.concurrencyManager.hasExclusiveQueued) {
|
|
14881
|
+
const exclusiveQueue = this.concurrencyManager.drainExclusiveQueue();
|
|
14447
14882
|
this.logger.debug("Processing deferred exclusive gadgets", {
|
|
14448
|
-
count:
|
|
14883
|
+
count: exclusiveQueue.length
|
|
14449
14884
|
});
|
|
14450
|
-
const
|
|
14451
|
-
this.exclusiveQueue = [];
|
|
14452
|
-
for (const call of queue) {
|
|
14885
|
+
for (const call of exclusiveQueue) {
|
|
14453
14886
|
for await (const evt of this.executeGadgetGenerator(call)) {
|
|
14454
14887
|
yield evt;
|
|
14455
14888
|
}
|
|
14456
14889
|
}
|
|
14457
14890
|
}
|
|
14458
14891
|
}
|
|
14459
|
-
/**
|
|
14460
|
-
* Check if there are any gadgets waiting in concurrency queues.
|
|
14461
|
-
*/
|
|
14462
|
-
hasQueuedGadgets() {
|
|
14463
|
-
for (const queue of this.concurrencyQueue.values()) {
|
|
14464
|
-
if (queue.length > 0) return true;
|
|
14465
|
-
}
|
|
14466
|
-
return false;
|
|
14467
|
-
}
|
|
14468
|
-
/**
|
|
14469
|
-
* Get total count of queued gadgets across all queues.
|
|
14470
|
-
*/
|
|
14471
|
-
getQueuedGadgetCount() {
|
|
14472
|
-
let count = 0;
|
|
14473
|
-
for (const queue of this.concurrencyQueue.values()) {
|
|
14474
|
-
count += queue.length;
|
|
14475
|
-
}
|
|
14476
|
-
return count;
|
|
14477
|
-
}
|
|
14478
|
-
/**
|
|
14479
|
-
* Get total count of actively executing gadgets across all types.
|
|
14480
|
-
* Used to know when all work is truly complete (not just when allDone resolves).
|
|
14481
|
-
*/
|
|
14482
|
-
getTotalActiveGadgetCount() {
|
|
14483
|
-
let total = 0;
|
|
14484
|
-
for (const count of this.activeCountByGadget.values()) {
|
|
14485
|
-
total += count;
|
|
14486
|
-
}
|
|
14487
|
-
return total;
|
|
14488
|
-
}
|
|
14489
14892
|
/**
|
|
14490
14893
|
* Handle a gadget that cannot execute because a dependency failed.
|
|
14491
14894
|
* Calls the onDependencySkipped controller to allow customization.
|
|
14492
14895
|
*/
|
|
14493
14896
|
async handleFailedDependency(call, failedDep) {
|
|
14494
14897
|
const events = [];
|
|
14495
|
-
const depResult = this.
|
|
14898
|
+
const depResult = this.dependencyResolver.getCompletedResult(failedDep);
|
|
14496
14899
|
const depError = depResult?.error ?? "Dependency failed";
|
|
14497
14900
|
let action = { action: "skip" };
|
|
14498
14901
|
if (this.hooks.controllers?.onDependencySkipped) {
|
|
@@ -14508,7 +14911,7 @@ var init_stream_processor = __esm({
|
|
|
14508
14911
|
action = await this.hooks.controllers.onDependencySkipped(context);
|
|
14509
14912
|
}
|
|
14510
14913
|
if (action.action === "skip") {
|
|
14511
|
-
this.
|
|
14914
|
+
this.dependencyResolver.markFailed(call.invocationId);
|
|
14512
14915
|
if (this.tree) {
|
|
14513
14916
|
const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
|
|
14514
14917
|
if (gadgetNode) {
|
|
@@ -14524,34 +14927,18 @@ var init_stream_processor = __esm({
|
|
|
14524
14927
|
failedDependencyError: depError
|
|
14525
14928
|
};
|
|
14526
14929
|
events.push(skipEvent);
|
|
14527
|
-
|
|
14528
|
-
|
|
14529
|
-
|
|
14530
|
-
|
|
14531
|
-
|
|
14532
|
-
|
|
14533
|
-
|
|
14534
|
-
|
|
14535
|
-
|
|
14536
|
-
|
|
14537
|
-
|
|
14538
|
-
|
|
14539
|
-
};
|
|
14540
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(context));
|
|
14541
|
-
}
|
|
14542
|
-
if (this.parentObservers?.onGadgetSkipped) {
|
|
14543
|
-
const context = {
|
|
14544
|
-
iteration: this.iteration,
|
|
14545
|
-
gadgetName: call.gadgetName,
|
|
14546
|
-
invocationId: call.invocationId,
|
|
14547
|
-
parameters: call.parameters ?? {},
|
|
14548
|
-
failedDependency: failedDep,
|
|
14549
|
-
failedDependencyError: depError,
|
|
14550
|
-
logger: this.logger,
|
|
14551
|
-
subagentContext: skipSubagentContext
|
|
14552
|
-
};
|
|
14553
|
-
await this.safeObserve(() => this.parentObservers.onGadgetSkipped(context));
|
|
14554
|
-
}
|
|
14930
|
+
await notifyGadgetSkipped({
|
|
14931
|
+
tree: this.tree,
|
|
14932
|
+
hooks: this.hooks.observers,
|
|
14933
|
+
parentObservers: this.parentObservers,
|
|
14934
|
+
logger: this.logger,
|
|
14935
|
+
iteration: this.iteration,
|
|
14936
|
+
gadgetName: call.gadgetName,
|
|
14937
|
+
invocationId: call.invocationId,
|
|
14938
|
+
parameters: call.parameters ?? {},
|
|
14939
|
+
failedDependency: failedDep,
|
|
14940
|
+
failedDependencyError: depError
|
|
14941
|
+
});
|
|
14555
14942
|
this.logger.info("Gadget skipped due to failed dependency", {
|
|
14556
14943
|
gadgetName: call.gadgetName,
|
|
14557
14944
|
invocationId: call.invocationId,
|
|
@@ -14574,7 +14961,7 @@ var init_stream_processor = __esm({
|
|
|
14574
14961
|
result: action.fallbackResult,
|
|
14575
14962
|
executionTimeMs: 0
|
|
14576
14963
|
};
|
|
14577
|
-
this.
|
|
14964
|
+
this.dependencyResolver.markComplete(fallbackResult);
|
|
14578
14965
|
events.push({ type: "gadget_result", result: fallbackResult });
|
|
14579
14966
|
this.logger.info("Using fallback result for gadget with failed dependency", {
|
|
14580
14967
|
gadgetName: call.gadgetName,
|
|
@@ -14605,7 +14992,7 @@ var init_stream_processor = __esm({
|
|
|
14605
14992
|
limit: this.maxGadgetsPerResponse,
|
|
14606
14993
|
currentCount: this.gadgetStartedCount
|
|
14607
14994
|
});
|
|
14608
|
-
this.
|
|
14995
|
+
this.dependencyResolver.markFailed(call.invocationId);
|
|
14609
14996
|
if (this.tree) {
|
|
14610
14997
|
const gadgetNode = this.tree.getNodeByInvocationId(call.invocationId);
|
|
14611
14998
|
if (gadgetNode) {
|
|
@@ -14626,34 +15013,18 @@ var init_stream_processor = __esm({
|
|
|
14626
15013
|
failedDependencyError: errorMessage
|
|
14627
15014
|
};
|
|
14628
15015
|
yield skipEvent;
|
|
14629
|
-
|
|
14630
|
-
|
|
14631
|
-
|
|
14632
|
-
|
|
14633
|
-
|
|
14634
|
-
|
|
14635
|
-
|
|
14636
|
-
|
|
14637
|
-
|
|
14638
|
-
|
|
14639
|
-
|
|
14640
|
-
|
|
14641
|
-
};
|
|
14642
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(context));
|
|
14643
|
-
}
|
|
14644
|
-
if (this.parentObservers?.onGadgetSkipped) {
|
|
14645
|
-
const context = {
|
|
14646
|
-
iteration: this.iteration,
|
|
14647
|
-
gadgetName: call.gadgetName,
|
|
14648
|
-
invocationId: call.invocationId,
|
|
14649
|
-
parameters: call.parameters ?? {},
|
|
14650
|
-
failedDependency: "maxGadgetsPerResponse",
|
|
14651
|
-
failedDependencyError: errorMessage,
|
|
14652
|
-
logger: this.logger,
|
|
14653
|
-
subagentContext: limitSkipSubagentContext
|
|
14654
|
-
};
|
|
14655
|
-
await this.safeObserve(() => this.parentObservers.onGadgetSkipped(context));
|
|
14656
|
-
}
|
|
15016
|
+
await notifyGadgetSkipped({
|
|
15017
|
+
tree: this.tree,
|
|
15018
|
+
hooks: this.hooks.observers,
|
|
15019
|
+
parentObservers: this.parentObservers,
|
|
15020
|
+
logger: this.logger,
|
|
15021
|
+
iteration: this.iteration,
|
|
15022
|
+
gadgetName: call.gadgetName,
|
|
15023
|
+
invocationId: call.invocationId,
|
|
15024
|
+
parameters: call.parameters ?? {},
|
|
15025
|
+
failedDependency: "maxGadgetsPerResponse",
|
|
15026
|
+
failedDependencyError: errorMessage
|
|
15027
|
+
});
|
|
14657
15028
|
return true;
|
|
14658
15029
|
}
|
|
14659
15030
|
this.gadgetStartedCount++;
|
|
@@ -14671,27 +15042,11 @@ var init_stream_processor = __esm({
|
|
|
14671
15042
|
return;
|
|
14672
15043
|
}
|
|
14673
15044
|
let progress = true;
|
|
14674
|
-
while (progress && this.
|
|
15045
|
+
while (progress && this.dependencyResolver.pendingCount > 0) {
|
|
14675
15046
|
progress = false;
|
|
14676
|
-
const readyToExecute =
|
|
14677
|
-
const readyToSkip = [];
|
|
14678
|
-
for (const [_invocationId, call] of this.gadgetsAwaitingDependencies) {
|
|
14679
|
-
const failedDep = call.dependencies.find(
|
|
14680
|
-
(dep) => this.failedInvocations.has(dep) || this.priorFailedInvocations.has(dep)
|
|
14681
|
-
);
|
|
14682
|
-
if (failedDep) {
|
|
14683
|
-
readyToSkip.push({ call, failedDep });
|
|
14684
|
-
continue;
|
|
14685
|
-
}
|
|
14686
|
-
const allSatisfied = call.dependencies.every(
|
|
14687
|
-
(dep) => this.completedResults.has(dep) || this.priorCompletedInvocations.has(dep)
|
|
14688
|
-
);
|
|
14689
|
-
if (allSatisfied) {
|
|
14690
|
-
readyToExecute.push(call);
|
|
14691
|
-
}
|
|
14692
|
-
}
|
|
15047
|
+
const { readyToExecute, readyToSkip } = this.dependencyResolver.getReadyCalls();
|
|
14693
15048
|
for (const { call, failedDep } of readyToSkip) {
|
|
14694
|
-
this.
|
|
15049
|
+
this.dependencyResolver.removePending(call.invocationId);
|
|
14695
15050
|
const skipEvents = await this.handleFailedDependency(call, failedDep);
|
|
14696
15051
|
for (const evt of skipEvents) {
|
|
14697
15052
|
yield evt;
|
|
@@ -14700,7 +15055,7 @@ var init_stream_processor = __esm({
|
|
|
14700
15055
|
}
|
|
14701
15056
|
if (readyToExecute.length > 0) {
|
|
14702
15057
|
for (const call of readyToExecute) {
|
|
14703
|
-
this.
|
|
15058
|
+
this.dependencyResolver.removePending(call.invocationId);
|
|
14704
15059
|
}
|
|
14705
15060
|
if (this.gadgetExecutionMode === "sequential") {
|
|
14706
15061
|
this.logger.debug("Executing ready gadgets sequentially", {
|
|
@@ -14753,11 +15108,12 @@ var init_stream_processor = __esm({
|
|
|
14753
15108
|
progress = true;
|
|
14754
15109
|
}
|
|
14755
15110
|
}
|
|
14756
|
-
if (this.
|
|
14757
|
-
const
|
|
14758
|
-
|
|
15111
|
+
if (this.dependencyResolver.pendingCount > 0) {
|
|
15112
|
+
const pendingEntries = this.dependencyResolver.getPendingEntries();
|
|
15113
|
+
const pendingIds = new Set(pendingEntries.map(([id]) => id));
|
|
15114
|
+
for (const [invocationId, call] of pendingEntries) {
|
|
14759
15115
|
const missingDeps = call.dependencies.filter(
|
|
14760
|
-
(dep) => !this.
|
|
15116
|
+
(dep) => !this.dependencyResolver.isCompleted(dep)
|
|
14761
15117
|
);
|
|
14762
15118
|
const circularDeps = missingDeps.filter((dep) => pendingIds.has(dep));
|
|
14763
15119
|
const trulyMissingDeps = missingDeps.filter((dep) => !pendingIds.has(dep));
|
|
@@ -14777,7 +15133,7 @@ var init_stream_processor = __esm({
|
|
|
14777
15133
|
circularDependencies: circularDeps,
|
|
14778
15134
|
missingDependencies: trulyMissingDeps
|
|
14779
15135
|
});
|
|
14780
|
-
this.
|
|
15136
|
+
this.dependencyResolver.markFailed(invocationId);
|
|
14781
15137
|
const skipEvent = {
|
|
14782
15138
|
type: "gadget_skipped",
|
|
14783
15139
|
gadgetName: call.gadgetName,
|
|
@@ -14787,51 +15143,20 @@ var init_stream_processor = __esm({
|
|
|
14787
15143
|
failedDependencyError: errorMessage
|
|
14788
15144
|
};
|
|
14789
15145
|
yield skipEvent;
|
|
14790
|
-
|
|
14791
|
-
|
|
14792
|
-
|
|
14793
|
-
|
|
14794
|
-
|
|
14795
|
-
|
|
14796
|
-
|
|
14797
|
-
|
|
14798
|
-
|
|
14799
|
-
|
|
14800
|
-
|
|
14801
|
-
|
|
14802
|
-
};
|
|
14803
|
-
await this.safeObserve(() => this.hooks.observers.onGadgetSkipped(context));
|
|
14804
|
-
}
|
|
14805
|
-
if (this.parentObservers?.onGadgetSkipped) {
|
|
14806
|
-
const context = {
|
|
14807
|
-
iteration: this.iteration,
|
|
14808
|
-
gadgetName: call.gadgetName,
|
|
14809
|
-
invocationId,
|
|
14810
|
-
parameters: call.parameters ?? {},
|
|
14811
|
-
failedDependency: missingDeps[0],
|
|
14812
|
-
failedDependencyError: errorMessage,
|
|
14813
|
-
logger: this.logger,
|
|
14814
|
-
subagentContext: timeoutSubagentContext
|
|
14815
|
-
};
|
|
14816
|
-
await this.safeObserve(() => this.parentObservers.onGadgetSkipped(context));
|
|
14817
|
-
}
|
|
15146
|
+
await notifyGadgetSkipped({
|
|
15147
|
+
tree: this.tree,
|
|
15148
|
+
hooks: this.hooks.observers,
|
|
15149
|
+
parentObservers: this.parentObservers,
|
|
15150
|
+
logger: this.logger,
|
|
15151
|
+
iteration: this.iteration,
|
|
15152
|
+
gadgetName: call.gadgetName,
|
|
15153
|
+
invocationId,
|
|
15154
|
+
parameters: call.parameters ?? {},
|
|
15155
|
+
failedDependency: missingDeps[0],
|
|
15156
|
+
failedDependencyError: errorMessage
|
|
15157
|
+
});
|
|
14818
15158
|
}
|
|
14819
|
-
this.
|
|
14820
|
-
}
|
|
14821
|
-
}
|
|
14822
|
-
/**
|
|
14823
|
-
* Safely execute an observer, catching and logging any errors.
|
|
14824
|
-
* Observers are non-critical, so errors are logged but don't crash the system.
|
|
14825
|
-
*/
|
|
14826
|
-
async safeObserve(fn) {
|
|
14827
|
-
try {
|
|
14828
|
-
await fn();
|
|
14829
|
-
} catch (error) {
|
|
14830
|
-
this.observerFailureCount++;
|
|
14831
|
-
this.logger.error("Observer threw error (ignoring)", {
|
|
14832
|
-
error: error instanceof Error ? error.message : String(error),
|
|
14833
|
-
failureCount: this.observerFailureCount
|
|
14834
|
-
});
|
|
15159
|
+
this.dependencyResolver.clearPending();
|
|
14835
15160
|
}
|
|
14836
15161
|
}
|
|
14837
15162
|
/**
|
|
@@ -14840,7 +15165,7 @@ var init_stream_processor = __esm({
|
|
|
14840
15165
|
*/
|
|
14841
15166
|
async runObserversInParallel(observers) {
|
|
14842
15167
|
if (observers.length === 0) return;
|
|
14843
|
-
await Promise.allSettled(observers.map((observer) =>
|
|
15168
|
+
await Promise.allSettled(observers.map((observer) => safeObserve(observer, this.logger)));
|
|
14844
15169
|
}
|
|
14845
15170
|
// ==========================================================================
|
|
14846
15171
|
// Public accessors for cross-iteration dependency tracking
|
|
@@ -14850,14 +15175,14 @@ var init_stream_processor = __esm({
|
|
|
14850
15175
|
* Used by Agent to accumulate completed IDs across iterations.
|
|
14851
15176
|
*/
|
|
14852
15177
|
getCompletedInvocationIds() {
|
|
14853
|
-
return
|
|
15178
|
+
return this.dependencyResolver.getCompletedInvocationIds();
|
|
14854
15179
|
}
|
|
14855
15180
|
/**
|
|
14856
15181
|
* Get all invocation IDs that failed in this iteration.
|
|
14857
15182
|
* Used by Agent to accumulate failed IDs across iterations.
|
|
14858
15183
|
*/
|
|
14859
15184
|
getFailedInvocationIds() {
|
|
14860
|
-
return
|
|
15185
|
+
return this.dependencyResolver.getFailedInvocationIds();
|
|
14861
15186
|
}
|
|
14862
15187
|
};
|
|
14863
15188
|
}
|
|
@@ -14884,6 +15209,7 @@ var init_agent = __esm({
|
|
|
14884
15209
|
init_event_handlers();
|
|
14885
15210
|
init_gadget_output_store();
|
|
14886
15211
|
init_hook_validators();
|
|
15212
|
+
init_safe_observe();
|
|
14887
15213
|
init_stream_processor();
|
|
14888
15214
|
init_tree_hook_bridge();
|
|
14889
15215
|
Agent = class {
|
|
@@ -15372,7 +15698,7 @@ var init_agent = __esm({
|
|
|
15372
15698
|
}
|
|
15373
15699
|
);
|
|
15374
15700
|
this.retryConfig.onRetry?.(error, streamAttempt);
|
|
15375
|
-
await
|
|
15701
|
+
await safeObserve(async () => {
|
|
15376
15702
|
if (this.hooks.observers?.onRetryAttempt) {
|
|
15377
15703
|
const subagentContext = getSubagentContextForNode(this.tree, currentLLMNodeId);
|
|
15378
15704
|
const hookContext = {
|
|
@@ -15386,7 +15712,7 @@ var init_agent = __esm({
|
|
|
15386
15712
|
};
|
|
15387
15713
|
await this.hooks.observers.onRetryAttempt(hookContext);
|
|
15388
15714
|
}
|
|
15389
|
-
});
|
|
15715
|
+
}, this.logger);
|
|
15390
15716
|
await this.sleep(finalDelay);
|
|
15391
15717
|
streamMetadata = null;
|
|
15392
15718
|
gadgetCallCount = 0;
|
|
@@ -15416,7 +15742,7 @@ var init_agent = __esm({
|
|
|
15416
15742
|
this.logger.silly("LLM response details", {
|
|
15417
15743
|
rawResponse: result.rawResponse
|
|
15418
15744
|
});
|
|
15419
|
-
await
|
|
15745
|
+
await safeObserve(async () => {
|
|
15420
15746
|
if (this.hooks.observers?.onLLMCallComplete) {
|
|
15421
15747
|
const subagentContext = getSubagentContextForNode(this.tree, currentLLMNodeId);
|
|
15422
15748
|
const context = {
|
|
@@ -15432,7 +15758,7 @@ var init_agent = __esm({
|
|
|
15432
15758
|
};
|
|
15433
15759
|
await this.hooks.observers.onLLMCallComplete(context);
|
|
15434
15760
|
}
|
|
15435
|
-
});
|
|
15761
|
+
}, this.logger);
|
|
15436
15762
|
this.completeLLMCallInTree(currentLLMNodeId, result);
|
|
15437
15763
|
const finalMessage = await this.processAfterLLMCallController(
|
|
15438
15764
|
currentIteration,
|
|
@@ -15466,7 +15792,7 @@ var init_agent = __esm({
|
|
|
15466
15792
|
}
|
|
15467
15793
|
} catch (error) {
|
|
15468
15794
|
const errorHandled = await this.handleLLMError(error, currentIteration);
|
|
15469
|
-
await
|
|
15795
|
+
await safeObserve(async () => {
|
|
15470
15796
|
if (this.hooks.observers?.onLLMCallError) {
|
|
15471
15797
|
const options = llmOptions ?? {
|
|
15472
15798
|
model: this.model,
|
|
@@ -15485,7 +15811,7 @@ var init_agent = __esm({
|
|
|
15485
15811
|
};
|
|
15486
15812
|
await this.hooks.observers.onLLMCallError(context);
|
|
15487
15813
|
}
|
|
15488
|
-
});
|
|
15814
|
+
}, this.logger);
|
|
15489
15815
|
if (!errorHandled) {
|
|
15490
15816
|
throw error;
|
|
15491
15817
|
}
|
|
@@ -15512,7 +15838,7 @@ var init_agent = __esm({
|
|
|
15512
15838
|
if (currentLLMNodeId) {
|
|
15513
15839
|
const node = this.tree.getNode(currentLLMNodeId);
|
|
15514
15840
|
if (node && node.type === "llm_call" && !node.completedAt) {
|
|
15515
|
-
await
|
|
15841
|
+
await safeObserve(async () => {
|
|
15516
15842
|
if (this.hooks.observers?.onLLMCallComplete) {
|
|
15517
15843
|
const subagentContext = getSubagentContextForNode(this.tree, currentLLMNodeId);
|
|
15518
15844
|
const context = {
|
|
@@ -15534,7 +15860,7 @@ var init_agent = __esm({
|
|
|
15534
15860
|
};
|
|
15535
15861
|
await this.hooks.observers.onLLMCallComplete(context);
|
|
15536
15862
|
}
|
|
15537
|
-
});
|
|
15863
|
+
}, this.logger);
|
|
15538
15864
|
this.tree.completeLLMCall(currentLLMNodeId, {
|
|
15539
15865
|
finishReason: "interrupted"
|
|
15540
15866
|
});
|
|
@@ -15554,7 +15880,7 @@ var init_agent = __esm({
|
|
|
15554
15880
|
const throttleDelay = this.rateLimitTracker.getRequiredDelayMs();
|
|
15555
15881
|
if (throttleDelay > 0) {
|
|
15556
15882
|
this.logger.debug("Rate limit throttling", { delayMs: throttleDelay });
|
|
15557
|
-
await
|
|
15883
|
+
await safeObserve(async () => {
|
|
15558
15884
|
if (this.hooks.observers?.onRateLimitThrottle) {
|
|
15559
15885
|
const subagentContext = getSubagentContextForNode(this.tree, llmNodeId);
|
|
15560
15886
|
const context = {
|
|
@@ -15566,7 +15892,7 @@ var init_agent = __esm({
|
|
|
15566
15892
|
};
|
|
15567
15893
|
await this.hooks.observers.onRateLimitThrottle(context);
|
|
15568
15894
|
}
|
|
15569
|
-
});
|
|
15895
|
+
}, this.logger);
|
|
15570
15896
|
await this.sleep(throttleDelay);
|
|
15571
15897
|
}
|
|
15572
15898
|
this.rateLimitTracker.reserveRequest();
|
|
@@ -15577,7 +15903,7 @@ var init_agent = __esm({
|
|
|
15577
15903
|
* Simple sleep utility for rate limit delays.
|
|
15578
15904
|
*/
|
|
15579
15905
|
sleep(ms) {
|
|
15580
|
-
return new Promise((
|
|
15906
|
+
return new Promise((resolve2) => setTimeout(resolve2, ms));
|
|
15581
15907
|
}
|
|
15582
15908
|
/**
|
|
15583
15909
|
* Handle LLM error through controller.
|
|
@@ -15629,18 +15955,6 @@ var init_agent = __esm({
|
|
|
15629
15955
|
}
|
|
15630
15956
|
return true;
|
|
15631
15957
|
}
|
|
15632
|
-
/**
|
|
15633
|
-
* Safely execute an observer, catching and logging any errors.
|
|
15634
|
-
*/
|
|
15635
|
-
async safeObserve(fn) {
|
|
15636
|
-
try {
|
|
15637
|
-
await fn();
|
|
15638
|
-
} catch (error) {
|
|
15639
|
-
this.logger.error("Observer threw error (ignoring)", {
|
|
15640
|
-
error: error instanceof Error ? error.message : String(error)
|
|
15641
|
-
});
|
|
15642
|
-
}
|
|
15643
|
-
}
|
|
15644
15958
|
/**
|
|
15645
15959
|
* Resolve max tokens from model catalog.
|
|
15646
15960
|
*/
|
|
@@ -15709,7 +16023,7 @@ var init_agent = __esm({
|
|
|
15709
16023
|
iteration,
|
|
15710
16024
|
reason: this.signal.reason
|
|
15711
16025
|
});
|
|
15712
|
-
await
|
|
16026
|
+
await safeObserve(async () => {
|
|
15713
16027
|
if (this.hooks.observers?.onAbort) {
|
|
15714
16028
|
const context = {
|
|
15715
16029
|
iteration,
|
|
@@ -15718,7 +16032,7 @@ var init_agent = __esm({
|
|
|
15718
16032
|
};
|
|
15719
16033
|
await this.hooks.observers.onAbort(context);
|
|
15720
16034
|
}
|
|
15721
|
-
});
|
|
16035
|
+
}, this.logger);
|
|
15722
16036
|
return true;
|
|
15723
16037
|
}
|
|
15724
16038
|
/**
|
|
@@ -15737,7 +16051,7 @@ var init_agent = __esm({
|
|
|
15737
16051
|
tokensBefore: compactionEvent.tokensBefore,
|
|
15738
16052
|
tokensAfter: compactionEvent.tokensAfter
|
|
15739
16053
|
});
|
|
15740
|
-
await
|
|
16054
|
+
await safeObserve(async () => {
|
|
15741
16055
|
if (this.hooks.observers?.onCompaction) {
|
|
15742
16056
|
await this.hooks.observers.onCompaction({
|
|
15743
16057
|
iteration,
|
|
@@ -15747,7 +16061,7 @@ var init_agent = __esm({
|
|
|
15747
16061
|
logger: this.logger
|
|
15748
16062
|
});
|
|
15749
16063
|
}
|
|
15750
|
-
});
|
|
16064
|
+
}, this.logger);
|
|
15751
16065
|
return { type: "compaction", event: compactionEvent };
|
|
15752
16066
|
}
|
|
15753
16067
|
/**
|
|
@@ -15800,7 +16114,7 @@ var init_agent = __esm({
|
|
|
15800
16114
|
parentId: this.parentNodeId,
|
|
15801
16115
|
request: llmOptions.messages
|
|
15802
16116
|
});
|
|
15803
|
-
await
|
|
16117
|
+
await safeObserve(async () => {
|
|
15804
16118
|
if (this.hooks.observers?.onLLMCallStart) {
|
|
15805
16119
|
const subagentContext = getSubagentContextForNode(this.tree, llmNode.id);
|
|
15806
16120
|
const context = {
|
|
@@ -15811,7 +16125,7 @@ var init_agent = __esm({
|
|
|
15811
16125
|
};
|
|
15812
16126
|
await this.hooks.observers.onLLMCallStart(context);
|
|
15813
16127
|
}
|
|
15814
|
-
});
|
|
16128
|
+
}, this.logger);
|
|
15815
16129
|
if (this.hooks.controllers?.beforeLLMCall) {
|
|
15816
16130
|
const context = {
|
|
15817
16131
|
iteration,
|
|
@@ -15834,7 +16148,7 @@ var init_agent = __esm({
|
|
|
15834
16148
|
llmOptions = { ...llmOptions, ...action.modifiedOptions };
|
|
15835
16149
|
}
|
|
15836
16150
|
}
|
|
15837
|
-
await
|
|
16151
|
+
await safeObserve(async () => {
|
|
15838
16152
|
if (this.hooks.observers?.onLLMCallReady) {
|
|
15839
16153
|
const subagentContext = getSubagentContextForNode(this.tree, llmNode.id);
|
|
15840
16154
|
const context = {
|
|
@@ -15848,7 +16162,7 @@ var init_agent = __esm({
|
|
|
15848
16162
|
};
|
|
15849
16163
|
await this.hooks.observers.onLLMCallReady(context);
|
|
15850
16164
|
}
|
|
15851
|
-
});
|
|
16165
|
+
}, this.logger);
|
|
15852
16166
|
return { options: llmOptions, llmNodeId: llmNode.id };
|
|
15853
16167
|
}
|
|
15854
16168
|
/**
|
|
@@ -16033,6 +16347,7 @@ __export(index_exports, {
|
|
|
16033
16347
|
collectText: () => collectText,
|
|
16034
16348
|
complete: () => complete,
|
|
16035
16349
|
createAnthropicProviderFromEnv: () => createAnthropicProviderFromEnv,
|
|
16350
|
+
createFileLoggingState: () => createFileLoggingState,
|
|
16036
16351
|
createGadget: () => createGadget,
|
|
16037
16352
|
createGadgetOutputViewer: () => createGadgetOutputViewer,
|
|
16038
16353
|
createGeminiProviderFromEnv: () => createGeminiProviderFromEnv,
|
|
@@ -16095,6 +16410,7 @@ __export(index_exports, {
|
|
|
16095
16410
|
parseManifest: () => parseManifest,
|
|
16096
16411
|
parseRetryAfterHeader: () => parseRetryAfterHeader,
|
|
16097
16412
|
randomDelay: () => randomDelay,
|
|
16413
|
+
resetFileLoggingState: () => resetFileLoggingState,
|
|
16098
16414
|
resolveConfig: () => resolveConfig,
|
|
16099
16415
|
resolveHintTemplate: () => resolveHintTemplate,
|
|
16100
16416
|
resolveModel: () => resolveModel,
|
|
@@ -16772,10 +17088,10 @@ function randomDelay(min, max) {
|
|
|
16772
17088
|
}
|
|
16773
17089
|
async function humanDelay(min = 50, max = 150) {
|
|
16774
17090
|
const delay = randomDelay(min, max);
|
|
16775
|
-
return new Promise((
|
|
17091
|
+
return new Promise((resolve2) => setTimeout(resolve2, delay));
|
|
16776
17092
|
}
|
|
16777
17093
|
async function withTimeout(fn, timeoutMs, signal) {
|
|
16778
|
-
return new Promise((
|
|
17094
|
+
return new Promise((resolve2, reject) => {
|
|
16779
17095
|
if (signal?.aborted) {
|
|
16780
17096
|
reject(new Error("Operation aborted"));
|
|
16781
17097
|
return;
|
|
@@ -16801,7 +17117,7 @@ async function withTimeout(fn, timeoutMs, signal) {
|
|
|
16801
17117
|
settled = true;
|
|
16802
17118
|
clearTimeout(timeoutId);
|
|
16803
17119
|
signal?.removeEventListener("abort", abortHandler);
|
|
16804
|
-
|
|
17120
|
+
resolve2(result);
|
|
16805
17121
|
}
|
|
16806
17122
|
}).catch((error) => {
|
|
16807
17123
|
if (!settled) {
|
|
@@ -16834,7 +17150,7 @@ async function withRetry(fn, options = {}) {
|
|
|
16834
17150
|
}
|
|
16835
17151
|
const waitTime = Math.min(currentDelay, maxDelay);
|
|
16836
17152
|
onRetry?.(error, attempt + 1, waitTime);
|
|
16837
|
-
await new Promise((
|
|
17153
|
+
await new Promise((resolve2) => setTimeout(resolve2, waitTime));
|
|
16838
17154
|
if (backoff === "exponential") {
|
|
16839
17155
|
currentDelay *= 2;
|
|
16840
17156
|
} else {
|
|
@@ -16914,6 +17230,7 @@ function getHostExports2(ctx) {
|
|
|
16914
17230
|
collectText,
|
|
16915
17231
|
complete,
|
|
16916
17232
|
createAnthropicProviderFromEnv,
|
|
17233
|
+
createFileLoggingState,
|
|
16917
17234
|
createGadget,
|
|
16918
17235
|
createGadgetOutputViewer,
|
|
16919
17236
|
createGeminiProviderFromEnv,
|
|
@@ -16976,6 +17293,7 @@ function getHostExports2(ctx) {
|
|
|
16976
17293
|
parseManifest,
|
|
16977
17294
|
parseRetryAfterHeader,
|
|
16978
17295
|
randomDelay,
|
|
17296
|
+
resetFileLoggingState,
|
|
16979
17297
|
resolveConfig,
|
|
16980
17298
|
resolveHintTemplate,
|
|
16981
17299
|
resolveModel,
|