@theokit/sdk 2.15.2 → 2.18.1
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/CHANGELOG.md +90 -0
- package/dist/a2a/index.cjs +875 -198
- package/dist/a2a/index.cjs.map +1 -1
- package/dist/a2a/index.js +876 -199
- package/dist/a2a/index.js.map +1 -1
- package/dist/{cron-BxLSz1UH.d.cts → cron-Bbg0mBOv.d.ts} +33 -3
- package/dist/{cron-DcaoP7aW.d.ts → cron-ZLSKbDbB.d.cts} +33 -3
- package/dist/cron.cjs +840 -187
- package/dist/cron.cjs.map +1 -1
- package/dist/cron.d.cts +2 -2
- package/dist/cron.d.ts +2 -2
- package/dist/cron.js +840 -187
- package/dist/cron.js.map +1 -1
- package/dist/define-tool.d.ts +9 -2
- package/dist/{errors-Bart0ptP.d.cts → errors-1tVcX3Fq.d.cts} +1 -1
- package/dist/{errors-DJuuubJK.d.ts → errors-qyVYfk9H.d.ts} +1 -1
- package/dist/errors.d.cts +2 -2
- package/dist/eval.cjs +846 -189
- package/dist/eval.cjs.map +1 -1
- package/dist/eval.js +846 -189
- package/dist/eval.js.map +1 -1
- package/dist/event-bus.d.ts +3 -0
- package/dist/index.cjs +977 -215
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +122 -27
- package/dist/index.d.ts +122 -27
- package/dist/index.js +977 -217
- package/dist/index.js.map +1 -1
- package/dist/internal/agent-loop/tool-dispatch.d.ts +3 -1
- package/dist/internal/agent-loop/tool-result-guard.d.ts +24 -0
- package/dist/internal/agent-loop/tool-timeout.d.ts +23 -0
- package/dist/internal/llm/openai.d.ts +4 -0
- package/dist/internal/llm/sse.d.ts +13 -1
- package/dist/internal/mcp/client.d.ts +1 -1
- package/dist/internal/memory/active-memory.d.ts +1 -1
- package/dist/internal/persistence/conversation-storage-fs.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-fs.d.ts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.cts +7 -1
- package/dist/internal/persistence/conversation-storage-memory.d.ts +7 -1
- package/dist/internal/persistence/pagination.d.cts +8 -0
- package/dist/internal/persistence/pagination.d.ts +8 -0
- package/dist/internal/plugins/index.cjs +135 -0
- package/dist/internal/plugins/index.cjs.map +1 -1
- package/dist/internal/plugins/index.js +135 -0
- package/dist/internal/plugins/index.js.map +1 -1
- package/dist/internal/plugins/manager.d.cts +21 -1
- package/dist/internal/plugins/manager.d.ts +21 -1
- package/dist/internal/plugins/types.d.cts +40 -0
- package/dist/internal/plugins/types.d.ts +40 -0
- package/dist/internal/{memory → resilience}/circuit-breaker.d.ts +5 -1
- package/dist/internal/runtime/hooks/hooks-frontmatter.d.ts +1 -1
- package/dist/internal/runtime/lifecycle/env-policy.d.ts +30 -0
- package/dist/internal/runtime/session/agent-session-store.d.ts +1 -0
- package/dist/internal/telemetry/span-names.d.ts +7 -1
- package/dist/job-queue.d.ts +29 -7
- package/dist/permission-engine.d.ts +32 -7
- package/dist/{run-DXy_MVwz.d.cts → run-pE-34AAo.d.cts} +64 -3
- package/dist/{run-DXy_MVwz.d.ts → run-pE-34AAo.d.ts} +64 -3
- package/dist/sandbox/index.cjs +53 -2
- package/dist/sandbox/index.cjs.map +1 -1
- package/dist/sandbox/index.js +53 -2
- package/dist/sandbox/index.js.map +1 -1
- package/dist/sandbox/local-sandbox.d.cts +11 -3
- package/dist/sandbox/local-sandbox.d.ts +11 -3
- package/dist/sandbox/types.d.cts +7 -0
- package/dist/sandbox/types.d.ts +7 -0
- package/dist/types/agent-prims.d.ts +6 -2
- package/dist/types/conversation-storage.d.ts +32 -2
- package/dist/types/mcp.d.ts +20 -0
- package/dist/types/run.d.ts +17 -0
- package/dist/workflow.cjs +6 -3
- package/dist/workflow.cjs.map +1 -1
- package/dist/workflow.js +6 -3
- package/dist/workflow.js.map +1 -1
- package/package.json +14 -14
package/dist/eval.cjs
CHANGED
|
@@ -888,6 +888,16 @@ var init_credential_pool_types = __esm({
|
|
|
888
888
|
});
|
|
889
889
|
|
|
890
890
|
// src/internal/llm/retry.ts
|
|
891
|
+
function computeBackoffMs(opts) {
|
|
892
|
+
const base = opts.baseMs ?? DEFAULT_BASE_MS;
|
|
893
|
+
const cap = opts.capMs ?? DEFAULT_CAP_MS;
|
|
894
|
+
const rng = opts.rng ?? Math.random;
|
|
895
|
+
if (opts.retryAfterMs !== void 0 && opts.retryAfterMs >= 0) {
|
|
896
|
+
return Math.max(base, Math.min(cap, opts.retryAfterMs));
|
|
897
|
+
}
|
|
898
|
+
const ceiling = Math.min(cap, base * 2 ** opts.attempt);
|
|
899
|
+
return Math.floor(rng() * ceiling);
|
|
900
|
+
}
|
|
891
901
|
function sleepWithAbort(ms, signal) {
|
|
892
902
|
if (ms <= 0 || signal.aborted) return Promise.resolve();
|
|
893
903
|
return new Promise((resolve3) => {
|
|
@@ -902,8 +912,11 @@ function sleepWithAbort(ms, signal) {
|
|
|
902
912
|
signal.addEventListener("abort", onAbort, { once: true });
|
|
903
913
|
});
|
|
904
914
|
}
|
|
915
|
+
var DEFAULT_BASE_MS, DEFAULT_CAP_MS;
|
|
905
916
|
var init_retry = __esm({
|
|
906
917
|
"src/internal/llm/retry.ts"() {
|
|
918
|
+
DEFAULT_BASE_MS = 500;
|
|
919
|
+
DEFAULT_CAP_MS = 32e3;
|
|
907
920
|
}
|
|
908
921
|
});
|
|
909
922
|
|
|
@@ -4564,13 +4577,15 @@ function buildGitInfo() {
|
|
|
4564
4577
|
init_errors();
|
|
4565
4578
|
|
|
4566
4579
|
// src/internal/llm/sse.ts
|
|
4567
|
-
|
|
4580
|
+
init_errors();
|
|
4581
|
+
var DEFAULT_SSE_IDLE_MS = 6e4;
|
|
4582
|
+
async function* parseSseStream(body, signal, idleTimeoutMs = DEFAULT_SSE_IDLE_MS) {
|
|
4568
4583
|
if (body === null) return;
|
|
4569
4584
|
const reader = body.getReader();
|
|
4570
4585
|
const decoder = new TextDecoder("utf-8");
|
|
4571
4586
|
const state2 = { buffer: "", event: "message", data: "" };
|
|
4572
4587
|
try {
|
|
4573
|
-
for await (const chunk of readChunks(reader, signal)) {
|
|
4588
|
+
for await (const chunk of readChunks(reader, signal, idleTimeoutMs)) {
|
|
4574
4589
|
state2.buffer += decoder.decode(chunk, { stream: true });
|
|
4575
4590
|
for (const record of drainCompleteRecords(state2)) yield record;
|
|
4576
4591
|
}
|
|
@@ -4580,14 +4595,38 @@ async function* parseSseStream(body, signal) {
|
|
|
4580
4595
|
releaseReader(reader);
|
|
4581
4596
|
}
|
|
4582
4597
|
}
|
|
4583
|
-
async function* readChunks(reader, signal) {
|
|
4598
|
+
async function* readChunks(reader, signal, idleTimeoutMs) {
|
|
4584
4599
|
while (true) {
|
|
4585
4600
|
if (signal.aborted) return;
|
|
4586
|
-
const { value, done } = await reader
|
|
4601
|
+
const { value, done } = await readWithIdleTimeout(reader, idleTimeoutMs);
|
|
4587
4602
|
if (done) return;
|
|
4588
4603
|
if (value !== void 0) yield value;
|
|
4589
4604
|
}
|
|
4590
4605
|
}
|
|
4606
|
+
function readWithIdleTimeout(reader, idleTimeoutMs) {
|
|
4607
|
+
if (idleTimeoutMs <= 0) return reader.read();
|
|
4608
|
+
return new Promise(
|
|
4609
|
+
(resolve3, reject) => {
|
|
4610
|
+
const timer = setTimeout(() => {
|
|
4611
|
+
reject(
|
|
4612
|
+
new NetworkError(`SSE stream idle for ${idleTimeoutMs}ms \u2014 upstream stalled`, {
|
|
4613
|
+
code: "stream_idle_timeout"
|
|
4614
|
+
})
|
|
4615
|
+
);
|
|
4616
|
+
}, idleTimeoutMs);
|
|
4617
|
+
reader.read().then(
|
|
4618
|
+
(result) => {
|
|
4619
|
+
clearTimeout(timer);
|
|
4620
|
+
resolve3(result);
|
|
4621
|
+
},
|
|
4622
|
+
(err) => {
|
|
4623
|
+
clearTimeout(timer);
|
|
4624
|
+
reject(err);
|
|
4625
|
+
}
|
|
4626
|
+
);
|
|
4627
|
+
}
|
|
4628
|
+
);
|
|
4629
|
+
}
|
|
4591
4630
|
async function cancelReaderQuietly(reader) {
|
|
4592
4631
|
try {
|
|
4593
4632
|
await reader.cancel();
|
|
@@ -5281,6 +5320,9 @@ var PersonalityStore = class {
|
|
|
5281
5320
|
}
|
|
5282
5321
|
};
|
|
5283
5322
|
|
|
5323
|
+
// src/internal/plugins/manager.ts
|
|
5324
|
+
init_errors();
|
|
5325
|
+
|
|
5284
5326
|
// src/internal/plugins/context.ts
|
|
5285
5327
|
function createPluginContext() {
|
|
5286
5328
|
const registrations = {
|
|
@@ -5342,6 +5384,9 @@ var PluginManager = class {
|
|
|
5342
5384
|
memoryProviders: []
|
|
5343
5385
|
};
|
|
5344
5386
|
#initialized = false;
|
|
5387
|
+
// #68 — registrations of plugins added post-init via `register()`, keyed by
|
|
5388
|
+
// plugin name so a re-register REPLACES (not appends) the prior hooks.
|
|
5389
|
+
#byName = /* @__PURE__ */ new Map();
|
|
5345
5390
|
async initialize(plugins) {
|
|
5346
5391
|
if (this.#initialized) {
|
|
5347
5392
|
throw new Error("PluginManager.initialize called twice \u2014 register only once per process");
|
|
@@ -5359,6 +5404,36 @@ var PluginManager = class {
|
|
|
5359
5404
|
await this.#dispatchPlugin(plugin);
|
|
5360
5405
|
}
|
|
5361
5406
|
}
|
|
5407
|
+
/**
|
|
5408
|
+
* #68 — register a single `general` plugin AFTER `initialize()` has run.
|
|
5409
|
+
*
|
|
5410
|
+
* The bulk `initialize()` is single-shot (one call per process); late
|
|
5411
|
+
* registration is a distinct, named operation used by adapters that install
|
|
5412
|
+
* a plugin per-session/per-request (e.g. the ACP permission veto, which is
|
|
5413
|
+
* installed once the permission mode + connection are known — after the
|
|
5414
|
+
* agent's own plugins were already initialized).
|
|
5415
|
+
*
|
|
5416
|
+
* Idempotent by plugin NAME: re-registering a plugin with the same name
|
|
5417
|
+
* REPLACES its prior hooks/tools instead of appending duplicates (the ACP
|
|
5418
|
+
* permission plugin is re-installed on every prompt).
|
|
5419
|
+
*
|
|
5420
|
+
* Only `general` plugins may be registered late — model-provider / memory
|
|
5421
|
+
* plugins are resolved during the bulk init and cannot be added afterwards.
|
|
5422
|
+
*/
|
|
5423
|
+
async register(plugin) {
|
|
5424
|
+
if (plugin.kind !== "general") {
|
|
5425
|
+
throw new ConfigurationError(
|
|
5426
|
+
`late register supports general plugins only (got "${plugin.kind}" for "${plugin.name}")`,
|
|
5427
|
+
{ code: "plugin_late_register_kind" }
|
|
5428
|
+
);
|
|
5429
|
+
}
|
|
5430
|
+
const prior = this.#byName.get(plugin.name);
|
|
5431
|
+
if (prior !== void 0) this.#unmerge(prior);
|
|
5432
|
+
const { ctx, registrations } = createPluginContext();
|
|
5433
|
+
await plugin.register(ctx);
|
|
5434
|
+
this.#byName.set(plugin.name, registrations);
|
|
5435
|
+
this.#merge(registrations);
|
|
5436
|
+
}
|
|
5362
5437
|
get aggregated() {
|
|
5363
5438
|
return this.#aggregated;
|
|
5364
5439
|
}
|
|
@@ -5439,6 +5514,64 @@ var PluginManager = class {
|
|
|
5439
5514
|
}
|
|
5440
5515
|
}
|
|
5441
5516
|
}
|
|
5517
|
+
// #65 — the previously-dead hooks, now wired. Fire-and-forget hooks run
|
|
5518
|
+
// in order (per-handler errors logged, never thrown); transform hooks fold
|
|
5519
|
+
// over the payload (a handler returning a value replaces it).
|
|
5520
|
+
/** @internal */
|
|
5521
|
+
async #runFireAndForget(name, ctx) {
|
|
5522
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
5523
|
+
try {
|
|
5524
|
+
await h(ctx);
|
|
5525
|
+
} catch (err) {
|
|
5526
|
+
process.stderr.write(
|
|
5527
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
5528
|
+
`
|
|
5529
|
+
);
|
|
5530
|
+
}
|
|
5531
|
+
}
|
|
5532
|
+
}
|
|
5533
|
+
/** @internal — fold: each handler may return a replacement payload; a throw keeps the prior value. */
|
|
5534
|
+
async #runTransform(name, payload, ctx) {
|
|
5535
|
+
let current = payload;
|
|
5536
|
+
for (const h of this.#aggregated.hooks.get(name) ?? []) {
|
|
5537
|
+
try {
|
|
5538
|
+
const out = await h(current, ctx);
|
|
5539
|
+
if (out !== void 0) current = out;
|
|
5540
|
+
} catch (err) {
|
|
5541
|
+
process.stderr.write(
|
|
5542
|
+
`[theokit-sdk] ${name} hook failed: ${err instanceof Error ? err.message : String(err)}
|
|
5543
|
+
`
|
|
5544
|
+
);
|
|
5545
|
+
}
|
|
5546
|
+
}
|
|
5547
|
+
return current;
|
|
5548
|
+
}
|
|
5549
|
+
/** #65 — fired after a tool call completes. @internal */
|
|
5550
|
+
runPostToolCallHooks(ctx) {
|
|
5551
|
+
return this.#runFireAndForget("post_tool_call", ctx);
|
|
5552
|
+
}
|
|
5553
|
+
/** #65 — fired before / after each LLM turn. @internal */
|
|
5554
|
+
runPreLlmCallHooks(ctx) {
|
|
5555
|
+
return this.#runFireAndForget("pre_llm_call", ctx);
|
|
5556
|
+
}
|
|
5557
|
+
runPostLlmCallHooks(ctx) {
|
|
5558
|
+
return this.#runFireAndForget("post_llm_call", ctx);
|
|
5559
|
+
}
|
|
5560
|
+
/** #65 — fired at run start / end. @internal */
|
|
5561
|
+
runOnSessionStartHooks(ctx) {
|
|
5562
|
+
return this.#runFireAndForget("on_session_start", ctx);
|
|
5563
|
+
}
|
|
5564
|
+
runOnSessionEndHooks(ctx) {
|
|
5565
|
+
return this.#runFireAndForget("on_session_end", ctx);
|
|
5566
|
+
}
|
|
5567
|
+
/** #65/#57 — transform tool results before they reach the LLM (the #57 seam). @internal */
|
|
5568
|
+
runTransformToolResultHooks(results, ctx) {
|
|
5569
|
+
return this.#runTransform("transform_tool_result", results, ctx);
|
|
5570
|
+
}
|
|
5571
|
+
/** #65 — transform the LLM output text before it is consumed. @internal */
|
|
5572
|
+
runTransformLlmOutputHooks(output, ctx) {
|
|
5573
|
+
return this.#runTransform("transform_llm_output", output, ctx);
|
|
5574
|
+
}
|
|
5442
5575
|
async #dispatchPlugin(plugin) {
|
|
5443
5576
|
if (plugin.kind === "general") {
|
|
5444
5577
|
const { ctx, registrations } = createPluginContext();
|
|
@@ -5466,7 +5599,29 @@ var PluginManager = class {
|
|
|
5466
5599
|
}
|
|
5467
5600
|
this.#aggregated.injected.push(...r.injected);
|
|
5468
5601
|
}
|
|
5602
|
+
/**
|
|
5603
|
+
* #68 — inverse of #merge: remove a prior registration's contributions from
|
|
5604
|
+
* the aggregated view by object identity. Used by `register()` to replace a
|
|
5605
|
+
* same-named plugin's hooks/tools instead of accumulating duplicates.
|
|
5606
|
+
*/
|
|
5607
|
+
#unmerge(r) {
|
|
5608
|
+
removeAll(this.#aggregated.tools, r.tools);
|
|
5609
|
+
removeAll(this.#aggregated.commands, r.commands);
|
|
5610
|
+
removeAll(this.#aggregated.injected, r.injected);
|
|
5611
|
+
for (const [hook, handlers] of r.hooks.entries()) {
|
|
5612
|
+
const existing = this.#aggregated.hooks.get(hook);
|
|
5613
|
+
if (existing === void 0) continue;
|
|
5614
|
+
removeAll(existing, handlers);
|
|
5615
|
+
if (existing.length === 0) this.#aggregated.hooks.delete(hook);
|
|
5616
|
+
}
|
|
5617
|
+
}
|
|
5469
5618
|
};
|
|
5619
|
+
function removeAll(arr, toRemove) {
|
|
5620
|
+
for (const item of toRemove) {
|
|
5621
|
+
const idx = arr.indexOf(item);
|
|
5622
|
+
if (idx !== -1) arr.splice(idx, 1);
|
|
5623
|
+
}
|
|
5624
|
+
}
|
|
5470
5625
|
|
|
5471
5626
|
// src/internal/telemetry/span-names.ts
|
|
5472
5627
|
var SPAN_NAMES = {
|
|
@@ -5474,7 +5629,12 @@ var SPAN_NAMES = {
|
|
|
5474
5629
|
AGENT_SEND: "agent.send",
|
|
5475
5630
|
MEMORY_RECALL: "memory.recall"};
|
|
5476
5631
|
var HISTOGRAM_NAMES = {
|
|
5477
|
-
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms"
|
|
5632
|
+
MEMORY_RECALL_DURATION_MS: "theokit_memory_recall_duration_ms",
|
|
5633
|
+
TOOL_CALL_DURATION_MS: "theokit_tool_call_duration_ms",
|
|
5634
|
+
LLM_CALL_DURATION_MS: "theokit_llm_call_duration_ms",
|
|
5635
|
+
LLM_TOKENS: "theokit_llm_tokens",
|
|
5636
|
+
/** M3 #66 — count of finishes where the provider omitted usage (silent undercount). */
|
|
5637
|
+
LLM_USAGE_MISSING: "theokit_llm_usage_missing"
|
|
5478
5638
|
};
|
|
5479
5639
|
function safeRequire(moduleName) {
|
|
5480
5640
|
try {
|
|
@@ -5809,7 +5969,25 @@ function createTelemetry(settings) {
|
|
|
5809
5969
|
enabled: true,
|
|
5810
5970
|
includeContent: settings.includeContent === true,
|
|
5811
5971
|
startSpan: startNewSpan,
|
|
5812
|
-
|
|
5972
|
+
// M3 #64 — actually nest the child under its parent instead of discarding it.
|
|
5973
|
+
// The parent's SpanContext is set on a fresh OTel context so the child links
|
|
5974
|
+
// to it (traceId + parentSpanId), reconstructing the causal trace tree. Falls
|
|
5975
|
+
// back to a root span when the parent has no valid span id (telemetry off /
|
|
5976
|
+
// NOOP), preserving the pre-M3 behavior for parentless callers.
|
|
5977
|
+
startChildSpan: (parent, name, attrs) => {
|
|
5978
|
+
const redactedAttrs = attrs === void 0 ? void 0 : redactAttrs(attrs);
|
|
5979
|
+
const opts = redactedAttrs ? { attributes: redactedAttrs } : void 0;
|
|
5980
|
+
const pctx = safe(() => parent?.spanContext(), void 0);
|
|
5981
|
+
const span = safe(() => {
|
|
5982
|
+
if (pctx !== void 0 && pctx.spanId !== "0".repeat(16)) {
|
|
5983
|
+
const childCtx = otel.trace.setSpanContext(otel.context.active(), pctx);
|
|
5984
|
+
return tracer.startSpan(name, opts, childCtx);
|
|
5985
|
+
}
|
|
5986
|
+
return tracer.startSpan(name, opts);
|
|
5987
|
+
}, NOOP_SPAN);
|
|
5988
|
+
if (span !== NOOP_SPAN) openSpans.add(span);
|
|
5989
|
+
return wrapSpan(span, openSpans);
|
|
5990
|
+
},
|
|
5813
5991
|
recordHistogram,
|
|
5814
5992
|
endAll: () => {
|
|
5815
5993
|
for (const span of openSpans) safe(() => span.end(), void 0);
|
|
@@ -5845,12 +6023,62 @@ function redactAttrs(attrs) {
|
|
|
5845
6023
|
}
|
|
5846
6024
|
return out;
|
|
5847
6025
|
}
|
|
6026
|
+
|
|
6027
|
+
// src/internal/runtime/lifecycle/env-policy.ts
|
|
6028
|
+
var SECRET_PATTERNS = [
|
|
6029
|
+
/KEY/i,
|
|
6030
|
+
/SECRET/i,
|
|
6031
|
+
/TOKEN/i,
|
|
6032
|
+
/PASSWORD/i,
|
|
6033
|
+
/PASSWD/i,
|
|
6034
|
+
/PASSPHRASE/i,
|
|
6035
|
+
/[_-]PWD/i,
|
|
6036
|
+
/CREDENTIAL/i,
|
|
6037
|
+
/PRIVATE/i,
|
|
6038
|
+
/_AUTH/i
|
|
6039
|
+
];
|
|
6040
|
+
var CORE_VARS = [
|
|
6041
|
+
"PATH",
|
|
6042
|
+
"HOME",
|
|
6043
|
+
"SHELL",
|
|
6044
|
+
"LANG",
|
|
6045
|
+
"LC_ALL",
|
|
6046
|
+
"LC_CTYPE",
|
|
6047
|
+
"TMPDIR",
|
|
6048
|
+
"TMP",
|
|
6049
|
+
"TEMP",
|
|
6050
|
+
"USER",
|
|
6051
|
+
"LOGNAME"
|
|
6052
|
+
];
|
|
6053
|
+
function isSecretName(name) {
|
|
6054
|
+
return SECRET_PATTERNS.some((re) => re.test(name));
|
|
6055
|
+
}
|
|
6056
|
+
function inheritsUnderPolicy(name, policy) {
|
|
6057
|
+
if (policy === "all") return true;
|
|
6058
|
+
if (policy === "core") return CORE_VARS.includes(name);
|
|
6059
|
+
return !isSecretName(name);
|
|
6060
|
+
}
|
|
6061
|
+
function resolveChildEnv(options = {}) {
|
|
6062
|
+
const parent = options.parent ?? process.env;
|
|
6063
|
+
const policy = options.policy ?? "inherit-scrubbed";
|
|
6064
|
+
const base = {};
|
|
6065
|
+
for (const [name, value] of Object.entries(parent)) {
|
|
6066
|
+
if (value !== void 0 && inheritsUnderPolicy(name, policy)) base[name] = value;
|
|
6067
|
+
}
|
|
6068
|
+
for (const [name, value] of Object.entries(options.overrides ?? {})) {
|
|
6069
|
+
base[name] = value;
|
|
6070
|
+
}
|
|
6071
|
+
return base;
|
|
6072
|
+
}
|
|
6073
|
+
|
|
6074
|
+
// src/internal/runtime/lifecycle/spawn-collect.ts
|
|
5848
6075
|
function spawnAndCollect(options) {
|
|
5849
6076
|
return new Promise((resolve3) => {
|
|
5850
6077
|
const timeoutMs = options.timeoutMs ?? 3e4;
|
|
5851
6078
|
const spawnOptions = {
|
|
5852
6079
|
cwd: options.cwd,
|
|
5853
|
-
|
|
6080
|
+
// #54 — scrub secret-like parent env by default; `options.env` still wins.
|
|
6081
|
+
env: resolveChildEnv({ policy: options.envPolicy, overrides: options.env })
|
|
5854
6082
|
};
|
|
5855
6083
|
const child = child_process.spawn(options.command, options.args ?? [], spawnOptions);
|
|
5856
6084
|
let stdout = "";
|
|
@@ -6068,15 +6296,24 @@ function warnMalformed(agentId, line) {
|
|
|
6068
6296
|
`
|
|
6069
6297
|
);
|
|
6070
6298
|
}
|
|
6299
|
+
function hydrateSessionLine(parsed) {
|
|
6300
|
+
if (typeof parsed.text !== "string" || parsed.role === void 0) return void 0;
|
|
6301
|
+
if (parsed.role === "user" || parsed.role === "assistant") {
|
|
6302
|
+
return { role: parsed.role, text: parsed.text };
|
|
6303
|
+
}
|
|
6304
|
+
if (parsed.role === "tool_call" || parsed.role === "tool_result") {
|
|
6305
|
+
const label = parsed.role === "tool_call" ? "tool call" : "tool result";
|
|
6306
|
+
return { role: "assistant", text: `[${label}] ${parsed.text}` };
|
|
6307
|
+
}
|
|
6308
|
+
return void 0;
|
|
6309
|
+
}
|
|
6071
6310
|
async function readSessionFile(cwd, agentId) {
|
|
6072
6311
|
const lines = await readJsonlLines(cwd, agentId);
|
|
6073
6312
|
const messages = [];
|
|
6074
6313
|
for (const line of lines) {
|
|
6075
6314
|
try {
|
|
6076
|
-
const
|
|
6077
|
-
if (
|
|
6078
|
-
messages.push({ role: parsed.role, text: parsed.text });
|
|
6079
|
-
}
|
|
6315
|
+
const msg = hydrateSessionLine(JSON.parse(line));
|
|
6316
|
+
if (msg !== void 0) messages.push(msg);
|
|
6080
6317
|
} catch {
|
|
6081
6318
|
warnMalformed(agentId, line);
|
|
6082
6319
|
}
|
|
@@ -6103,24 +6340,72 @@ async function readAllPersistedMessages(cwd, agentId) {
|
|
|
6103
6340
|
return messages;
|
|
6104
6341
|
}
|
|
6105
6342
|
async function appendAnyPersistedMessage(cwd, agentId, record) {
|
|
6343
|
+
await appendPersistedMessages(cwd, agentId, [record]);
|
|
6344
|
+
}
|
|
6345
|
+
async function appendPersistedMessages(cwd, agentId, records) {
|
|
6346
|
+
if (records.length === 0) return;
|
|
6106
6347
|
const path$1 = sessionFilePath(cwd, agentId);
|
|
6107
|
-
|
|
6108
|
-
|
|
6109
|
-
|
|
6348
|
+
const payload = records.map((r) => `${redactSecrets(JSON.stringify(r))}
|
|
6349
|
+
`).join("");
|
|
6350
|
+
const dir = path.dirname(path$1);
|
|
6351
|
+
let written = false;
|
|
6352
|
+
const attempt = async () => {
|
|
6353
|
+
await promises.mkdir(dir, { recursive: true });
|
|
6354
|
+
await withFileLock(path$1, async () => {
|
|
6355
|
+
await promises.appendFile(path$1, payload, "utf8");
|
|
6356
|
+
written = true;
|
|
6357
|
+
});
|
|
6358
|
+
};
|
|
6359
|
+
try {
|
|
6360
|
+
await attempt();
|
|
6361
|
+
} catch (cause) {
|
|
6362
|
+
if (written || cause.code !== "ENOENT") throw cause;
|
|
6363
|
+
await attempt();
|
|
6364
|
+
}
|
|
6365
|
+
}
|
|
6366
|
+
async function rewriteLockedSession(path, transform) {
|
|
6367
|
+
await withFileLock(path, async () => {
|
|
6368
|
+
let raw;
|
|
6369
|
+
try {
|
|
6370
|
+
raw = await promises.readFile(path, "utf8");
|
|
6371
|
+
} catch {
|
|
6372
|
+
return;
|
|
6373
|
+
}
|
|
6374
|
+
const lines = raw.split("\n").filter((line) => line.length > 0);
|
|
6375
|
+
const next = transform(lines);
|
|
6376
|
+
if (next === void 0) return;
|
|
6377
|
+
await replaceFileAtomic(path, next);
|
|
6378
|
+
});
|
|
6110
6379
|
}
|
|
6111
6380
|
async function compactSessionFile(cwd, agentId, maxTurns) {
|
|
6112
6381
|
const path = sessionFilePath(cwd, agentId);
|
|
6113
|
-
|
|
6114
|
-
|
|
6115
|
-
|
|
6116
|
-
|
|
6117
|
-
|
|
6118
|
-
|
|
6119
|
-
|
|
6120
|
-
|
|
6121
|
-
const
|
|
6382
|
+
if (!fs.existsSync(path)) return;
|
|
6383
|
+
await rewriteLockedSession(
|
|
6384
|
+
path,
|
|
6385
|
+
(lines) => lines.length <= maxTurns * 2 ? void 0 : `${lines.slice(-maxTurns).join("\n")}
|
|
6386
|
+
`
|
|
6387
|
+
);
|
|
6388
|
+
}
|
|
6389
|
+
async function truncateSessionTo(cwd, agentId, keepCount) {
|
|
6390
|
+
const path = sessionFilePath(cwd, agentId);
|
|
6391
|
+
if (!fs.existsSync(path)) return 0;
|
|
6392
|
+
let kept = 0;
|
|
6393
|
+
await rewriteLockedSession(path, (lines) => {
|
|
6394
|
+
const keep = Math.max(0, Math.min(keepCount, lines.length));
|
|
6395
|
+
kept = keep;
|
|
6396
|
+
if (keep === lines.length) return void 0;
|
|
6397
|
+
return keep === 0 ? "" : `${lines.slice(0, keep).join("\n")}
|
|
6122
6398
|
`;
|
|
6123
|
-
|
|
6399
|
+
});
|
|
6400
|
+
return kept;
|
|
6401
|
+
}
|
|
6402
|
+
|
|
6403
|
+
// src/internal/persistence/pagination.ts
|
|
6404
|
+
function paginate(items, opts) {
|
|
6405
|
+
if (opts === void 0 || opts.offset === void 0 && opts.limit === void 0) return items;
|
|
6406
|
+
const start = Math.max(0, opts.offset ?? 0);
|
|
6407
|
+
const end = opts.limit === void 0 ? items.length : start + Math.max(0, opts.limit);
|
|
6408
|
+
return items.slice(start, end);
|
|
6124
6409
|
}
|
|
6125
6410
|
|
|
6126
6411
|
// src/internal/persistence/conversation-storage-fs.ts
|
|
@@ -6133,23 +6418,31 @@ var FileSystemConversationStorage = class {
|
|
|
6133
6418
|
get root() {
|
|
6134
6419
|
return this.#root;
|
|
6135
6420
|
}
|
|
6136
|
-
async getMessages(conversationId) {
|
|
6421
|
+
async getMessages(conversationId, opts) {
|
|
6137
6422
|
const records = await readAllPersistedMessages(this.#root, conversationId);
|
|
6138
|
-
|
|
6423
|
+
const all = records.map(toStoredMessage);
|
|
6424
|
+
return paginate(all, opts);
|
|
6139
6425
|
}
|
|
6140
6426
|
async appendMessage(conversationId, message) {
|
|
6141
|
-
|
|
6142
|
-
|
|
6143
|
-
|
|
6144
|
-
|
|
6145
|
-
|
|
6146
|
-
|
|
6427
|
+
await appendAnyPersistedMessage(this.#root, conversationId, toRecord(message));
|
|
6428
|
+
}
|
|
6429
|
+
async appendMessages(conversationId, messages) {
|
|
6430
|
+
await appendPersistedMessages(this.#root, conversationId, messages.map(toRecord));
|
|
6431
|
+
}
|
|
6432
|
+
async truncateConversation(conversationId, keepCount) {
|
|
6433
|
+
return truncateSessionTo(this.#root, conversationId, keepCount);
|
|
6147
6434
|
}
|
|
6148
6435
|
async deleteConversation(conversationId) {
|
|
6149
6436
|
const safe3 = sanitizeIdentifier(conversationId, { maxLen: 128 });
|
|
6150
6437
|
const dirPath = safePathJoin(this.#root, ".theokit", "agents", safe3);
|
|
6151
6438
|
await promises.rm(dirPath, { recursive: true, force: true });
|
|
6152
6439
|
}
|
|
6440
|
+
async deleteScope(prefix) {
|
|
6441
|
+
const ids = await this.listConversationIds();
|
|
6442
|
+
const matching = ids.filter((id) => id.startsWith(prefix));
|
|
6443
|
+
for (const id of matching) await this.deleteConversation(id);
|
|
6444
|
+
return matching.length;
|
|
6445
|
+
}
|
|
6153
6446
|
async listConversationIds(opts = {}) {
|
|
6154
6447
|
const agentsRoot = safePathJoin(this.#root, ".theokit", "agents");
|
|
6155
6448
|
let entries;
|
|
@@ -6175,6 +6468,9 @@ function toStoredMessage(record) {
|
|
|
6175
6468
|
at: record.at
|
|
6176
6469
|
};
|
|
6177
6470
|
}
|
|
6471
|
+
function toRecord(message) {
|
|
6472
|
+
return { role: message.role, text: message.content, at: message.at ?? Date.now() };
|
|
6473
|
+
}
|
|
6178
6474
|
|
|
6179
6475
|
// src/internal/runtime/session/agent-session.ts
|
|
6180
6476
|
var DEFAULT_MAX_TURNS = 200;
|
|
@@ -6260,12 +6556,19 @@ async function readPersistedForCache(adapter, agentId) {
|
|
|
6260
6556
|
const records = await adapter.getMessages(agentId);
|
|
6261
6557
|
const out = [];
|
|
6262
6558
|
for (const r of records) {
|
|
6263
|
-
|
|
6264
|
-
|
|
6265
|
-
}
|
|
6559
|
+
const folded = foldStoredToSession(r);
|
|
6560
|
+
if (folded !== void 0) out.push(folded);
|
|
6266
6561
|
}
|
|
6267
6562
|
return out;
|
|
6268
6563
|
}
|
|
6564
|
+
function foldStoredToSession(r) {
|
|
6565
|
+
if (r.role === "user" || r.role === "assistant") return { role: r.role, text: r.content };
|
|
6566
|
+
if (r.role === "tool_call" || r.role === "tool_result") {
|
|
6567
|
+
const label = r.role === "tool_call" ? "tool call" : "tool result";
|
|
6568
|
+
return { role: "assistant", text: `[${label}] ${r.content}` };
|
|
6569
|
+
}
|
|
6570
|
+
return void 0;
|
|
6571
|
+
}
|
|
6269
6572
|
async function flushSessionWrites() {
|
|
6270
6573
|
while (pendingAppends.size > 0) {
|
|
6271
6574
|
const all = Array.from(pendingAppends.values());
|
|
@@ -8615,11 +8918,32 @@ function reasoningEffortFromParams(params) {
|
|
|
8615
8918
|
const thinking = params?.find((p) => p.id === "thinking");
|
|
8616
8919
|
return thinking !== void 0 && thinking.value.length > 0 ? thinking.value : void 0;
|
|
8617
8920
|
}
|
|
8921
|
+
function emitLlmMetrics(inputs, result, startAt) {
|
|
8922
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_CALL_DURATION_MS, Date.now() - startAt, {
|
|
8923
|
+
provider: inputs.llm.name
|
|
8924
|
+
});
|
|
8925
|
+
if (result.inputTokens === void 0 && result.outputTokens === void 0) {
|
|
8926
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.LLM_USAGE_MISSING, 1, {
|
|
8927
|
+
provider: inputs.llm.name
|
|
8928
|
+
});
|
|
8929
|
+
process.stderr.write(
|
|
8930
|
+
`[theokit-sdk] llm usage missing from ${inputs.llm.name} finish \u2014 budget may undercount
|
|
8931
|
+
`
|
|
8932
|
+
);
|
|
8933
|
+
return;
|
|
8934
|
+
}
|
|
8935
|
+
inputs.telemetry?.recordHistogram(
|
|
8936
|
+
HISTOGRAM_NAMES.LLM_TOKENS,
|
|
8937
|
+
(result.inputTokens ?? 0) + (result.outputTokens ?? 0),
|
|
8938
|
+
{ provider: inputs.llm.name }
|
|
8939
|
+
);
|
|
8940
|
+
}
|
|
8618
8941
|
async function streamLlmTurn(inputs, ctx) {
|
|
8619
|
-
const llmSpan = inputs.telemetry?.
|
|
8942
|
+
const llmSpan = inputs.telemetry?.startChildSpan(ctx.sendSpan, "llm.call", {
|
|
8620
8943
|
"model.id": inputs.model.id ?? "auto",
|
|
8621
8944
|
provider: inputs.llm.name
|
|
8622
8945
|
});
|
|
8946
|
+
const startAt = Date.now();
|
|
8623
8947
|
const signal = inputs.signal ?? new AbortController().signal;
|
|
8624
8948
|
const generator = inputs.llm.stream(
|
|
8625
8949
|
{
|
|
@@ -8662,6 +8986,7 @@ async function streamLlmTurn(inputs, ctx) {
|
|
|
8662
8986
|
inputTokens: result.inputTokens ?? 0,
|
|
8663
8987
|
outputTokens: result.outputTokens ?? 0
|
|
8664
8988
|
});
|
|
8989
|
+
emitLlmMetrics(inputs, result, startAt);
|
|
8665
8990
|
llmSpan?.end();
|
|
8666
8991
|
const stripped = stripThinkBlocks(collected.accumulatedText);
|
|
8667
8992
|
return {
|
|
@@ -8891,21 +9216,21 @@ async function executeTool(inputs, resolved, call) {
|
|
|
8891
9216
|
}
|
|
8892
9217
|
if (resolved.origin === "shell") return runShellTool(inputs, call);
|
|
8893
9218
|
if (resolved.origin === "memory") return runMemoryTool(resolved, call);
|
|
8894
|
-
if (resolved.origin === "custom") return runCustomTool(resolved, call);
|
|
9219
|
+
if (resolved.origin === "custom") return runCustomTool(resolved, call, inputs.signal);
|
|
8895
9220
|
return runMcpTool(inputs, resolved, call);
|
|
8896
9221
|
}
|
|
8897
9222
|
async function runMemoryTool(resolved, call) {
|
|
8898
9223
|
return runHandlerTool("memory", resolved.memoryHandler, call);
|
|
8899
9224
|
}
|
|
8900
|
-
async function runCustomTool(resolved, call) {
|
|
8901
|
-
return runHandlerTool("custom", resolved.customHandler, call);
|
|
9225
|
+
async function runCustomTool(resolved, call, signal) {
|
|
9226
|
+
return runHandlerTool("custom", resolved.customHandler, call, signal);
|
|
8902
9227
|
}
|
|
8903
|
-
async function runHandlerTool(kind, handler, call) {
|
|
9228
|
+
async function runHandlerTool(kind, handler, call, signal) {
|
|
8904
9229
|
if (handler === void 0) {
|
|
8905
9230
|
return { stdout: "", stderr: `${kind} tool ${call.name} has no handler`, exitCode: 127 };
|
|
8906
9231
|
}
|
|
8907
9232
|
try {
|
|
8908
|
-
const stdout = await handler(call.input);
|
|
9233
|
+
const stdout = await handler(call.input, { signal });
|
|
8909
9234
|
return { stdout, stderr: "", exitCode: 0 };
|
|
8910
9235
|
} catch (cause) {
|
|
8911
9236
|
const message = cause instanceof Error ? cause.message : String(cause);
|
|
@@ -8951,22 +9276,66 @@ ${result.stderr}`.trim();
|
|
|
8951
9276
|
return result.stdout.trim();
|
|
8952
9277
|
}
|
|
8953
9278
|
|
|
9279
|
+
// src/internal/agent-loop/tool-timeout.ts
|
|
9280
|
+
var TOOL_ABORTED_EXIT = 124;
|
|
9281
|
+
function abortedResult(signal) {
|
|
9282
|
+
const reason = signal.reason;
|
|
9283
|
+
const timedOut = reason?.name === "TimeoutError";
|
|
9284
|
+
return {
|
|
9285
|
+
stdout: "",
|
|
9286
|
+
stderr: timedOut ? "tool execution timed out" : "tool execution aborted",
|
|
9287
|
+
exitCode: TOOL_ABORTED_EXIT
|
|
9288
|
+
};
|
|
9289
|
+
}
|
|
9290
|
+
function raceToolExecution(exec, opts) {
|
|
9291
|
+
const { signal, timeoutMs } = opts;
|
|
9292
|
+
if (signal === void 0 && timeoutMs === void 0) return exec;
|
|
9293
|
+
const signals = [];
|
|
9294
|
+
if (signal !== void 0) signals.push(signal);
|
|
9295
|
+
if (timeoutMs !== void 0) signals.push(AbortSignal.timeout(timeoutMs));
|
|
9296
|
+
const merged = signals.length === 1 ? signals[0] : AbortSignal.any(signals);
|
|
9297
|
+
if (merged.aborted) return Promise.resolve(abortedResult(merged));
|
|
9298
|
+
return new Promise((resolve3, reject) => {
|
|
9299
|
+
let settled = false;
|
|
9300
|
+
const onAbort = () => {
|
|
9301
|
+
if (settled) return;
|
|
9302
|
+
settled = true;
|
|
9303
|
+
resolve3(abortedResult(merged));
|
|
9304
|
+
};
|
|
9305
|
+
merged.addEventListener("abort", onAbort, { once: true });
|
|
9306
|
+
exec.then(
|
|
9307
|
+
(r) => {
|
|
9308
|
+
if (settled) return;
|
|
9309
|
+
settled = true;
|
|
9310
|
+
merged.removeEventListener("abort", onAbort);
|
|
9311
|
+
resolve3(r);
|
|
9312
|
+
},
|
|
9313
|
+
(e) => {
|
|
9314
|
+
if (settled) return;
|
|
9315
|
+
settled = true;
|
|
9316
|
+
merged.removeEventListener("abort", onAbort);
|
|
9317
|
+
reject(e);
|
|
9318
|
+
}
|
|
9319
|
+
);
|
|
9320
|
+
});
|
|
9321
|
+
}
|
|
9322
|
+
|
|
8954
9323
|
// src/internal/agent-loop/tool-dispatch.ts
|
|
8955
|
-
async function dispatchTools(inputs, tools, toolCalls, events) {
|
|
9324
|
+
async function dispatchTools(inputs, tools, toolCalls, events, parentSpan) {
|
|
8956
9325
|
const maxConcurrent = inputs.maxConcurrentTools ?? 4;
|
|
8957
9326
|
return mapWithConcurrency(
|
|
8958
9327
|
toolCalls,
|
|
8959
9328
|
maxConcurrent,
|
|
8960
|
-
(call) => dispatchSingleCall(inputs, tools, call, events)
|
|
9329
|
+
(call) => dispatchSingleCall(inputs, tools, call, events, parentSpan)
|
|
8961
9330
|
);
|
|
8962
9331
|
}
|
|
8963
|
-
async function dispatchSingleCall(inputs, tools, call, events) {
|
|
9332
|
+
async function dispatchSingleCall(inputs, tools, call, events, parentSpan) {
|
|
8964
9333
|
const { call: workingCall, repairs } = applyRepairAndExtractCall(tools, call);
|
|
8965
9334
|
const callId = generateCallId();
|
|
8966
9335
|
const forkVeto = vetoFromForkWhitelist(inputs, workingCall, callId, events);
|
|
8967
9336
|
if (forkVeto !== void 0) return forkVeto;
|
|
8968
9337
|
const resolved = tools.find((tool) => tool.name === workingCall.name);
|
|
8969
|
-
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs);
|
|
9338
|
+
const toolSpan = startToolCallSpan(inputs, workingCall, resolved, callId, repairs, parentSpan);
|
|
8970
9339
|
events.push(buildToolUseRunning(inputs, callId, workingCall));
|
|
8971
9340
|
const pluginVeto = await vetoFromPluginPreHook(inputs, workingCall, callId, events);
|
|
8972
9341
|
if (pluginVeto !== void 0) {
|
|
@@ -8983,6 +9352,13 @@ async function dispatchSingleCall(inputs, tools, call, events) {
|
|
|
8983
9352
|
return fileVeto;
|
|
8984
9353
|
}
|
|
8985
9354
|
const result = await runToolWithLifecycle(inputs, resolved, workingCall, callId);
|
|
9355
|
+
await inputs.pluginManager?.runPostToolCallHooks({
|
|
9356
|
+
name: workingCall.name,
|
|
9357
|
+
args: workingCall.input,
|
|
9358
|
+
result: { stdout: result.stdout, stderr: result.stderr, exitCode: result.exitCode },
|
|
9359
|
+
agentId: inputs.agentId,
|
|
9360
|
+
runId: inputs.runId
|
|
9361
|
+
});
|
|
8986
9362
|
return finalizeSpanAndPostHook(inputs, workingCall, callId, result, events, toolSpan);
|
|
8987
9363
|
}
|
|
8988
9364
|
function applyRepairAndExtractCall(tools, call) {
|
|
@@ -9015,8 +9391,8 @@ function vetoFromForkWhitelist(inputs, call, callId, events) {
|
|
|
9015
9391
|
content: `Tool blocked by fork whitelist: ${whitelistDecision.reason}`
|
|
9016
9392
|
};
|
|
9017
9393
|
}
|
|
9018
|
-
function startToolCallSpan(inputs, call, resolved, callId, repairs) {
|
|
9019
|
-
const toolSpan = inputs.telemetry?.
|
|
9394
|
+
function startToolCallSpan(inputs, call, resolved, callId, repairs, parentSpan) {
|
|
9395
|
+
const toolSpan = inputs.telemetry?.startChildSpan(parentSpan, "tool.call", {
|
|
9020
9396
|
"tool.name": call.name,
|
|
9021
9397
|
"tool.origin": resolved?.origin ?? "unknown",
|
|
9022
9398
|
callId
|
|
@@ -9080,8 +9456,14 @@ async function runToolWithLifecycle(inputs, resolved, call, callId) {
|
|
|
9080
9456
|
conversationId: inputs.agentId,
|
|
9081
9457
|
callId
|
|
9082
9458
|
});
|
|
9083
|
-
const result = await executeTool(inputs, resolved, call)
|
|
9459
|
+
const result = await raceToolExecution(executeTool(inputs, resolved, call), {
|
|
9460
|
+
signal: inputs.signal,
|
|
9461
|
+
timeoutMs: inputs.perToolTimeoutMs
|
|
9462
|
+
});
|
|
9084
9463
|
const durationMs = Date.now() - startAt;
|
|
9464
|
+
inputs.telemetry?.recordHistogram(HISTOGRAM_NAMES.TOOL_CALL_DURATION_MS, durationMs, {
|
|
9465
|
+
"tool.name": call.name
|
|
9466
|
+
});
|
|
9085
9467
|
if (result.exitCode !== void 0 && result.exitCode !== 0 && result.exitCode !== null) {
|
|
9086
9468
|
await safeEmitToolHook(inputs.onToolError, {
|
|
9087
9469
|
toolName: call.name,
|
|
@@ -9180,6 +9562,35 @@ function buildToolUseCompleted(inputs, callId, call, result) {
|
|
|
9180
9562
|
};
|
|
9181
9563
|
}
|
|
9182
9564
|
|
|
9565
|
+
// src/internal/agent-loop/tool-result-guard.ts
|
|
9566
|
+
var OPEN = "<untrusted-tool-output>";
|
|
9567
|
+
var CLOSE = "</untrusted-tool-output>";
|
|
9568
|
+
var PII_PATTERNS = [
|
|
9569
|
+
/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}\b/g,
|
|
9570
|
+
// email
|
|
9571
|
+
/\b(?:\+?\d{1,3}[-.\s]?)?\(?\d{3}\)?[-.\s]?\d{3}[-.\s]?\d{4}\b/g
|
|
9572
|
+
// phone
|
|
9573
|
+
];
|
|
9574
|
+
function guardText(content, opts) {
|
|
9575
|
+
let out = content;
|
|
9576
|
+
if (opts.redactPii === true) {
|
|
9577
|
+
for (const re of PII_PATTERNS) out = out.replace(re, "[REDACTED]");
|
|
9578
|
+
}
|
|
9579
|
+
if (opts.delimit === true) {
|
|
9580
|
+
const safe3 = out.split(CLOSE).join("</ untrusted-tool-output>");
|
|
9581
|
+
out = `${OPEN}
|
|
9582
|
+
${safe3}
|
|
9583
|
+
${CLOSE}`;
|
|
9584
|
+
}
|
|
9585
|
+
return out;
|
|
9586
|
+
}
|
|
9587
|
+
function applyToolResultGuard(parts, opts) {
|
|
9588
|
+
if (opts.delimit !== true && opts.redactPii !== true) return parts;
|
|
9589
|
+
return parts.map(
|
|
9590
|
+
(p) => p.type === "tool_result" ? { ...p, content: guardText(p.content, opts) } : p
|
|
9591
|
+
);
|
|
9592
|
+
}
|
|
9593
|
+
|
|
9183
9594
|
// src/internal/budget/pricing-data.json
|
|
9184
9595
|
var pricing_data_default = {
|
|
9185
9596
|
_meta: {
|
|
@@ -9530,6 +9941,11 @@ async function runAgentLoop(inputs) {
|
|
|
9530
9941
|
try {
|
|
9531
9942
|
const ctx = await initLoopContext(inputs);
|
|
9532
9943
|
ctxRef = ctx;
|
|
9944
|
+
ctx.sendSpan = sendSpan;
|
|
9945
|
+
await inputs.pluginManager?.runOnSessionStartHooks({
|
|
9946
|
+
agentId: inputs.agentId,
|
|
9947
|
+
runId: inputs.runId
|
|
9948
|
+
});
|
|
9533
9949
|
const budget = inputs.budget ?? new IterationBudget({ maxIterations: inputs.maxIterations ?? 8 });
|
|
9534
9950
|
let lastTurnDecision;
|
|
9535
9951
|
while (budget.shouldContinue()) {
|
|
@@ -9557,6 +9973,7 @@ async function runAgentLoop(inputs) {
|
|
|
9557
9973
|
}
|
|
9558
9974
|
budget.consume();
|
|
9559
9975
|
inputs.budgetTracker?.nextIteration?.();
|
|
9976
|
+
if (inputs.signal?.aborted === true) break;
|
|
9560
9977
|
}
|
|
9561
9978
|
if (lastTurnDecision === "continue" && budget.shouldContinue() === false) {
|
|
9562
9979
|
ctx.stoppedAtIterationLimit = true;
|
|
@@ -9596,6 +10013,10 @@ async function runAgentLoop(inputs) {
|
|
|
9596
10013
|
...ctx.stoppedByDoomLoop === true ? { stoppedByDoomLoop: true } : {}
|
|
9597
10014
|
};
|
|
9598
10015
|
} finally {
|
|
10016
|
+
await inputs.pluginManager?.runOnSessionEndHooks({
|
|
10017
|
+
agentId: inputs.agentId,
|
|
10018
|
+
runId: inputs.runId
|
|
10019
|
+
});
|
|
9599
10020
|
if (ctxRef !== void 0 && ctxRef.memoryProviderHandle !== void 0 && inputs.memoryProvider !== void 0) {
|
|
9600
10021
|
try {
|
|
9601
10022
|
await inputs.memoryProvider.dispose(ctxRef.memoryProviderHandle);
|
|
@@ -9699,7 +10120,10 @@ async function finishOrReflect(inputs, ctx, llmOutput) {
|
|
|
9699
10120
|
return "done";
|
|
9700
10121
|
}
|
|
9701
10122
|
async function runIteration(inputs, ctx) {
|
|
10123
|
+
const hookCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
10124
|
+
await inputs.pluginManager?.runPreLlmCallHooks(hookCtx);
|
|
9702
10125
|
const llmOutput = await streamLlmTurn(inputs, ctx);
|
|
10126
|
+
await inputs.pluginManager?.runPostLlmCallHooks(hookCtx);
|
|
9703
10127
|
accumulateUsage(ctx.usage, llmOutput);
|
|
9704
10128
|
if (inputs.budgetTracker !== void 0) {
|
|
9705
10129
|
const modelId = inputs.model.id ?? "auto";
|
|
@@ -9725,6 +10149,13 @@ async function runIteration(inputs, ctx) {
|
|
|
9725
10149
|
}
|
|
9726
10150
|
return continueOrTerminate(inputs, ctx, llmOutput);
|
|
9727
10151
|
}
|
|
10152
|
+
async function transformLlmOutputText(inputs, text, ctx) {
|
|
10153
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformLlmOutputHooks(text, ctx) : text;
|
|
10154
|
+
}
|
|
10155
|
+
async function guardAndTransformToolResults(inputs, raw, ctx) {
|
|
10156
|
+
const guarded = inputs.toolResultGuard !== void 0 ? applyToolResultGuard(raw, inputs.toolResultGuard) : raw;
|
|
10157
|
+
return inputs.pluginManager !== void 0 ? inputs.pluginManager.runTransformToolResultHooks(guarded, ctx) : guarded;
|
|
10158
|
+
}
|
|
9728
10159
|
async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
9729
10160
|
if (llmOutput.errored) return "error";
|
|
9730
10161
|
if (llmOutput.text.length > 0) {
|
|
@@ -9733,8 +10164,18 @@ async function continueOrTerminate(inputs, ctx, llmOutput) {
|
|
|
9733
10164
|
if (llmOutput.stopReason !== "tool_use" || llmOutput.toolCalls.length === 0) {
|
|
9734
10165
|
return finishOrReflect(inputs, ctx, llmOutput);
|
|
9735
10166
|
}
|
|
9736
|
-
|
|
9737
|
-
const
|
|
10167
|
+
const tCtx = { agentId: inputs.agentId, runId: inputs.runId };
|
|
10168
|
+
const outText = await transformLlmOutputText(inputs, llmOutput.text, tCtx);
|
|
10169
|
+
ctx.messages.push(buildAssistantTurn(outText, llmOutput.toolCalls));
|
|
10170
|
+
const rawResults = await dispatchTools(
|
|
10171
|
+
inputs,
|
|
10172
|
+
ctx.tools,
|
|
10173
|
+
llmOutput.toolCalls,
|
|
10174
|
+
ctx.events,
|
|
10175
|
+
ctx.sendSpan
|
|
10176
|
+
// M3 #64 — nest tool.call spans under agent.send
|
|
10177
|
+
);
|
|
10178
|
+
const toolResults = await guardAndTransformToolResults(inputs, rawResults, tCtx);
|
|
9738
10179
|
ctx.messages.push({ role: "user", content: toolResults });
|
|
9739
10180
|
if (inputs.onStep !== void 0) {
|
|
9740
10181
|
const cb = inputs.onStep;
|
|
@@ -10164,6 +10605,56 @@ function mapAnthropicStatusToCode(status, body) {
|
|
|
10164
10605
|
function formatMessage(status, code) {
|
|
10165
10606
|
return `Anthropic API error: ${code} (HTTP ${status})`;
|
|
10166
10607
|
}
|
|
10608
|
+
var cachedJsonrepair;
|
|
10609
|
+
function loadJsonrepair() {
|
|
10610
|
+
if (cachedJsonrepair === void 0) {
|
|
10611
|
+
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('eval.cjs', document.baseURI).href)));
|
|
10612
|
+
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
10613
|
+
}
|
|
10614
|
+
return cachedJsonrepair;
|
|
10615
|
+
}
|
|
10616
|
+
function isPlainObject(v) {
|
|
10617
|
+
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
10618
|
+
}
|
|
10619
|
+
function toFiniteNumber(raw) {
|
|
10620
|
+
if (raw === "") return void 0;
|
|
10621
|
+
const n = Number(raw);
|
|
10622
|
+
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
10623
|
+
}
|
|
10624
|
+
function tryJson(raw, repair) {
|
|
10625
|
+
const t = raw.trimStart();
|
|
10626
|
+
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
10627
|
+
try {
|
|
10628
|
+
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
10629
|
+
} catch {
|
|
10630
|
+
return void 0;
|
|
10631
|
+
}
|
|
10632
|
+
}
|
|
10633
|
+
function heuristicCoerce(raw, repairJson) {
|
|
10634
|
+
if (raw === "true") return true;
|
|
10635
|
+
if (raw === "false") return false;
|
|
10636
|
+
if (raw === "null") return null;
|
|
10637
|
+
const n = toFiniteNumber(raw);
|
|
10638
|
+
if (n !== void 0) return n;
|
|
10639
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10640
|
+
return json === void 0 ? raw : json;
|
|
10641
|
+
}
|
|
10642
|
+
function coerceCandidates(raw, repairJson) {
|
|
10643
|
+
const out = [];
|
|
10644
|
+
if (raw === "true") out.push(true);
|
|
10645
|
+
else if (raw === "false") out.push(false);
|
|
10646
|
+
else if (raw === "null") out.push(null);
|
|
10647
|
+
const n = toFiniteNumber(raw);
|
|
10648
|
+
if (n !== void 0) out.push(n);
|
|
10649
|
+
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
10650
|
+
if (json !== void 0) out.push(json);
|
|
10651
|
+
out.push(raw);
|
|
10652
|
+
return out;
|
|
10653
|
+
}
|
|
10654
|
+
function objectShape(schema) {
|
|
10655
|
+
const shape = schema?.shape;
|
|
10656
|
+
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
10657
|
+
}
|
|
10167
10658
|
|
|
10168
10659
|
// src/internal/llm/finish.ts
|
|
10169
10660
|
function collapseSystemText(system) {
|
|
@@ -10176,9 +10667,21 @@ function parseToolArguments(buffered) {
|
|
|
10176
10667
|
try {
|
|
10177
10668
|
return JSON.parse(buffered);
|
|
10178
10669
|
} catch {
|
|
10670
|
+
const repaired = tryJson(buffered, true);
|
|
10671
|
+
if (isPlainObject(repaired)) return repaired;
|
|
10179
10672
|
return { raw: buffered };
|
|
10180
10673
|
}
|
|
10181
10674
|
}
|
|
10675
|
+
function mapOpenAIFinish(reason) {
|
|
10676
|
+
switch (reason) {
|
|
10677
|
+
case "tool_calls":
|
|
10678
|
+
return "tool_use";
|
|
10679
|
+
case "length":
|
|
10680
|
+
return "max_tokens";
|
|
10681
|
+
default:
|
|
10682
|
+
return "end_turn";
|
|
10683
|
+
}
|
|
10684
|
+
}
|
|
10182
10685
|
function makeLlmFinish(state2) {
|
|
10183
10686
|
const finish = {
|
|
10184
10687
|
stopReason: state2.stopReason,
|
|
@@ -11060,56 +11563,9 @@ function toOllamaTools(tools) {
|
|
|
11060
11563
|
}
|
|
11061
11564
|
}));
|
|
11062
11565
|
}
|
|
11063
|
-
|
|
11064
|
-
|
|
11065
|
-
|
|
11066
|
-
const req = module$1.createRequire((typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('eval.cjs', document.baseURI).href)));
|
|
11067
|
-
cachedJsonrepair = req("jsonrepair").jsonrepair;
|
|
11068
|
-
}
|
|
11069
|
-
return cachedJsonrepair;
|
|
11070
|
-
}
|
|
11071
|
-
function isPlainObject(v) {
|
|
11072
|
-
return v !== null && typeof v === "object" && !Array.isArray(v);
|
|
11073
|
-
}
|
|
11074
|
-
function toFiniteNumber(raw) {
|
|
11075
|
-
if (raw === "") return void 0;
|
|
11076
|
-
const n = Number(raw);
|
|
11077
|
-
return Number.isFinite(n) && String(n) === raw ? n : void 0;
|
|
11078
|
-
}
|
|
11079
|
-
function tryJson(raw, repair) {
|
|
11080
|
-
const t = raw.trimStart();
|
|
11081
|
-
if (!(t.startsWith("{") || t.startsWith("["))) return void 0;
|
|
11082
|
-
try {
|
|
11083
|
-
return JSON.parse(repair ? loadJsonrepair()(t) : t);
|
|
11084
|
-
} catch {
|
|
11085
|
-
return void 0;
|
|
11086
|
-
}
|
|
11087
|
-
}
|
|
11088
|
-
function heuristicCoerce(raw, repairJson) {
|
|
11089
|
-
if (raw === "true") return true;
|
|
11090
|
-
if (raw === "false") return false;
|
|
11091
|
-
if (raw === "null") return null;
|
|
11092
|
-
const n = toFiniteNumber(raw);
|
|
11093
|
-
if (n !== void 0) return n;
|
|
11094
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
11095
|
-
return json === void 0 ? raw : json;
|
|
11096
|
-
}
|
|
11097
|
-
function coerceCandidates(raw, repairJson) {
|
|
11098
|
-
const out = [];
|
|
11099
|
-
if (raw === "true") out.push(true);
|
|
11100
|
-
else if (raw === "false") out.push(false);
|
|
11101
|
-
else if (raw === "null") out.push(null);
|
|
11102
|
-
const n = toFiniteNumber(raw);
|
|
11103
|
-
if (n !== void 0) out.push(n);
|
|
11104
|
-
const json = tryJson(raw, false) ?? (repairJson ? tryJson(raw, true) : void 0);
|
|
11105
|
-
if (json !== void 0) out.push(json);
|
|
11106
|
-
out.push(raw);
|
|
11107
|
-
return out;
|
|
11108
|
-
}
|
|
11109
|
-
function objectShape(schema) {
|
|
11110
|
-
const shape = schema?.shape;
|
|
11111
|
-
return shape !== null && typeof shape === "object" ? shape : void 0;
|
|
11112
|
-
}
|
|
11566
|
+
|
|
11567
|
+
// src/internal/llm/openai.ts
|
|
11568
|
+
init_errors();
|
|
11113
11569
|
|
|
11114
11570
|
// src/sanitize/sanitize-tool-input.ts
|
|
11115
11571
|
function applyTrim(key, value, ctx) {
|
|
@@ -11354,8 +11810,12 @@ var OpenAIClient = class {
|
|
|
11354
11810
|
// model was actually given. Empty set (no tools) recovers nothing.
|
|
11355
11811
|
new Set(request.tools?.map((tool) => tool.name) ?? [])
|
|
11356
11812
|
);
|
|
11813
|
+
let sawDone = false;
|
|
11357
11814
|
for await (const record of parseSseStream(response.body, signal)) {
|
|
11358
|
-
if (record.data === "[DONE]")
|
|
11815
|
+
if (record.data === "[DONE]") {
|
|
11816
|
+
sawDone = true;
|
|
11817
|
+
break;
|
|
11818
|
+
}
|
|
11359
11819
|
let chunk;
|
|
11360
11820
|
try {
|
|
11361
11821
|
chunk = JSON.parse(record.data);
|
|
@@ -11375,6 +11835,11 @@ var OpenAIClient = class {
|
|
|
11375
11835
|
const events = accumulator.consume(chunk);
|
|
11376
11836
|
for (const event of events) yield event;
|
|
11377
11837
|
}
|
|
11838
|
+
if (!sawDone && !accumulator.finishReasonSeen) {
|
|
11839
|
+
throw new NetworkError("SSE stream truncated (no finish_reason / [DONE])", {
|
|
11840
|
+
code: "stream_truncated"
|
|
11841
|
+
});
|
|
11842
|
+
}
|
|
11378
11843
|
const drainEvent = accumulator.finalizeHeldText();
|
|
11379
11844
|
if (drainEvent !== void 0) yield drainEvent;
|
|
11380
11845
|
return accumulator.finish();
|
|
@@ -11474,8 +11939,15 @@ var OpenAIStreamAccumulator = class {
|
|
|
11474
11939
|
this.toolCalls.set(call.index, existing);
|
|
11475
11940
|
}
|
|
11476
11941
|
}
|
|
11942
|
+
/** M2 #61 — true once any chunk carried a non-null `finish_reason` (else a
|
|
11943
|
+
* stream ending without `[DONE]` is a truncation, not a clean end). */
|
|
11944
|
+
sawFinishReason = false;
|
|
11945
|
+
get finishReasonSeen() {
|
|
11946
|
+
return this.sawFinishReason;
|
|
11947
|
+
}
|
|
11477
11948
|
applyFinishReason(reason) {
|
|
11478
11949
|
if (reason === void 0 || reason === null) return;
|
|
11950
|
+
this.sawFinishReason = true;
|
|
11479
11951
|
this.stopReason = mapOpenAIFinish(reason);
|
|
11480
11952
|
}
|
|
11481
11953
|
finish() {
|
|
@@ -11520,18 +11992,6 @@ var OpenAIStreamAccumulator = class {
|
|
|
11520
11992
|
});
|
|
11521
11993
|
}
|
|
11522
11994
|
};
|
|
11523
|
-
function mapOpenAIFinish(reason) {
|
|
11524
|
-
switch (reason) {
|
|
11525
|
-
case "tool_calls":
|
|
11526
|
-
return "tool_use";
|
|
11527
|
-
case "length":
|
|
11528
|
-
return "max_tokens";
|
|
11529
|
-
case "stop":
|
|
11530
|
-
return "end_turn";
|
|
11531
|
-
default:
|
|
11532
|
-
return "end_turn";
|
|
11533
|
-
}
|
|
11534
|
-
}
|
|
11535
11995
|
function applyReasoningRequest(body, effort, providerName) {
|
|
11536
11996
|
if (providerName === "openai") {
|
|
11537
11997
|
body.reasoning_effort = effort;
|
|
@@ -11629,19 +12089,76 @@ function assistantMessage(message) {
|
|
|
11629
12089
|
|
|
11630
12090
|
// src/internal/llm/pool-aware-client.ts
|
|
11631
12091
|
init_errors();
|
|
12092
|
+
|
|
12093
|
+
// src/internal/resilience/circuit-breaker.ts
|
|
12094
|
+
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
12095
|
+
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
12096
|
+
var CircuitBreaker = class {
|
|
12097
|
+
constructor(opts = {}) {
|
|
12098
|
+
this.opts = opts;
|
|
12099
|
+
}
|
|
12100
|
+
opts;
|
|
12101
|
+
states = /* @__PURE__ */ new Map();
|
|
12102
|
+
/** @returns true when the breaker is open and the call should be skipped. */
|
|
12103
|
+
shouldSkip(key) {
|
|
12104
|
+
const state2 = this.states.get(key);
|
|
12105
|
+
if (state2 === void 0) return false;
|
|
12106
|
+
if (state2.cooldownUntilMs === 0) return false;
|
|
12107
|
+
if (this.now() < state2.cooldownUntilMs) return true;
|
|
12108
|
+
state2.cooldownUntilMs = 0;
|
|
12109
|
+
state2.consecutiveTimeouts = 0;
|
|
12110
|
+
return false;
|
|
12111
|
+
}
|
|
12112
|
+
recordSuccess(key) {
|
|
12113
|
+
const state2 = this.states.get(key);
|
|
12114
|
+
if (state2 === void 0) return;
|
|
12115
|
+
state2.consecutiveTimeouts = 0;
|
|
12116
|
+
state2.cooldownUntilMs = 0;
|
|
12117
|
+
}
|
|
12118
|
+
recordTimeout(key) {
|
|
12119
|
+
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12120
|
+
state2.consecutiveTimeouts += 1;
|
|
12121
|
+
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
12122
|
+
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
12123
|
+
}
|
|
12124
|
+
this.states.set(key, state2);
|
|
12125
|
+
}
|
|
12126
|
+
/** @internal — tests inspect counter state. */
|
|
12127
|
+
inspect(key) {
|
|
12128
|
+
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
12129
|
+
}
|
|
12130
|
+
now() {
|
|
12131
|
+
return this.opts.now?.() ?? Date.now();
|
|
12132
|
+
}
|
|
12133
|
+
};
|
|
12134
|
+
|
|
12135
|
+
// src/internal/llm/pool-aware-client.ts
|
|
12136
|
+
init_retry();
|
|
11632
12137
|
var PoolAwareLlmClient = class {
|
|
11633
|
-
constructor(pool, buildClient2, waitForAvailableMs = 3e4) {
|
|
12138
|
+
constructor(pool, buildClient2, waitForAvailableMs = 3e4, resilience = {}) {
|
|
11634
12139
|
this.pool = pool;
|
|
11635
12140
|
this.buildClient = buildClient2;
|
|
11636
12141
|
this.waitForAvailableMs = waitForAvailableMs;
|
|
11637
12142
|
this.name = `pool-aware:${pool.provider}`;
|
|
12143
|
+
this.breaker = resilience.breaker ?? new CircuitBreaker();
|
|
12144
|
+
this.backoffBaseMs = resilience.backoffBaseMs;
|
|
12145
|
+
this.rng = resilience.rng;
|
|
11638
12146
|
}
|
|
11639
12147
|
pool;
|
|
11640
12148
|
buildClient;
|
|
11641
12149
|
waitForAvailableMs;
|
|
11642
12150
|
name;
|
|
12151
|
+
/** M2 #60 — provider-level circuit breaker (consecutive-failure). */
|
|
12152
|
+
breaker;
|
|
12153
|
+
backoffBaseMs;
|
|
12154
|
+
rng;
|
|
11643
12155
|
// biome-ignore lint/complexity/noExcessiveCognitiveComplexity: stream() must serialize pool-select → build client → first-event probe → classify → retry/rotate/propagate. Extracting helpers fragments the linear narrative; the comments above each branch keep it readable.
|
|
11644
12156
|
async *stream(request, signal) {
|
|
12157
|
+
if (this.breaker.shouldSkip(this.pool.provider)) {
|
|
12158
|
+
throw new NetworkError(`${this.pool.provider} circuit open \u2014 failing fast`, {
|
|
12159
|
+
code: "circuit_open"
|
|
12160
|
+
});
|
|
12161
|
+
}
|
|
11645
12162
|
let hasRetried429 = false;
|
|
11646
12163
|
while (true) {
|
|
11647
12164
|
if (signal.aborted) throw abortError2(signal);
|
|
@@ -11656,6 +12173,7 @@ var PoolAwareLlmClient = class {
|
|
|
11656
12173
|
}
|
|
11657
12174
|
}
|
|
11658
12175
|
if (entry === null) {
|
|
12176
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
11659
12177
|
throw new CredentialPoolExhaustedError(
|
|
11660
12178
|
`All ${this.pool.provider} credentials exhausted; next retry available at ${this.nextRetryHint() ?? "unknown"}`,
|
|
11661
12179
|
{ provider: this.pool.provider, nextRetryAt: this.nextRetryHint() }
|
|
@@ -11665,10 +12183,19 @@ var PoolAwareLlmClient = class {
|
|
|
11665
12183
|
const realClient = this.buildClient(entry.accessToken);
|
|
11666
12184
|
const attempt = await tryFirstEvent(realClient, request, signal);
|
|
11667
12185
|
if (attempt.kind === "ok") {
|
|
12186
|
+
this.breaker.recordSuccess(this.pool.provider);
|
|
11668
12187
|
return yield* relayStream(attempt.generator, attempt.firstResult);
|
|
11669
12188
|
}
|
|
11670
12189
|
const decision = classifyAndDecide(attempt.error, hasRetried429);
|
|
11671
12190
|
if (decision === "retry") {
|
|
12191
|
+
await sleepWithAbort(
|
|
12192
|
+
computeBackoffMs({
|
|
12193
|
+
attempt: 0,
|
|
12194
|
+
...this.backoffBaseMs !== void 0 ? { baseMs: this.backoffBaseMs } : {},
|
|
12195
|
+
...this.rng !== void 0 ? { rng: this.rng } : {}
|
|
12196
|
+
}),
|
|
12197
|
+
signal
|
|
12198
|
+
);
|
|
11672
12199
|
hasRetried429 = true;
|
|
11673
12200
|
continue;
|
|
11674
12201
|
}
|
|
@@ -11688,6 +12215,7 @@ var PoolAwareLlmClient = class {
|
|
|
11688
12215
|
hasRetried429 = false;
|
|
11689
12216
|
continue;
|
|
11690
12217
|
}
|
|
12218
|
+
this.breaker.recordTimeout(this.pool.provider);
|
|
11691
12219
|
throw attempt.error;
|
|
11692
12220
|
}
|
|
11693
12221
|
}
|
|
@@ -12122,9 +12650,28 @@ function selectTransport(profile, apiKey) {
|
|
|
12122
12650
|
|
|
12123
12651
|
// src/internal/mcp/client.ts
|
|
12124
12652
|
init_errors();
|
|
12125
|
-
function createMcpClient(name, config) {
|
|
12653
|
+
function createMcpClient(name, config, fetchImpl = fetch) {
|
|
12126
12654
|
if (isStdio(config)) return new StdioMcpClient(name, config);
|
|
12127
|
-
return new HttpMcpClient(name, config);
|
|
12655
|
+
return new HttpMcpClient(name, config, fetchImpl);
|
|
12656
|
+
}
|
|
12657
|
+
var DEFAULT_MCP_TIMEOUT_MS = 3e4;
|
|
12658
|
+
var MAX_STDIO_BUFFER_BYTES = 8 * 1024 * 1024;
|
|
12659
|
+
var RECONNECT_BASE_MS = 250;
|
|
12660
|
+
var MAX_RECONNECT_ATTEMPTS = 2;
|
|
12661
|
+
function reconnectDelay(attempt) {
|
|
12662
|
+
const ceiling = RECONNECT_BASE_MS * 2 ** attempt;
|
|
12663
|
+
const ms = Math.floor(Math.random() * ceiling);
|
|
12664
|
+
return ms <= 0 ? Promise.resolve() : new Promise((resolve3) => setTimeout(resolve3, ms));
|
|
12665
|
+
}
|
|
12666
|
+
function mcpTimeoutError(name, timeoutMs) {
|
|
12667
|
+
return new NetworkError(`MCP ${name} request timed out after ${timeoutMs}ms`, {
|
|
12668
|
+
code: "mcp_timeout"
|
|
12669
|
+
});
|
|
12670
|
+
}
|
|
12671
|
+
function isAbortLike(cause) {
|
|
12672
|
+
if (typeof cause !== "object" || cause === null || !("name" in cause)) return false;
|
|
12673
|
+
const name = cause.name;
|
|
12674
|
+
return name === "TimeoutError" || name === "AbortError";
|
|
12128
12675
|
}
|
|
12129
12676
|
async function rpcInitialize(request) {
|
|
12130
12677
|
await request("initialize", {
|
|
@@ -12170,32 +12717,107 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
12170
12717
|
name;
|
|
12171
12718
|
child;
|
|
12172
12719
|
nextId = 1;
|
|
12720
|
+
// #59 — pending requests carry a reject + timer so a silent server times out
|
|
12721
|
+
// (typed error), a late reply after timeout is a no-op, and close() settles them.
|
|
12173
12722
|
pending = /* @__PURE__ */ new Map();
|
|
12174
12723
|
buffer = "";
|
|
12175
|
-
|
|
12724
|
+
// M2 #59 — reconnect-after-drop state. `dropped` is set when the child exits
|
|
12725
|
+
// unexpectedly OR times out (not via close()); the next request re-spawns with
|
|
12726
|
+
// backoff. `reconnectPromise` is a SINGLE in-flight reconnect shared by every
|
|
12727
|
+
// concurrent request so parallel tool dispatch after a drop awaits one handshake
|
|
12728
|
+
// instead of racing (or spuriously failing with mcp_not_init).
|
|
12729
|
+
dropped = false;
|
|
12730
|
+
reconnectAttempts = 0;
|
|
12731
|
+
reconnectPromise;
|
|
12732
|
+
get timeoutMs() {
|
|
12733
|
+
return this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12734
|
+
}
|
|
12735
|
+
/** Spawn the server child and wire stdout/stderr/error/exit handlers.
|
|
12736
|
+
* Shared by `initialize()` and the M2 #59 reconnect path. */
|
|
12737
|
+
spawnChild() {
|
|
12176
12738
|
const resolvedCwd = resolveMcpCwd(this.config.cwd);
|
|
12177
12739
|
const child = child_process.spawn(this.config.command, this.config.args ?? [], {
|
|
12178
12740
|
cwd: resolvedCwd,
|
|
12179
|
-
|
|
12741
|
+
// #54 (F-H1) — a third-party MCP server binary must not inherit host
|
|
12742
|
+
// secrets. Scrub secret-like vars by default; `config.env` still wins.
|
|
12743
|
+
env: resolveChildEnv({ policy: this.config.envPolicy, overrides: this.config.env })
|
|
12180
12744
|
});
|
|
12181
12745
|
this.child = child;
|
|
12182
12746
|
child.stdout.on("data", (chunk) => this.consume(chunk));
|
|
12183
12747
|
child.stderr.on("data", () => void 0);
|
|
12748
|
+
child.stdin.on("error", () => void 0);
|
|
12184
12749
|
child.on("error", () => {
|
|
12185
|
-
|
|
12186
|
-
|
|
12187
|
-
|
|
12188
|
-
|
|
12750
|
+
this.rejectAllPending(
|
|
12751
|
+
new NetworkError(`MCP ${this.name} process crashed`, { code: "mcp_crashed" })
|
|
12752
|
+
);
|
|
12753
|
+
});
|
|
12754
|
+
child.on("exit", () => {
|
|
12755
|
+
if (this.child !== child) return;
|
|
12756
|
+
this.child = void 0;
|
|
12757
|
+
this.dropped = true;
|
|
12758
|
+
this.rejectAllPending(
|
|
12759
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12760
|
+
);
|
|
12189
12761
|
});
|
|
12762
|
+
}
|
|
12763
|
+
async initialize() {
|
|
12764
|
+
this.spawnChild();
|
|
12190
12765
|
await super.initialize();
|
|
12191
12766
|
}
|
|
12767
|
+
/** M2 #59 — ensure a live child before a request. Reconnect (bounded, with
|
|
12768
|
+
* full-jitter backoff) when the client was dropped; fail fast when never
|
|
12769
|
+
* initialized. Concurrent callers share ONE reconnect handshake. */
|
|
12770
|
+
ensureConnected() {
|
|
12771
|
+
if (this.child !== void 0) return Promise.resolve();
|
|
12772
|
+
if (!this.dropped) {
|
|
12773
|
+
return Promise.reject(
|
|
12774
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12775
|
+
);
|
|
12776
|
+
}
|
|
12777
|
+
this.reconnectPromise ??= this.reconnect().finally(() => {
|
|
12778
|
+
this.reconnectPromise = void 0;
|
|
12779
|
+
});
|
|
12780
|
+
return this.reconnectPromise;
|
|
12781
|
+
}
|
|
12782
|
+
async reconnect() {
|
|
12783
|
+
if (this.reconnectAttempts >= MAX_RECONNECT_ATTEMPTS) {
|
|
12784
|
+
throw new NetworkError(`MCP ${this.name} reconnect exhausted`, { code: "mcp_disconnected" });
|
|
12785
|
+
}
|
|
12786
|
+
await reconnectDelay(this.reconnectAttempts);
|
|
12787
|
+
this.reconnectAttempts += 1;
|
|
12788
|
+
this.spawnChild();
|
|
12789
|
+
await super.initialize();
|
|
12790
|
+
this.dropped = false;
|
|
12791
|
+
this.reconnectAttempts = 0;
|
|
12792
|
+
}
|
|
12192
12793
|
async close() {
|
|
12193
|
-
|
|
12194
|
-
this.child
|
|
12794
|
+
this.rejectAllPending(new NetworkError(`MCP ${this.name} closed`, { code: "mcp_closed" }));
|
|
12795
|
+
const child = this.child;
|
|
12195
12796
|
this.child = void 0;
|
|
12797
|
+
this.dropped = false;
|
|
12798
|
+
child?.kill("SIGTERM");
|
|
12799
|
+
}
|
|
12800
|
+
/** Reject + clear every pending request (crash / close). @internal */
|
|
12801
|
+
rejectAllPending(error) {
|
|
12802
|
+
for (const entry of this.pending.values()) {
|
|
12803
|
+
clearTimeout(entry.timer);
|
|
12804
|
+
entry.reject(error);
|
|
12805
|
+
}
|
|
12806
|
+
this.pending.clear();
|
|
12196
12807
|
}
|
|
12197
12808
|
consume(chunk) {
|
|
12198
12809
|
this.buffer += chunk.toString("utf8");
|
|
12810
|
+
if (this.buffer.length > MAX_STDIO_BUFFER_BYTES) {
|
|
12811
|
+
this.buffer = "";
|
|
12812
|
+
this.rejectAllPending(
|
|
12813
|
+
new NetworkError(`MCP ${this.name} exceeded stdout buffer limit`, {
|
|
12814
|
+
code: "mcp_buffer_overflow"
|
|
12815
|
+
})
|
|
12816
|
+
);
|
|
12817
|
+
this.child?.kill("SIGKILL");
|
|
12818
|
+
this.child = void 0;
|
|
12819
|
+
return;
|
|
12820
|
+
}
|
|
12199
12821
|
let newlineIndex = this.buffer.indexOf("\n");
|
|
12200
12822
|
while (newlineIndex !== -1) {
|
|
12201
12823
|
const line = this.buffer.slice(0, newlineIndex).trim();
|
|
@@ -12212,23 +12834,47 @@ var StdioMcpClient = class extends BaseMcpClient {
|
|
|
12212
12834
|
return;
|
|
12213
12835
|
}
|
|
12214
12836
|
if (typeof message.id !== "number") return;
|
|
12215
|
-
const
|
|
12216
|
-
if (
|
|
12837
|
+
const entry = this.pending.get(message.id);
|
|
12838
|
+
if (entry === void 0) return;
|
|
12217
12839
|
this.pending.delete(message.id);
|
|
12218
|
-
|
|
12840
|
+
clearTimeout(entry.timer);
|
|
12841
|
+
entry.resolve(message);
|
|
12219
12842
|
}
|
|
12220
12843
|
request(method, params) {
|
|
12221
|
-
|
|
12222
|
-
|
|
12223
|
-
|
|
12224
|
-
|
|
12844
|
+
const child = this.child;
|
|
12845
|
+
if (child !== void 0) return this.send(child, method, params);
|
|
12846
|
+
if (this.dropped) return this.reconnectAndRequest(method, params);
|
|
12847
|
+
return Promise.reject(
|
|
12848
|
+
new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" })
|
|
12849
|
+
);
|
|
12850
|
+
}
|
|
12851
|
+
/** M2 #59 — reconnect a dropped client, then send. Separate async path so the
|
|
12852
|
+
* happy path above never pays an extra microtask tick. */
|
|
12853
|
+
async reconnectAndRequest(method, params) {
|
|
12854
|
+
await this.ensureConnected();
|
|
12855
|
+
const child = this.child;
|
|
12856
|
+
if (child === void 0) {
|
|
12857
|
+
throw new ConfigurationError(`MCP ${this.name} is not initialized`, { code: "mcp_not_init" });
|
|
12225
12858
|
}
|
|
12859
|
+
return this.send(child, method, params);
|
|
12860
|
+
}
|
|
12861
|
+
send(child, method, params) {
|
|
12226
12862
|
const id = this.nextId++;
|
|
12227
12863
|
const payload = { jsonrpc: "2.0", id, method, params };
|
|
12228
|
-
|
|
12864
|
+
child.stdin.write(`${JSON.stringify(payload)}
|
|
12229
12865
|
`);
|
|
12230
|
-
return new Promise((resolve3) => {
|
|
12231
|
-
|
|
12866
|
+
return new Promise((resolve3, reject) => {
|
|
12867
|
+
const timer = setTimeout(() => {
|
|
12868
|
+
this.pending.delete(id);
|
|
12869
|
+
reject(mcpTimeoutError(this.name, this.timeoutMs));
|
|
12870
|
+
this.child?.kill("SIGKILL");
|
|
12871
|
+
this.child = void 0;
|
|
12872
|
+
this.dropped = true;
|
|
12873
|
+
this.rejectAllPending(
|
|
12874
|
+
new NetworkError(`MCP ${this.name} disconnected`, { code: "mcp_disconnected" })
|
|
12875
|
+
);
|
|
12876
|
+
}, this.timeoutMs);
|
|
12877
|
+
this.pending.set(id, { resolve: resolve3, reject, timer });
|
|
12232
12878
|
});
|
|
12233
12879
|
}
|
|
12234
12880
|
};
|
|
@@ -12254,11 +12900,20 @@ var HttpMcpClient = class extends BaseMcpClient {
|
|
|
12254
12900
|
accept: "application/json",
|
|
12255
12901
|
...this.config.headers ?? {}
|
|
12256
12902
|
};
|
|
12257
|
-
const
|
|
12258
|
-
|
|
12259
|
-
|
|
12260
|
-
|
|
12261
|
-
|
|
12903
|
+
const timeoutMs = this.config.requestTimeoutMs ?? DEFAULT_MCP_TIMEOUT_MS;
|
|
12904
|
+
let response;
|
|
12905
|
+
try {
|
|
12906
|
+
response = await this.fetchImpl(this.config.url, {
|
|
12907
|
+
method: "POST",
|
|
12908
|
+
headers,
|
|
12909
|
+
body: JSON.stringify(payload),
|
|
12910
|
+
// #59 — bound the request; a non-responding endpoint aborts here.
|
|
12911
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
12912
|
+
});
|
|
12913
|
+
} catch (cause) {
|
|
12914
|
+
if (isAbortLike(cause)) throw mcpTimeoutError(this.name, timeoutMs);
|
|
12915
|
+
throw cause;
|
|
12916
|
+
}
|
|
12262
12917
|
if (!response.ok) {
|
|
12263
12918
|
throw new NetworkError(`MCP ${this.name} returned ${response.status}`, {
|
|
12264
12919
|
code: "mcp_http_error"
|
|
@@ -12373,11 +13028,33 @@ function resolveRunProvider(options) {
|
|
|
12373
13028
|
);
|
|
12374
13029
|
}
|
|
12375
13030
|
const parsedModel = parseModelId(options.model?.id);
|
|
12376
|
-
const
|
|
12377
|
-
const
|
|
12378
|
-
const
|
|
13031
|
+
const modelInferredProvider = parsedModel.provider !== void 0 && getProviderProfile(parsedModel.provider) !== void 0 ? parsedModel.provider : void 0;
|
|
13032
|
+
const keyInferredProvider = inferProviderFromApiKey(options.agentOptions.apiKey);
|
|
13033
|
+
const primary = options.agentOptions.providers?.routes?.[0]?.provider ?? keyInferredProvider ?? modelInferredProvider ?? detectPrimaryProvider();
|
|
13034
|
+
const effectiveModelId = modelInferredProvider !== void 0 && modelInferredProvider === primary ? parsedModel.name : options.model?.id ?? "claude-sonnet-4-6";
|
|
12379
13035
|
return { primary, effectiveModelId };
|
|
12380
13036
|
}
|
|
13037
|
+
function inferProviderFromApiKey(apiKey) {
|
|
13038
|
+
if (apiKey === void 0 || apiKey.length === 0) return void 0;
|
|
13039
|
+
const byPrefix = [
|
|
13040
|
+
{ provider: "openrouter", prefix: "sk-or-" },
|
|
13041
|
+
{ provider: "anthropic", prefix: "sk-ant-" },
|
|
13042
|
+
{ provider: "openai", prefix: "sk-" }
|
|
13043
|
+
];
|
|
13044
|
+
for (const { provider, prefix } of byPrefix) {
|
|
13045
|
+
if (apiKey.startsWith(prefix) && getProviderProfile(provider) !== void 0) {
|
|
13046
|
+
return provider;
|
|
13047
|
+
}
|
|
13048
|
+
}
|
|
13049
|
+
return void 0;
|
|
13050
|
+
}
|
|
13051
|
+
function mergeExplicitApiKey(pools, primary, apiKey) {
|
|
13052
|
+
if (apiKey === void 0 || apiKey.length === 0) return pools;
|
|
13053
|
+
if (isFixtureApiKey(apiKey) || apiKey === LOCAL_RUNTIME_MOCK_KEY) return pools;
|
|
13054
|
+
const existing = pools?.[primary];
|
|
13055
|
+
if (existing !== void 0 && existing.length > 0) return pools;
|
|
13056
|
+
return { ...pools ?? {}, [primary]: [apiKey] };
|
|
13057
|
+
}
|
|
12381
13058
|
function buildLoopInputs(options, runId, userText) {
|
|
12382
13059
|
const maxIterations = options.sendOptions.maxIterations;
|
|
12383
13060
|
if (maxIterations !== void 0 && (!Number.isInteger(maxIterations) || maxIterations < 1)) {
|
|
@@ -12388,7 +13065,11 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12388
13065
|
}
|
|
12389
13066
|
const { primary, effectiveModelId } = resolveRunProvider(options);
|
|
12390
13067
|
const fallback = options.agentOptions.providers?.fallback;
|
|
12391
|
-
const apiKeys =
|
|
13068
|
+
const apiKeys = mergeExplicitApiKey(
|
|
13069
|
+
options.agentOptions.providers?.apiKeys,
|
|
13070
|
+
primary,
|
|
13071
|
+
options.agentOptions.apiKey
|
|
13072
|
+
);
|
|
12392
13073
|
const credentialPoolStrategy = options.agentOptions.providers?.credentialPoolStrategy;
|
|
12393
13074
|
const extractToolCallsFromContent = options.agentOptions.providers?.routes?.[0]?.extractToolCallsFromContent;
|
|
12394
13075
|
const chain = resolveProviderChain({
|
|
@@ -12432,6 +13113,10 @@ function buildLoopInputs(options, runId, userText) {
|
|
|
12432
13113
|
// D318 — forward SendOptions.signal to the agent loop so streamLlmTurn
|
|
12433
13114
|
// can attach it to the LLM `fetch({ signal })` call.
|
|
12434
13115
|
...options.sendOptions.signal !== void 0 ? { signal: options.sendOptions.signal } : {},
|
|
13116
|
+
// #58 / #57 — forward the per-tool timeout + tool-result guard so a consumer
|
|
13117
|
+
// can enable them via SendOptions (not only internal AgentLoopInputs).
|
|
13118
|
+
...options.sendOptions.perToolTimeoutMs !== void 0 ? { perToolTimeoutMs: options.sendOptions.perToolTimeoutMs } : {},
|
|
13119
|
+
...options.sendOptions.toolResultGuard !== void 0 ? { toolResultGuard: options.sendOptions.toolResultGuard } : {},
|
|
12435
13120
|
// M1-2: per-send iteration ceiling (validated above). The loop reads
|
|
12436
13121
|
// inputs.maxIterations (default 8 when unset).
|
|
12437
13122
|
...maxIterations !== void 0 ? { maxIterations } : {},
|
|
@@ -12748,7 +13433,12 @@ async function runActiveMemory(args) {
|
|
|
12748
13433
|
hits: []
|
|
12749
13434
|
});
|
|
12750
13435
|
}
|
|
12751
|
-
const
|
|
13436
|
+
const tenantCtx = {
|
|
13437
|
+
namespace: args.namespace,
|
|
13438
|
+
userId: args.userId,
|
|
13439
|
+
scope: args.scope
|
|
13440
|
+
};
|
|
13441
|
+
const cached2 = args.cache?.get(args.userText, cfg.queryMode, tenantCtx);
|
|
12752
13442
|
if (cached2 !== void 0) return endRecallSpan(span, args, cached2);
|
|
12753
13443
|
const query = buildQuery(args.userText, args.priorMessages, cfg.queryMode, cfg.recentUserTurns);
|
|
12754
13444
|
if (query.trim().length === 0) {
|
|
@@ -12833,7 +13523,12 @@ function notifyBreaker(breaker, key, status) {
|
|
|
12833
13523
|
else if (status === "ok" || status === "no-recall") breaker.recordSuccess(key);
|
|
12834
13524
|
}
|
|
12835
13525
|
async function finalize(args, queryMode, result) {
|
|
12836
|
-
|
|
13526
|
+
const tenantCtx = {
|
|
13527
|
+
namespace: args.namespace,
|
|
13528
|
+
userId: args.userId,
|
|
13529
|
+
scope: args.scope
|
|
13530
|
+
};
|
|
13531
|
+
args.cache?.set(args.userText, queryMode, result, tenantCtx);
|
|
12837
13532
|
if (args.persistTranscripts === true && args.cwd !== void 0) {
|
|
12838
13533
|
await persistActiveMemoryTranscript(args.cwd, {
|
|
12839
13534
|
runId: args.runId ?? `run-${Date.now()}`,
|
|
@@ -13435,48 +14130,6 @@ var MEMORY_EMBEDDING_ADAPTERS = {
|
|
|
13435
14130
|
gemini: geminiMemoryEmbeddingProviderAdapter
|
|
13436
14131
|
};
|
|
13437
14132
|
|
|
13438
|
-
// src/internal/memory/circuit-breaker.ts
|
|
13439
|
-
var DEFAULT_MAX_TIMEOUTS = 3;
|
|
13440
|
-
var DEFAULT_COOLDOWN_MS2 = 6e4;
|
|
13441
|
-
var CircuitBreaker = class {
|
|
13442
|
-
constructor(opts = {}) {
|
|
13443
|
-
this.opts = opts;
|
|
13444
|
-
}
|
|
13445
|
-
opts;
|
|
13446
|
-
states = /* @__PURE__ */ new Map();
|
|
13447
|
-
/** @returns true when the breaker is open and the call should be skipped. */
|
|
13448
|
-
shouldSkip(key) {
|
|
13449
|
-
const state2 = this.states.get(key);
|
|
13450
|
-
if (state2 === void 0) return false;
|
|
13451
|
-
if (state2.cooldownUntilMs === 0) return false;
|
|
13452
|
-
if (this.now() < state2.cooldownUntilMs) return true;
|
|
13453
|
-
state2.cooldownUntilMs = 0;
|
|
13454
|
-
state2.consecutiveTimeouts = 0;
|
|
13455
|
-
return false;
|
|
13456
|
-
}
|
|
13457
|
-
recordSuccess(key) {
|
|
13458
|
-
const state2 = this.states.get(key);
|
|
13459
|
-
if (state2 === void 0) return;
|
|
13460
|
-
state2.consecutiveTimeouts = 0;
|
|
13461
|
-
state2.cooldownUntilMs = 0;
|
|
13462
|
-
}
|
|
13463
|
-
recordTimeout(key) {
|
|
13464
|
-
const state2 = this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13465
|
-
state2.consecutiveTimeouts += 1;
|
|
13466
|
-
if (state2.consecutiveTimeouts >= (this.opts.maxTimeouts ?? DEFAULT_MAX_TIMEOUTS)) {
|
|
13467
|
-
state2.cooldownUntilMs = this.now() + (this.opts.cooldownMs ?? DEFAULT_COOLDOWN_MS2);
|
|
13468
|
-
}
|
|
13469
|
-
this.states.set(key, state2);
|
|
13470
|
-
}
|
|
13471
|
-
/** @internal — tests inspect counter state. */
|
|
13472
|
-
inspect(key) {
|
|
13473
|
-
return this.states.get(key) ?? { consecutiveTimeouts: 0, cooldownUntilMs: 0 };
|
|
13474
|
-
}
|
|
13475
|
-
now() {
|
|
13476
|
-
return this.opts.now?.() ?? Date.now();
|
|
13477
|
-
}
|
|
13478
|
-
};
|
|
13479
|
-
|
|
13480
14133
|
// src/internal/persistence/fts5-sanitize.ts
|
|
13481
14134
|
var PHRASE_OPEN = "";
|
|
13482
14135
|
var PHRASE_CLOSE = "";
|
|
@@ -16710,7 +17363,9 @@ var SandboxBackend = class {
|
|
|
16710
17363
|
this.config = {
|
|
16711
17364
|
workDir: config.workDir ?? "/tmp",
|
|
16712
17365
|
timeoutMs: config.timeoutMs ?? 3e4,
|
|
16713
|
-
maxOutputBytes: config.maxOutputBytes ?? 5 * 1024 * 1024
|
|
17366
|
+
maxOutputBytes: config.maxOutputBytes ?? 5 * 1024 * 1024,
|
|
17367
|
+
// #54 — preserve the env policy so backends can scrub secrets.
|
|
17368
|
+
env: config.env ?? "inherit-scrubbed"
|
|
16714
17369
|
};
|
|
16715
17370
|
}
|
|
16716
17371
|
async readFile(path) {
|
|
@@ -16780,7 +17435,9 @@ var LocalSandbox = class extends SandboxBackend {
|
|
|
16780
17435
|
cwd: this.config.workDir,
|
|
16781
17436
|
timeout,
|
|
16782
17437
|
maxBuffer: max,
|
|
16783
|
-
encoding: "utf-8"
|
|
17438
|
+
encoding: "utf-8",
|
|
17439
|
+
// #54 — scrub secret-like host env vars from the child by default.
|
|
17440
|
+
env: resolveChildEnv({ policy: this.config.env })
|
|
16784
17441
|
},
|
|
16785
17442
|
(error, stdout, stderr) => {
|
|
16786
17443
|
resolve3(this.buildResult(error, stdout ?? "", stderr ?? ""));
|