@wrongstack/core 0.306.4 → 0.307.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/dist/chronicle/index.js +957 -953
- package/dist/chronicle/metrics-ingest.d.ts +55 -0
- package/dist/chronicle/metrics-schema.d.ts +86 -0
- package/dist/chronicle/metrics-store.d.ts +5 -120
- package/dist/chronicle/project-server.js +1083 -1084
- package/dist/chronicle/sqlite-journal-quota.d.ts +39 -0
- package/dist/chronicle/sqlite-journal-schema.d.ts +25 -0
- package/dist/chronicle/sqlite-journal.d.ts +12 -177
- package/dist/coordination/agents/index.js +1803 -1701
- package/dist/coordination/agents/project-agent-capture.d.ts +48 -0
- package/dist/coordination/agents/project-agent-identity.d.ts +3 -129
- package/dist/coordination/agents/project-agent-roster.d.ts +17 -0
- package/dist/coordination/agents/types.d.ts +6 -9
- package/dist/coordination/index.js +6051 -5880
- package/dist/coordination/mail-tools.d.ts +1 -1
- package/dist/coordination/mailbox-http-actor-query.d.ts +16 -0
- package/dist/coordination/mailbox-http-router.d.ts +0 -48
- package/dist/coordination/mailbox-http-sse.d.ts +12 -0
- package/dist/coordination/mailbox-message-types.d.ts +71 -0
- package/dist/coordination/mailbox-predicates.d.ts +13 -0
- package/dist/coordination/mailbox-project-server.js +1 -1
- package/dist/coordination/mailbox-session-sync.d.ts +8 -0
- package/dist/coordination/mailbox-types.d.ts +6 -793
- package/dist/core/agent-loop-context.d.ts +28 -0
- package/dist/core/agent-loop-detector.d.ts +25 -0
- package/dist/core/fallback-doctor.d.ts +72 -0
- package/dist/core/index.d.ts +1 -0
- package/dist/core/index.js +1859 -1335
- package/dist/defaults/index.js +3647 -3559
- package/dist/execution/autonomy-brain-llm.d.ts +55 -0
- package/dist/execution/autonomy-brain.d.ts +3 -156
- package/dist/execution/compaction-budget.d.ts +38 -0
- package/dist/execution/compaction-core.d.ts +3 -170
- package/dist/execution/compaction-elision.d.ts +58 -0
- package/dist/execution/council-orchestrator-helpers.d.ts +39 -0
- package/dist/execution/council-orchestrator.d.ts +3 -42
- package/dist/execution/council-response-parser.d.ts +56 -0
- package/dist/execution/index.js +2475 -2449
- package/dist/execution/prompt-enhancer.js +11 -3
- package/dist/execution/tool-executor-guard.d.ts +18 -0
- package/dist/execution/tool-executor-runner.d.ts +12 -0
- package/dist/execution/tool-executor.d.ts +0 -75
- package/dist/goal/index.js +19 -6
- package/dist/hq/index.js +8 -2
- package/dist/index.d.ts +1 -0
- package/dist/index.js +8021 -7405
- package/dist/infrastructure/index.js +3 -1
- package/dist/kernel/events/agent-events.d.ts +2 -0
- package/dist/kernel/events/provider-events.d.ts +13 -0
- package/dist/plugin/index.js +2590 -2561
- package/dist/plugins/auto-review-config.d.ts +53 -0
- package/dist/plugins/auto-review-git.d.ts +19 -0
- package/dist/plugins/auto-review-plugin.d.ts +2 -140
- package/dist/plugins/review-finding-verification.d.ts +7 -0
- package/dist/prompts/index.d.ts +1 -0
- package/dist/prompts/index.js +601 -0
- package/dist/prompts/prompt-journal.d.ts +103 -0
- package/dist/security/index.js +627 -652
- package/dist/security/permission-explain.d.ts +19 -0
- package/dist/security/permission-policy.d.ts +0 -101
- package/dist/security/yolo-risk.d.ts +5 -4
- package/dist/session-catalog/index.js +220 -212
- package/dist/session-catalog/project-server.js +227 -219
- package/dist/session-catalog/store-rebuild.d.ts +9 -0
- package/dist/session-catalog/store-schema.d.ts +48 -0
- package/dist/session-catalog/store.d.ts +0 -17
- package/dist/storage/director-state.d.ts +6 -0
- package/dist/storage/file-session-writer.d.ts +2 -58
- package/dist/storage/index.d.ts +2 -1
- package/dist/storage/index.js +2057 -1925
- package/dist/storage/orphan-lock-cleaner.d.ts +15 -0
- package/dist/storage/queue-store.d.ts +6 -0
- package/dist/storage/session-recovery.d.ts +9 -0
- package/dist/storage/session-store/rename-session.d.ts +16 -0
- package/dist/storage/session-store/resume-session.d.ts +26 -0
- package/dist/storage/session-store/session-store-clear.d.ts +11 -0
- package/dist/storage/session-store/session-store-index.d.ts +13 -0
- package/dist/storage/session-store.d.ts +1 -129
- package/dist/storage/session-summary-tracker.d.ts +39 -0
- package/dist/storage/session-write-buffer.d.ts +43 -0
- package/dist/storage/session-writer/session-writer-flush.d.ts +4 -0
- package/dist/storage/session-writer/session-writer-summary-tracker.d.ts +28 -0
- package/dist/tools/fallback-agent-model-assign-tool.d.ts +18 -0
- package/dist/tools/fallback-leader-model-set-tool.d.ts +18 -0
- package/dist/tools/fallback-manage-tools.d.ts +8 -45
- package/dist/tools/fallback-profile-manage-tool.d.ts +16 -0
- package/dist/tools/fallback-provider-key-set-tool.d.ts +17 -0
- package/dist/tools/fallback-provider-manage-tool.d.ts +26 -0
- package/dist/tools/index.js +5124 -5071
- package/dist/types/config/root.d.ts +7 -2
- package/dist/types/default-config.d.ts +1 -1
- package/dist/types/index.d.ts +54 -55
- package/dist/types/index.js +953 -932
- package/dist/types/runtime-capability-manifest.d.ts +5 -5
- package/dist/types/session-markers.d.ts +12 -0
- package/dist/types/session-markers.js +19 -0
- package/dist/worktree/worktree-manager.d.ts +1 -1
- package/instructions/agents/architect.md +2 -0
- package/instructions/agents/code-reviewer.md +1 -0
- package/instructions/agents/debugger.md +2 -0
- package/instructions/agents/executor.md +2 -0
- package/instructions/agents/explore.md +3 -1
- package/instructions/agents/refactor.md +2 -0
- package/instructions/agents/reviewer.md +1 -0
- package/instructions/coordination/subagent-baseline.md +68 -7
- package/instructions/llm/prompt-enhancer.md +2 -1
- package/instructions/sections/tool/common-patterns.md +30 -0
- package/instructions/sections/tool/delegation-compact.md +3 -1
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/system-lite.md +32 -4
- package/instructions/system-pro.md +63 -9
- package/instructions/system.md +56 -11
- package/package.json +9 -5
package/dist/core/index.js
CHANGED
|
@@ -1158,7 +1158,10 @@ var MAILBOX_TYPE_PROPERTIES = {
|
|
|
1158
1158
|
}
|
|
1159
1159
|
};
|
|
1160
1160
|
|
|
1161
|
-
// src/coordination/mailbox-
|
|
1161
|
+
// src/coordination/mailbox-predicates.ts
|
|
1162
|
+
var SESSION_RECIPIENT_PREFIX = "@session:";
|
|
1163
|
+
|
|
1164
|
+
// src/coordination/mailbox-session-sync.ts
|
|
1162
1165
|
function isAffectedBySessionAffinity(message) {
|
|
1163
1166
|
return message.sessionAffinity !== void 0;
|
|
1164
1167
|
}
|
|
@@ -1189,7 +1192,6 @@ async function acceptMailboxMessageForSession(message, currentSessionId, ctx) {
|
|
|
1189
1192
|
if (ctx?.allowUnscoped === true) return true;
|
|
1190
1193
|
return false;
|
|
1191
1194
|
}
|
|
1192
|
-
var SESSION_RECIPIENT_PREFIX = "@session:";
|
|
1193
1195
|
|
|
1194
1196
|
// src/coordination/sqlite-mailbox.ts
|
|
1195
1197
|
var SQLITE_MAILBOX_FILE = "_mailbox.sqlite";
|
|
@@ -5661,1281 +5663,1653 @@ function resolveEventSessionId(ctx) {
|
|
|
5661
5663
|
return session?.id ?? "";
|
|
5662
5664
|
}
|
|
5663
5665
|
|
|
5664
|
-
// src/core/
|
|
5665
|
-
function
|
|
5666
|
-
|
|
5667
|
-
|
|
5668
|
-
let
|
|
5669
|
-
let
|
|
5670
|
-
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5666
|
+
// src/core/agent-loop-context.ts
|
|
5667
|
+
function createAgentLoopContextManager(a, handlers) {
|
|
5668
|
+
let _lastEmittedMsgCount = -1;
|
|
5669
|
+
let _lastEmittedToolCount = -1;
|
|
5670
|
+
let _lastEmittedRevision = -1;
|
|
5671
|
+
let _lastEmittedMaxContext = -1;
|
|
5672
|
+
let _lastPreFlightMsgCount = -1;
|
|
5673
|
+
let _lastPreFlightToolCount = -1;
|
|
5674
|
+
let _lastPreFlightRevision = -1;
|
|
5675
|
+
let _lastCompactionMsgCount = -1;
|
|
5676
|
+
let _lastCompactionRevision = -1;
|
|
5677
|
+
let _lastCompactionToolsRef = null;
|
|
5678
|
+
let _lastCompactionSystemRef = null;
|
|
5679
|
+
let _lastCompactionRequestTokens;
|
|
5680
|
+
let _lastCompactionMaxContext = -1;
|
|
5681
|
+
let _lastCompactionWasNoop = false;
|
|
5682
|
+
let _cachedSysRef = null;
|
|
5683
|
+
let _cachedToolsRef = null;
|
|
5684
|
+
let _cachedOverheadTokens = 0;
|
|
5685
|
+
const calibrationKey = (model = a.ctx.model) => `${a.ctx.provider?.id ?? "unknown"}/${model}`;
|
|
5686
|
+
function currentMaxContext() {
|
|
5687
|
+
const metaLimit = a.ctx.meta?.["effectiveMaxContext"];
|
|
5688
|
+
const providerMax = a.ctx.provider.capabilities.maxContext;
|
|
5689
|
+
return typeof metaLimit === "number" && metaLimit > 0 ? metaLimit : typeof providerMax === "number" && providerMax > 0 ? providerMax : 2e5;
|
|
5677
5690
|
}
|
|
5678
|
-
|
|
5679
|
-
}
|
|
5680
|
-
|
|
5681
|
-
|
|
5682
|
-
|
|
5683
|
-
|
|
5684
|
-
|
|
5685
|
-
|
|
5686
|
-
|
|
5687
|
-
|
|
5688
|
-
|
|
5689
|
-
|
|
5690
|
-
|
|
5691
|
-
|
|
5692
|
-
|
|
5693
|
-
|
|
5694
|
-
|
|
5695
|
-
|
|
5696
|
-
|
|
5697
|
-
|
|
5698
|
-
|
|
5699
|
-
|
|
5700
|
-
|
|
5701
|
-
|
|
5702
|
-
|
|
5703
|
-
|
|
5704
|
-
|
|
5705
|
-
|
|
5706
|
-
|
|
5691
|
+
async function refreshProviderContextLimit(provider, model, opts = {}) {
|
|
5692
|
+
a.ctx.meta ??= {};
|
|
5693
|
+
const routeKey = `${provider.id}/${model}`;
|
|
5694
|
+
const previousRouteKey = a.ctx.meta["contextLimitRouteKey"];
|
|
5695
|
+
const routeChanged = previousRouteKey !== void 0 && previousRouteKey !== routeKey;
|
|
5696
|
+
const previousMaxContext = currentMaxContext();
|
|
5697
|
+
if (routeChanged) {
|
|
5698
|
+
delete a.ctx.meta["providerOverflowMaxContext"];
|
|
5699
|
+
delete a.ctx.meta["contextLimitBaseline"];
|
|
5700
|
+
delete a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
5701
|
+
delete a.ctx.meta["effectiveMaxContext"];
|
|
5702
|
+
delete a.ctx.meta["effectiveMaxContextSource"];
|
|
5703
|
+
delete a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
5704
|
+
delete a.ctx.meta["contextLimitLastEmittedSource"];
|
|
5705
|
+
}
|
|
5706
|
+
a.ctx.meta["contextLimitRouteKey"] = routeKey;
|
|
5707
|
+
const routeMeta = a.ctx.meta["effectiveMaxContext"];
|
|
5708
|
+
const routeProviderCap = provider.capabilities.maxContext;
|
|
5709
|
+
const currentEffective = typeof routeMeta === "number" && routeMeta > 0 ? routeMeta : typeof routeProviderCap === "number" && routeProviderCap > 0 ? routeProviderCap : 2e5;
|
|
5710
|
+
const emitEffectiveLimit = (effective2, source) => {
|
|
5711
|
+
const lastEmitted = a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
5712
|
+
const lastEmittedSource = a.ctx.meta["contextLimitLastEmittedSource"];
|
|
5713
|
+
if (lastEmitted === effective2 && lastEmittedSource === source) return;
|
|
5714
|
+
const eventPrevious = typeof lastEmitted === "number" && Number.isFinite(lastEmitted) && lastEmitted > 0 ? Math.floor(lastEmitted) : previousMaxContext;
|
|
5715
|
+
a.ctx.meta["contextLimitLastEmittedMaxContext"] = effective2;
|
|
5716
|
+
a.ctx.meta["contextLimitLastEmittedSource"] = source;
|
|
5717
|
+
a.events.emit("ctx.max_context", {
|
|
5718
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
5719
|
+
providerId: provider.id,
|
|
5720
|
+
modelId: model,
|
|
5721
|
+
maxContext: effective2,
|
|
5722
|
+
...eventPrevious > 0 ? { previousMaxContext: eventPrevious } : {},
|
|
5723
|
+
source,
|
|
5724
|
+
decreased: eventPrevious > 0 && effective2 < eventPrevious
|
|
5725
|
+
});
|
|
5707
5726
|
};
|
|
5708
|
-
const
|
|
5709
|
-
if (
|
|
5710
|
-
|
|
5711
|
-
|
|
5712
|
-
|
|
5727
|
+
const emitNativeAfterRouteChange = () => {
|
|
5728
|
+
if (routeChanged && currentEffective > 0) {
|
|
5729
|
+
a.ctx.meta["effectiveMaxContext"] = currentEffective;
|
|
5730
|
+
a.ctx.meta["effectiveMaxContextSource"] = "configured";
|
|
5731
|
+
emitEffectiveLimit(currentEffective, "configured");
|
|
5713
5732
|
}
|
|
5714
5733
|
};
|
|
5715
|
-
|
|
5716
|
-
|
|
5717
|
-
|
|
5718
|
-
|
|
5719
|
-
grant,
|
|
5720
|
-
deny
|
|
5721
|
-
});
|
|
5722
|
-
if (autoExtend) {
|
|
5723
|
-
setImmediate(() => {
|
|
5724
|
-
if (!resolved) {
|
|
5725
|
-
resolved = true;
|
|
5726
|
-
clearTimeout(timer);
|
|
5727
|
-
resolve10(100);
|
|
5728
|
-
}
|
|
5729
|
-
});
|
|
5734
|
+
const refresh = provider.refreshContextLimit;
|
|
5735
|
+
if (opts.probe === false || !refresh) {
|
|
5736
|
+
emitNativeAfterRouteChange();
|
|
5737
|
+
return;
|
|
5730
5738
|
}
|
|
5731
|
-
|
|
5732
|
-
|
|
5733
|
-
|
|
5734
|
-
|
|
5735
|
-
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
|
|
5739
|
-
|
|
5740
|
-
|
|
5741
|
-
|
|
5742
|
-
|
|
5743
|
-
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
5752
|
-
|
|
5753
|
-
|
|
5754
|
-
|
|
5755
|
-
|
|
5756
|
-
(
|
|
5757
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
}
|
|
5763
|
-
function hasNextStepsTag(text) {
|
|
5764
|
-
return NEXT_STEPS_TAG_RE.test(text);
|
|
5765
|
-
}
|
|
5766
|
-
function renderNextStepsBlock(steps) {
|
|
5767
|
-
const lines = steps.map((step, i) => {
|
|
5768
|
-
const auto = i === 0 && step.auto === true ? ' auto="true"' : "";
|
|
5769
|
-
return `${i + 1}. ${step.text}${auto}`;
|
|
5770
|
-
});
|
|
5771
|
-
return `<nextsteps>
|
|
5772
|
-
${lines.join("\n")}
|
|
5773
|
-
</nextsteps>`;
|
|
5774
|
-
}
|
|
5775
|
-
function endsTurn(stopReason) {
|
|
5776
|
-
return stopReason !== "tool_use" && stopReason !== "tool_call";
|
|
5777
|
-
}
|
|
5778
|
-
function maybeAppendPendingNextSteps(ctx, res) {
|
|
5779
|
-
if (ctx.agentId !== "leader") return res;
|
|
5780
|
-
if (!endsTurn(res.stopReason)) return res;
|
|
5781
|
-
if (ctx.signal.aborted) return res;
|
|
5782
|
-
const steps = readPendingNextSteps(ctx);
|
|
5783
|
-
if (!steps) return res;
|
|
5784
|
-
if (hasOpenTodos(ctx.todos)) {
|
|
5785
|
-
clearPendingNextSteps(ctx);
|
|
5786
|
-
return res;
|
|
5787
|
-
}
|
|
5788
|
-
const lastTextIndex = res.content.findLastIndex(isTextBlock);
|
|
5789
|
-
if (lastTextIndex >= 0) {
|
|
5790
|
-
const block = res.content[lastTextIndex];
|
|
5791
|
-
if (block && isTextBlock(block) && hasNextStepsTag(block.text)) {
|
|
5792
|
-
clearPendingNextSteps(ctx);
|
|
5793
|
-
return res;
|
|
5739
|
+
let discovered;
|
|
5740
|
+
try {
|
|
5741
|
+
discovered = await refresh.call(provider, model, { signal: a.ctx.signal });
|
|
5742
|
+
} catch {
|
|
5743
|
+
emitNativeAfterRouteChange();
|
|
5744
|
+
return;
|
|
5745
|
+
}
|
|
5746
|
+
const providerLimit = discovered?.maxContext;
|
|
5747
|
+
if (typeof providerLimit !== "number" || !Number.isFinite(providerLimit) || providerLimit <= 0) {
|
|
5748
|
+
emitNativeAfterRouteChange();
|
|
5749
|
+
return;
|
|
5750
|
+
}
|
|
5751
|
+
const providerLimitFloored = Math.floor(providerLimit);
|
|
5752
|
+
const existingBaseline = a.ctx.meta["contextLimitBaseline"];
|
|
5753
|
+
const lastApplied = a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
5754
|
+
const existingOverflow = a.ctx.meta["providerOverflowMaxContext"];
|
|
5755
|
+
const effectiveChangedOutsideProbe = typeof lastApplied === "number" && Number.isFinite(lastApplied) && currentEffective !== Math.floor(lastApplied) && currentEffective !== existingOverflow;
|
|
5756
|
+
const baseline = effectiveChangedOutsideProbe || typeof existingBaseline !== "number" || !Number.isFinite(existingBaseline) || existingBaseline <= 0 ? currentEffective : Math.floor(existingBaseline);
|
|
5757
|
+
a.ctx.meta["contextLimitBaseline"] = baseline;
|
|
5758
|
+
const overflowLimit = a.ctx.meta["providerOverflowMaxContext"];
|
|
5759
|
+
const overflowFloored = typeof overflowLimit === "number" && Number.isFinite(overflowLimit) && overflowLimit > 0 ? Math.floor(overflowLimit) : void 0;
|
|
5760
|
+
const ceilings = [baseline, providerLimitFloored];
|
|
5761
|
+
if (overflowFloored !== void 0) ceilings.push(overflowFloored);
|
|
5762
|
+
const effective = Math.min(...ceilings.filter((limit) => limit > 0));
|
|
5763
|
+
let effectiveSource;
|
|
5764
|
+
if (overflowFloored !== void 0 && effective === overflowFloored) {
|
|
5765
|
+
effectiveSource = "provider_overflow";
|
|
5766
|
+
} else if (effective === providerLimitFloored) {
|
|
5767
|
+
effectiveSource = "provider";
|
|
5768
|
+
} else {
|
|
5769
|
+
effectiveSource = "configured";
|
|
5794
5770
|
}
|
|
5771
|
+
a.ctx.meta["effectiveMaxContext"] = effective;
|
|
5772
|
+
a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
|
|
5773
|
+
a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
|
|
5774
|
+
emitEffectiveLimit(effective, effectiveSource);
|
|
5795
5775
|
}
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
5803
|
-
|
|
5776
|
+
function systemAndToolsOverhead() {
|
|
5777
|
+
const sysRef = a.ctx.systemPrompt;
|
|
5778
|
+
const toolsRef = a.ctx.tools;
|
|
5779
|
+
if (sysRef === _cachedSysRef && toolsRef === _cachedToolsRef && _cachedOverheadTokens > 0) {
|
|
5780
|
+
return _cachedOverheadTokens;
|
|
5781
|
+
}
|
|
5782
|
+
const breakdown = estimateRequestTokens([], sysRef, toolsRef ?? [], calibrationKey());
|
|
5783
|
+
_cachedSysRef = sysRef;
|
|
5784
|
+
_cachedToolsRef = toolsRef;
|
|
5785
|
+
_cachedOverheadTokens = breakdown.systemPrompt + breakdown.tools;
|
|
5786
|
+
return _cachedOverheadTokens;
|
|
5804
5787
|
}
|
|
5805
|
-
|
|
5806
|
-
|
|
5807
|
-
|
|
5808
|
-
|
|
5809
|
-
|
|
5810
|
-
|
|
5811
|
-
|
|
5812
|
-
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
5820
|
-
|
|
5821
|
-
|
|
5822
|
-
|
|
5823
|
-
|
|
5824
|
-
|
|
5825
|
-
|
|
5826
|
-
|
|
5827
|
-
const
|
|
5828
|
-
|
|
5829
|
-
|
|
5830
|
-
|
|
5831
|
-
|
|
5832
|
-
|
|
5833
|
-
|
|
5834
|
-
if (
|
|
5835
|
-
|
|
5836
|
-
continue;
|
|
5837
|
-
}
|
|
5838
|
-
if (ch === '"') {
|
|
5839
|
-
inString = false;
|
|
5840
|
-
prevSig = '"';
|
|
5841
|
-
stringBraceDepth = 0;
|
|
5842
|
-
continue;
|
|
5788
|
+
function stashRequestTokens(req) {
|
|
5789
|
+
const preFlight = estimateRequestTokens(
|
|
5790
|
+
req.messages,
|
|
5791
|
+
req.system,
|
|
5792
|
+
req.tools ?? [],
|
|
5793
|
+
calibrationKey(req.model)
|
|
5794
|
+
);
|
|
5795
|
+
a.ctx.lastRequestTokens = preFlight.total;
|
|
5796
|
+
_lastPreFlightMsgCount = req.messages.length;
|
|
5797
|
+
_lastPreFlightToolCount = (req.tools ?? []).length;
|
|
5798
|
+
_lastPreFlightRevision = a.ctx.state.revision;
|
|
5799
|
+
_cachedSysRef = req.system;
|
|
5800
|
+
_cachedToolsRef = req.tools ?? [];
|
|
5801
|
+
_cachedOverheadTokens = preFlight.systemPrompt + preFlight.tools;
|
|
5802
|
+
a.ctx.meta["lastRequestTokensAt"] = {
|
|
5803
|
+
msgCount: req.messages.length,
|
|
5804
|
+
toolCount: (req.tools ?? []).length,
|
|
5805
|
+
revision: a.ctx.state.revision
|
|
5806
|
+
};
|
|
5807
|
+
return preFlight;
|
|
5808
|
+
}
|
|
5809
|
+
function refreshContextRequestTokenStash(opts = {}) {
|
|
5810
|
+
const msgCount = a.ctx.messages.length;
|
|
5811
|
+
const toolCount = (a.ctx.tools ?? []).length;
|
|
5812
|
+
const revision = a.ctx.state.revision;
|
|
5813
|
+
const stashed = a.ctx.lastRequestTokens;
|
|
5814
|
+
const stashedAt = a.ctx.meta?.["lastRequestTokensAt"];
|
|
5815
|
+
if (!opts.force && typeof stashed === "number" && stashed > 0 && typeof stashedAt === "object" && stashedAt !== null) {
|
|
5816
|
+
const meta = stashedAt;
|
|
5817
|
+
if (meta.msgCount === msgCount && meta.toolCount === toolCount && meta.revision === revision && _lastPreFlightRevision === revision) {
|
|
5818
|
+
return stashed;
|
|
5843
5819
|
}
|
|
5844
|
-
if (ch === "{") stringBraceDepth++;
|
|
5845
|
-
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
5846
|
-
continue;
|
|
5847
|
-
}
|
|
5848
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
5849
|
-
contentEnd = i + 1;
|
|
5850
|
-
if (ch === '"') {
|
|
5851
|
-
inString = true;
|
|
5852
|
-
sawKey = true;
|
|
5853
|
-
stringBraceDepth = 0;
|
|
5854
|
-
prevSig = '"';
|
|
5855
|
-
} else if (ch === "{" || ch === "[") {
|
|
5856
|
-
stack.push(ch);
|
|
5857
|
-
prevSig = ch;
|
|
5858
|
-
} else if (ch === "}" || ch === "]") {
|
|
5859
|
-
stack.pop();
|
|
5860
|
-
prevSig = ch;
|
|
5861
|
-
} else {
|
|
5862
|
-
prevSig = ch;
|
|
5863
5820
|
}
|
|
5821
|
+
const refreshed = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
|
|
5822
|
+
a.ctx.lastRequestTokens = refreshed;
|
|
5823
|
+
_lastPreFlightMsgCount = msgCount;
|
|
5824
|
+
_lastPreFlightToolCount = toolCount;
|
|
5825
|
+
_lastPreFlightRevision = revision;
|
|
5826
|
+
a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount, revision };
|
|
5827
|
+
return refreshed;
|
|
5864
5828
|
}
|
|
5865
|
-
|
|
5866
|
-
|
|
5867
|
-
|
|
5868
|
-
|
|
5869
|
-
|
|
5870
|
-
|
|
5871
|
-
|
|
5829
|
+
async function compactContextIfNeeded() {
|
|
5830
|
+
const msgCount = a.ctx.messages.length;
|
|
5831
|
+
const maxContext = currentMaxContext();
|
|
5832
|
+
const revision = a.ctx.state.revision;
|
|
5833
|
+
const toolsRef = a.ctx.tools;
|
|
5834
|
+
const systemRef = a.ctx.systemPrompt;
|
|
5835
|
+
const requestTokens = a.ctx.lastRequestTokens;
|
|
5836
|
+
if (_lastCompactionMsgCount === msgCount && _lastCompactionRevision === revision && _lastCompactionToolsRef === toolsRef && _lastCompactionSystemRef === systemRef && _lastCompactionRequestTokens === requestTokens && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
|
|
5837
|
+
return false;
|
|
5872
5838
|
}
|
|
5873
|
-
|
|
5874
|
-
|
|
5875
|
-
|
|
5876
|
-
|
|
5839
|
+
const beforeMessages = a.ctx.messages;
|
|
5840
|
+
const beforeMsgCount = a.ctx.messages.length;
|
|
5841
|
+
const beforeRevision = a.ctx.state.revision;
|
|
5842
|
+
await a.pipelines.contextWindow.run(a.ctx);
|
|
5843
|
+
_lastCompactionMsgCount = a.ctx.messages.length;
|
|
5844
|
+
_lastCompactionRevision = a.ctx.state.revision;
|
|
5845
|
+
_lastCompactionToolsRef = a.ctx.tools;
|
|
5846
|
+
_lastCompactionSystemRef = a.ctx.systemPrompt;
|
|
5847
|
+
_lastCompactionMaxContext = maxContext;
|
|
5848
|
+
const changed = a.ctx.state.revision !== beforeRevision || a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
|
|
5849
|
+
const tokens = refreshContextRequestTokenStash({ force: changed });
|
|
5850
|
+
_lastCompactionRequestTokens = tokens;
|
|
5851
|
+
const load = maxContext > 0 ? tokens / maxContext : 0;
|
|
5852
|
+
_lastCompactionWasNoop = tokens > 0 && load < 0.5;
|
|
5853
|
+
if (changed) {
|
|
5854
|
+
_lastEmittedMsgCount = -1;
|
|
5855
|
+
_lastEmittedToolCount = -1;
|
|
5856
|
+
_lastEmittedMaxContext = -1;
|
|
5857
|
+
}
|
|
5858
|
+
return changed;
|
|
5877
5859
|
}
|
|
5878
|
-
|
|
5879
|
-
|
|
5860
|
+
async function buildRequestWithPreflightCompaction(opts) {
|
|
5861
|
+
let prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
5862
|
+
let req = prepared.request;
|
|
5863
|
+
let preFlight = stashRequestTokens(req);
|
|
5864
|
+
let probedRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
5865
|
+
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
5866
|
+
if (await compactContextIfNeeded()) {
|
|
5867
|
+
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
5868
|
+
req = prepared.request;
|
|
5869
|
+
preFlight = stashRequestTokens(req);
|
|
5870
|
+
const rebuiltRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
5871
|
+
if (rebuiltRouteKey !== probedRouteKey) {
|
|
5872
|
+
probedRouteKey = rebuiltRouteKey;
|
|
5873
|
+
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
5874
|
+
if (await compactContextIfNeeded()) {
|
|
5875
|
+
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
5876
|
+
req = prepared.request;
|
|
5877
|
+
preFlight = stashRequestTokens(req);
|
|
5878
|
+
}
|
|
5879
|
+
}
|
|
5880
|
+
}
|
|
5881
|
+
return { req, provider: prepared.provider, preFlight };
|
|
5880
5882
|
}
|
|
5881
|
-
|
|
5882
|
-
const
|
|
5883
|
-
|
|
5883
|
+
function emitContextPct() {
|
|
5884
|
+
const msgCount = a.ctx.messages.length;
|
|
5885
|
+
const toolCount = (a.ctx.tools ?? []).length;
|
|
5886
|
+
const maxContext = currentMaxContext();
|
|
5887
|
+
const revision = a.ctx.state.revision;
|
|
5888
|
+
if (msgCount === _lastEmittedMsgCount && toolCount === _lastEmittedToolCount && revision === _lastEmittedRevision && maxContext === _lastEmittedMaxContext && maxContext > 0) {
|
|
5889
|
+
return;
|
|
5890
|
+
}
|
|
5891
|
+
_lastEmittedMsgCount = msgCount;
|
|
5892
|
+
_lastEmittedToolCount = toolCount;
|
|
5893
|
+
_lastEmittedRevision = revision;
|
|
5894
|
+
_lastEmittedMaxContext = maxContext;
|
|
5895
|
+
if (msgCount !== _lastPreFlightMsgCount || toolCount !== _lastPreFlightToolCount || revision !== _lastPreFlightRevision) {
|
|
5896
|
+
refreshContextRequestTokenStash({ force: true });
|
|
5897
|
+
}
|
|
5898
|
+
let total;
|
|
5899
|
+
const anchored = realAnchoredInputTokens(
|
|
5900
|
+
a.ctx.messages,
|
|
5901
|
+
a.ctx.lastRealInputTokens,
|
|
5902
|
+
typeof a.ctx.meta?.["realAnchorMsgCount"] === "number" ? a.ctx.meta["realAnchorMsgCount"] : void 0
|
|
5903
|
+
);
|
|
5904
|
+
const stashed = a.ctx.lastRequestTokens;
|
|
5905
|
+
if (anchored !== null) {
|
|
5906
|
+
total = anchored;
|
|
5907
|
+
} else if (typeof stashed === "number" && stashed > 0) {
|
|
5908
|
+
const cal = getCalibrationState(calibrationKey());
|
|
5909
|
+
total = cal.calibrated ? Math.round(stashed * Math.min(1.5, Math.max(0.5, cal.ratio))) : stashed;
|
|
5910
|
+
} else {
|
|
5911
|
+
const raw = refreshContextRequestTokenStash({ force: true });
|
|
5912
|
+
const cal = getCalibrationState(calibrationKey());
|
|
5913
|
+
total = cal.calibrated ? Math.round(raw * Math.min(1.5, Math.max(0.5, cal.ratio))) : raw;
|
|
5914
|
+
}
|
|
5915
|
+
const rawLoad = maxContext > 0 ? total / maxContext : 0;
|
|
5916
|
+
const load = Math.max(0, Math.min(1, rawLoad));
|
|
5917
|
+
a.events.emit("ctx.pct", {
|
|
5918
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
5919
|
+
load,
|
|
5920
|
+
rawLoad,
|
|
5921
|
+
tokens: total,
|
|
5922
|
+
maxContext
|
|
5923
|
+
});
|
|
5884
5924
|
}
|
|
5885
|
-
return
|
|
5925
|
+
return {
|
|
5926
|
+
refreshProviderContextLimit,
|
|
5927
|
+
compactContextIfNeeded,
|
|
5928
|
+
stashRequestTokens,
|
|
5929
|
+
refreshContextRequestTokenStash,
|
|
5930
|
+
buildRequestWithPreflightCompaction,
|
|
5931
|
+
emitContextPct,
|
|
5932
|
+
currentMaxContext,
|
|
5933
|
+
calibrationKey,
|
|
5934
|
+
get lastPreFlightMsgCount() {
|
|
5935
|
+
return _lastPreFlightMsgCount;
|
|
5936
|
+
}
|
|
5937
|
+
};
|
|
5886
5938
|
}
|
|
5887
|
-
|
|
5888
|
-
|
|
5889
|
-
|
|
5890
|
-
|
|
5891
|
-
|
|
5892
|
-
|
|
5893
|
-
|
|
5894
|
-
|
|
5939
|
+
|
|
5940
|
+
// src/core/agent-loop-detector.ts
|
|
5941
|
+
function iterationFingerprint(blocks) {
|
|
5942
|
+
const toolUses = blocks.filter(isToolUseBlock);
|
|
5943
|
+
const texts = blocks.filter(isTextBlock);
|
|
5944
|
+
const toolNameSet = Array.from(new Set(toolUses.map((u) => u.name))).sort();
|
|
5945
|
+
const firstInputHash = toolUses[0] ? hashSmall(stableStringify(toolUses[0].input ?? {})) : "";
|
|
5946
|
+
const textBlob = texts.map((t2) => t2.text).join("").slice(0, 512);
|
|
5947
|
+
const hasContent = toolNameSet.length > 0 || textBlob.length > 0;
|
|
5948
|
+
if (!hasContent) return "__empty__";
|
|
5949
|
+
return [
|
|
5950
|
+
`tools=${toolNameSet.join("+") || "-"}`,
|
|
5951
|
+
`in0=${firstInputHash}`,
|
|
5952
|
+
`txt=${textBlob}`
|
|
5953
|
+
].join("\n");
|
|
5895
5954
|
}
|
|
5896
|
-
function
|
|
5897
|
-
|
|
5898
|
-
|
|
5899
|
-
|
|
5900
|
-
|
|
5955
|
+
function stableStringify(value) {
|
|
5956
|
+
return JSON.stringify(canonicalize(value));
|
|
5957
|
+
}
|
|
5958
|
+
function canonicalize(value) {
|
|
5959
|
+
if (Array.isArray(value)) return value.map(canonicalize);
|
|
5960
|
+
if (value && typeof value === "object") {
|
|
5961
|
+
const src = value;
|
|
5962
|
+
const out = {};
|
|
5963
|
+
for (const k of Object.keys(src).sort()) out[k] = canonicalize(src[k]);
|
|
5964
|
+
return out;
|
|
5901
5965
|
}
|
|
5966
|
+
return value;
|
|
5902
5967
|
}
|
|
5903
|
-
|
|
5904
|
-
|
|
5905
|
-
|
|
5906
|
-
|
|
5907
|
-
|
|
5908
|
-
|
|
5909
|
-
|
|
5910
|
-
|
|
5911
|
-
|
|
5912
|
-
|
|
5913
|
-
|
|
5914
|
-
|
|
5915
|
-
|
|
5916
|
-
|
|
5917
|
-
|
|
5918
|
-
|
|
5919
|
-
|
|
5968
|
+
function hashSmall(s) {
|
|
5969
|
+
let h = 2166136261;
|
|
5970
|
+
for (let i = 0; i < s.length; i++) {
|
|
5971
|
+
h ^= s.charCodeAt(i);
|
|
5972
|
+
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
5973
|
+
}
|
|
5974
|
+
return h.toString(36);
|
|
5975
|
+
}
|
|
5976
|
+
var AgentLoopDetector = class {
|
|
5977
|
+
constructor(a) {
|
|
5978
|
+
this.a = a;
|
|
5979
|
+
}
|
|
5980
|
+
a;
|
|
5981
|
+
lastToolSignature = "";
|
|
5982
|
+
toolLoopCount = 0;
|
|
5983
|
+
iterationSteerDone = false;
|
|
5984
|
+
recentCallKeys = [];
|
|
5985
|
+
steeredCallKeys = /* @__PURE__ */ new Set();
|
|
5986
|
+
checkIteration(iterationIndex, content, toolUses, queueSteer) {
|
|
5987
|
+
const loopCfg = this.a.loopDetection;
|
|
5988
|
+
if (loopCfg.mode === "off") return { cut: false };
|
|
5989
|
+
const sig = iterationFingerprint(content);
|
|
5990
|
+
if (sig !== "__empty__") {
|
|
5991
|
+
if (sig === this.lastToolSignature) {
|
|
5992
|
+
this.toolLoopCount++;
|
|
5993
|
+
} else {
|
|
5994
|
+
this.lastToolSignature = sig;
|
|
5995
|
+
this.toolLoopCount = 1;
|
|
5996
|
+
this.iterationSteerDone = false;
|
|
5997
|
+
}
|
|
5998
|
+
const names = toolUses.map((t2) => t2.name).join(", ");
|
|
5999
|
+
const hasText2 = content.some(isTextBlock);
|
|
6000
|
+
const kind = toolUses.length > 0 && hasText2 ? "mixed" : toolUses.length > 0 ? "tool" : "message";
|
|
6001
|
+
const observationRepeat = loopCfg.mode === "steer-then-cut" && toolUses.length > 0 && toolUses.every((use) => {
|
|
6002
|
+
const tool = this.a.tools.get(use.name);
|
|
6003
|
+
return tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0);
|
|
6004
|
+
});
|
|
6005
|
+
const repeatMultiplier = observationRepeat ? 2 : 1;
|
|
6006
|
+
const detail = kind === "tool" ? `"${names}" called with effectively identical inputs ${this.toolLoopCount} times in a row` : kind === "mixed" ? `"${names}" + same text repeated ${this.toolLoopCount} times in a row` : `same assistant text repeated ${this.toolLoopCount} times in a row`;
|
|
6007
|
+
const cutAt = (loopCfg.mode === "cut" ? loopCfg.steerThreshold : loopCfg.cutThreshold) * repeatMultiplier;
|
|
6008
|
+
if (this.toolLoopCount >= cutAt) {
|
|
6009
|
+
this.a.logger.warn(`Loop detected: ${detail} \u2014 stopping to prevent infinite loop.`);
|
|
6010
|
+
this.a.events.emit("tool.loop_detected", {
|
|
6011
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
6012
|
+
ctx: this.a.ctx,
|
|
6013
|
+
tools: names,
|
|
6014
|
+
repeatCount: this.toolLoopCount,
|
|
6015
|
+
iteration: iterationIndex,
|
|
6016
|
+
kind,
|
|
6017
|
+
action: "cut",
|
|
6018
|
+
scope: "iteration"
|
|
6019
|
+
});
|
|
6020
|
+
const summary = kind === "message" ? `[Loop detected: same assistant message repeated ${this.toolLoopCount}\xD7 \u2014 stopping to prevent infinite repetition.]` : `[Loop detected: ${detail} \u2014 stopping to prevent infinite repetition.]`;
|
|
6021
|
+
return { cut: true, cutSummary: summary };
|
|
6022
|
+
}
|
|
6023
|
+
if (loopCfg.mode === "steer-then-cut" && this.toolLoopCount >= loopCfg.steerThreshold * repeatMultiplier && !this.iterationSteerDone) {
|
|
6024
|
+
this.iterationSteerDone = true;
|
|
6025
|
+
this.a.logger.warn(`Loop detected: ${detail} \u2014 steering the model to change approach.`);
|
|
6026
|
+
this.a.events.emit("tool.loop_detected", {
|
|
6027
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
6028
|
+
ctx: this.a.ctx,
|
|
6029
|
+
tools: names,
|
|
6030
|
+
repeatCount: this.toolLoopCount,
|
|
6031
|
+
iteration: iterationIndex,
|
|
6032
|
+
kind,
|
|
6033
|
+
action: "steer",
|
|
6034
|
+
scope: "iteration"
|
|
6035
|
+
});
|
|
6036
|
+
queueSteer(
|
|
6037
|
+
`[loop-detector] Your last ${this.toolLoopCount} responses were effectively identical (${detail}). This approach is not working. Change strategy: use a different tool, different arguments, or a different plan \u2014 or explain what is blocking you and stop. Repeating the same response ${cutAt - this.toolLoopCount} more time(s) will terminate the turn.`
|
|
6038
|
+
);
|
|
5920
6039
|
}
|
|
5921
|
-
content.push(block);
|
|
5922
6040
|
} else {
|
|
5923
|
-
|
|
5924
|
-
|
|
5925
|
-
|
|
5926
|
-
|
|
5927
|
-
|
|
5928
|
-
|
|
5929
|
-
|
|
5930
|
-
|
|
5931
|
-
if (
|
|
5932
|
-
|
|
5933
|
-
|
|
5934
|
-
|
|
5935
|
-
|
|
6041
|
+
this.lastToolSignature = "";
|
|
6042
|
+
this.toolLoopCount = 0;
|
|
6043
|
+
this.iterationSteerDone = false;
|
|
6044
|
+
}
|
|
6045
|
+
if (loopCfg.mode === "steer-then-cut") {
|
|
6046
|
+
for (const u of toolUses) {
|
|
6047
|
+
const key = `${u.name}:${hashSmall(stableStringify(u.input ?? {}))}`;
|
|
6048
|
+
this.recentCallKeys.push(key);
|
|
6049
|
+
if (this.recentCallKeys.length > loopCfg.windowSize) this.recentCallKeys.shift();
|
|
6050
|
+
if (this.steeredCallKeys.has(key)) continue;
|
|
6051
|
+
let count = 0;
|
|
6052
|
+
for (const k of this.recentCallKeys) if (k === key) count++;
|
|
6053
|
+
const tool = this.a.tools.get(u.name);
|
|
6054
|
+
const observationThreshold = tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0) ? loopCfg.callRepeatThreshold * 2 : loopCfg.callRepeatThreshold;
|
|
6055
|
+
if (count < observationThreshold) continue;
|
|
6056
|
+
this.steeredCallKeys.add(key);
|
|
6057
|
+
const preview = JSON.stringify(u.input ?? {}).slice(0, 160);
|
|
6058
|
+
this.a.logger.warn(
|
|
6059
|
+
`Loop detected: "${u.name}" called with identical arguments ${count}\xD7 within the last ${loopCfg.windowSize} tool calls \u2014 steering the model to change approach.`
|
|
6060
|
+
);
|
|
6061
|
+
this.a.events.emit("tool.loop_detected", {
|
|
6062
|
+
sessionId: resolveEventSessionId(this.a.ctx),
|
|
6063
|
+
ctx: this.a.ctx,
|
|
6064
|
+
tools: u.name,
|
|
6065
|
+
repeatCount: count,
|
|
6066
|
+
iteration: iterationIndex,
|
|
6067
|
+
kind: "tool",
|
|
6068
|
+
action: "steer",
|
|
6069
|
+
scope: "call"
|
|
6070
|
+
});
|
|
6071
|
+
queueSteer(
|
|
6072
|
+
`[loop-detector] You have called ${u.name}(${preview}) ${count} times with identical arguments within the last ${loopCfg.windowSize} tool calls. The result will not change. Do not repeat this call \u2014 use what you already know, try a different approach, or explain the blocker.`
|
|
6073
|
+
);
|
|
6074
|
+
}
|
|
5936
6075
|
}
|
|
6076
|
+
return { cut: false };
|
|
5937
6077
|
}
|
|
5938
|
-
|
|
5939
|
-
|
|
5940
|
-
|
|
5941
|
-
function
|
|
5942
|
-
|
|
5943
|
-
|
|
5944
|
-
|
|
5945
|
-
|
|
5946
|
-
|
|
5947
|
-
|
|
5948
|
-
|
|
5949
|
-
|
|
5950
|
-
|
|
5951
|
-
|
|
5952
|
-
|
|
6078
|
+
};
|
|
6079
|
+
|
|
6080
|
+
// src/core/continue-to-next-iteration.ts
|
|
6081
|
+
function parseContinueDirective(text) {
|
|
6082
|
+
const LINE_MARKERS = /^\s*\[(continue|next step|proceed|done)\]\s*$/gim;
|
|
6083
|
+
const tail = text.length <= DIRECTIVE_SCAN_WINDOW ? text : text.slice(text.length - DIRECTIVE_SCAN_WINDOW);
|
|
6084
|
+
let match;
|
|
6085
|
+
let lastDirective = "none";
|
|
6086
|
+
while ((match = LINE_MARKERS.exec(tail)) !== null) {
|
|
6087
|
+
const value = (match[1] ?? "").toLowerCase();
|
|
6088
|
+
if (value === "continue" || value === "next step" || value === "proceed") {
|
|
6089
|
+
lastDirective = "continue";
|
|
6090
|
+
} else if (value === "done") {
|
|
6091
|
+
lastDirective = "stop";
|
|
6092
|
+
}
|
|
6093
|
+
}
|
|
6094
|
+
return lastDirective;
|
|
5953
6095
|
}
|
|
5954
|
-
|
|
5955
|
-
|
|
6096
|
+
var DIRECTIVE_SCAN_WINDOW = 2048;
|
|
6097
|
+
var META_KEY2 = "_autonomousContinue";
|
|
6098
|
+
function consumeAutonomousContinue(ctx) {
|
|
6099
|
+
const val = ctx.meta[META_KEY2] === true;
|
|
6100
|
+
delete ctx.meta[META_KEY2];
|
|
6101
|
+
return val;
|
|
5956
6102
|
}
|
|
5957
|
-
|
|
5958
|
-
|
|
5959
|
-
|
|
5960
|
-
|
|
5961
|
-
|
|
5962
|
-
|
|
5963
|
-
|
|
5964
|
-
|
|
5965
|
-
|
|
5966
|
-
|
|
5967
|
-
|
|
5968
|
-
|
|
5969
|
-
|
|
5970
|
-
|
|
5971
|
-
|
|
5972
|
-
|
|
6103
|
+
|
|
6104
|
+
// src/core/iteration-limit.ts
|
|
6105
|
+
function requestLimitExtension(opts) {
|
|
6106
|
+
const { events, sessionId, currentIterations, currentLimit, autoExtend, timeoutMs = 3e4 } = opts;
|
|
6107
|
+
return new Promise((resolve10) => {
|
|
6108
|
+
let resolved = false;
|
|
6109
|
+
const timerFired = () => {
|
|
6110
|
+
if (!resolved) {
|
|
6111
|
+
resolved = true;
|
|
6112
|
+
resolve10(0);
|
|
6113
|
+
}
|
|
6114
|
+
};
|
|
6115
|
+
const timer = setTimeout(timerFired, timeoutMs);
|
|
6116
|
+
timer.unref();
|
|
6117
|
+
const deny = () => {
|
|
6118
|
+
if (!resolved) {
|
|
6119
|
+
resolved = true;
|
|
6120
|
+
clearTimeout(timer);
|
|
6121
|
+
resolve10(0);
|
|
6122
|
+
}
|
|
6123
|
+
};
|
|
6124
|
+
const grant = (extra) => {
|
|
6125
|
+
if (!resolved) {
|
|
6126
|
+
resolved = true;
|
|
6127
|
+
clearTimeout(timer);
|
|
6128
|
+
resolve10(Math.max(0, extra));
|
|
6129
|
+
}
|
|
6130
|
+
};
|
|
6131
|
+
events.emit("iteration.limit_reached", {
|
|
6132
|
+
sessionId,
|
|
6133
|
+
currentIterations,
|
|
6134
|
+
currentLimit,
|
|
6135
|
+
grant,
|
|
6136
|
+
deny
|
|
5973
6137
|
});
|
|
5974
|
-
|
|
5975
|
-
|
|
6138
|
+
if (autoExtend) {
|
|
6139
|
+
setImmediate(() => {
|
|
6140
|
+
if (!resolved) {
|
|
6141
|
+
resolved = true;
|
|
6142
|
+
clearTimeout(timer);
|
|
6143
|
+
resolve10(100);
|
|
6144
|
+
}
|
|
6145
|
+
});
|
|
6146
|
+
}
|
|
6147
|
+
});
|
|
6148
|
+
}
|
|
6149
|
+
|
|
6150
|
+
// src/core/next-steps-slot.ts
|
|
6151
|
+
var SLOT_KEY = "nextsteps.pending";
|
|
6152
|
+
var MAX_PENDING_NEXT_STEPS = 4;
|
|
6153
|
+
var NEXT_STEPS_TAG_RE = /<nextsteps\b[^>]*>/i;
|
|
6154
|
+
function writePendingNextSteps(ctx, steps) {
|
|
6155
|
+
const cleaned = [];
|
|
6156
|
+
for (const step of steps) {
|
|
6157
|
+
const text = typeof step?.text === "string" ? step.text.replace(/\s+/g, " ").trim() : "";
|
|
6158
|
+
if (!text) continue;
|
|
6159
|
+
cleaned.push(step.auto === true && cleaned.length === 0 ? { text, auto: true } : { text });
|
|
6160
|
+
if (cleaned.length >= MAX_PENDING_NEXT_STEPS) break;
|
|
6161
|
+
}
|
|
6162
|
+
if (cleaned.length === 0) {
|
|
6163
|
+
clearPendingNextSteps(ctx);
|
|
6164
|
+
return;
|
|
5976
6165
|
}
|
|
6166
|
+
ctx.meta[SLOT_KEY] = cleaned;
|
|
5977
6167
|
}
|
|
5978
|
-
function
|
|
5979
|
-
|
|
5980
|
-
void
|
|
6168
|
+
function readPendingNextSteps(ctx) {
|
|
6169
|
+
const raw = ctx.meta[SLOT_KEY];
|
|
6170
|
+
if (!Array.isArray(raw) || raw.length === 0) return void 0;
|
|
6171
|
+
const steps = raw.filter(
|
|
6172
|
+
(s) => typeof s?.text === "string" && s.text.length > 0
|
|
6173
|
+
);
|
|
6174
|
+
return steps.length > 0 ? steps : void 0;
|
|
5981
6175
|
}
|
|
5982
|
-
function
|
|
5983
|
-
|
|
5984
|
-
state.currentTextIndex = state.textBuffers.length;
|
|
5985
|
-
state.textBuffers.push("");
|
|
5986
|
-
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
5987
|
-
}
|
|
5988
|
-
state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
|
|
6176
|
+
function clearPendingNextSteps(ctx) {
|
|
6177
|
+
delete ctx.meta[SLOT_KEY];
|
|
5989
6178
|
}
|
|
5990
|
-
function
|
|
5991
|
-
|
|
5992
|
-
state.tools.set(ev.id, { name: ev.name, partial: "" });
|
|
5993
|
-
state.blockOrder.push({ kind: "tool", id: ev.id });
|
|
6179
|
+
function hasNextStepsTag(text) {
|
|
6180
|
+
return NEXT_STEPS_TAG_RE.test(text);
|
|
5994
6181
|
}
|
|
5995
|
-
function
|
|
5996
|
-
const
|
|
5997
|
-
|
|
6182
|
+
function renderNextStepsBlock(steps) {
|
|
6183
|
+
const lines = steps.map((step, i) => {
|
|
6184
|
+
const auto = i === 0 && step.auto === true ? ' auto="true"' : "";
|
|
6185
|
+
return `${i + 1}. ${step.text}${auto}`;
|
|
6186
|
+
});
|
|
6187
|
+
return `<nextsteps>
|
|
6188
|
+
${lines.join("\n")}
|
|
6189
|
+
</nextsteps>`;
|
|
5998
6190
|
}
|
|
5999
|
-
function
|
|
6000
|
-
|
|
6191
|
+
function endsTurn(stopReason) {
|
|
6192
|
+
return stopReason !== "tool_use" && stopReason !== "tool_call";
|
|
6193
|
+
}
|
|
6194
|
+
function maybeAppendPendingNextSteps(ctx, res) {
|
|
6195
|
+
if (ctx.agentId !== "leader") return res;
|
|
6196
|
+
if (!endsTurn(res.stopReason)) return res;
|
|
6197
|
+
if (ctx.signal.aborted) return res;
|
|
6198
|
+
const steps = readPendingNextSteps(ctx);
|
|
6199
|
+
if (!steps) return res;
|
|
6200
|
+
if (hasOpenTodos(ctx.todos)) {
|
|
6201
|
+
clearPendingNextSteps(ctx);
|
|
6202
|
+
return res;
|
|
6203
|
+
}
|
|
6204
|
+
const lastTextIndex = res.content.findLastIndex(isTextBlock);
|
|
6205
|
+
if (lastTextIndex >= 0) {
|
|
6206
|
+
const block = res.content[lastTextIndex];
|
|
6207
|
+
if (block && isTextBlock(block) && hasNextStepsTag(block.text)) {
|
|
6208
|
+
clearPendingNextSteps(ctx);
|
|
6209
|
+
return res;
|
|
6210
|
+
}
|
|
6211
|
+
}
|
|
6212
|
+
const rendered = renderNextStepsBlock(steps);
|
|
6213
|
+
const content = [...res.content];
|
|
6214
|
+
const target = lastTextIndex >= 0 ? content[lastTextIndex] : void 0;
|
|
6215
|
+
if (target && isTextBlock(target)) {
|
|
6216
|
+
const separator = target.text.trim() ? "\n\n" : "";
|
|
6217
|
+
content[lastTextIndex] = { ...target, text: `${target.text}${separator}${rendered}` };
|
|
6218
|
+
} else {
|
|
6219
|
+
content.push({ type: "text", text: rendered });
|
|
6220
|
+
}
|
|
6221
|
+
clearPendingNextSteps(ctx);
|
|
6222
|
+
return { ...res, content };
|
|
6223
|
+
}
|
|
6224
|
+
|
|
6225
|
+
// src/prompts/prompt-journal.ts
|
|
6226
|
+
import * as fs5 from "node:fs/promises";
|
|
6227
|
+
import * as path11 from "node:path";
|
|
6228
|
+
async function ensureGitignore(projectRoot) {
|
|
6229
|
+
const gitignorePath = path11.join(projectRoot, ".gitignore");
|
|
6001
6230
|
try {
|
|
6002
|
-
|
|
6003
|
-
} catch {
|
|
6004
|
-
const repaired = completePartialObject(s);
|
|
6231
|
+
let content = "";
|
|
6005
6232
|
try {
|
|
6006
|
-
|
|
6233
|
+
content = await fs5.readFile(gitignorePath, "utf8");
|
|
6007
6234
|
} catch {
|
|
6008
|
-
|
|
6235
|
+
content = "";
|
|
6236
|
+
}
|
|
6237
|
+
if (!content.includes(".wrongstack") && !content.includes(".wrongstack/")) {
|
|
6238
|
+
const addition = content.endsWith("\n") || content.length === 0 ? ".wrongstack/\n" : "\n.wrongstack/\n";
|
|
6239
|
+
await fs5.writeFile(gitignorePath, content + addition, "utf8");
|
|
6009
6240
|
}
|
|
6241
|
+
} catch {
|
|
6010
6242
|
}
|
|
6011
6243
|
}
|
|
6012
|
-
function
|
|
6013
|
-
const
|
|
6014
|
-
|
|
6015
|
-
|
|
6016
|
-
|
|
6244
|
+
function sessionFileId(sessionId) {
|
|
6245
|
+
const leaf = sessionId.split(/[\\/]/u).pop();
|
|
6246
|
+
return leaf && leaf.trim().length > 0 ? leaf : "general";
|
|
6247
|
+
}
|
|
6248
|
+
async function recordPromptJournalEntry(opts) {
|
|
6249
|
+
const now = /* @__PURE__ */ new Date();
|
|
6250
|
+
const timestamp = now.toISOString();
|
|
6251
|
+
const dateStr = timestamp.slice(0, 10);
|
|
6252
|
+
const monthStr = dateStr.slice(0, 7);
|
|
6253
|
+
const sessionId = opts.sessionId && opts.sessionId.trim() ? opts.sessionId.trim() : "general";
|
|
6254
|
+
const id = `pmt_${Date.now()}_${Math.random().toString(36).slice(2, 9)}`;
|
|
6255
|
+
const content = opts.content ?? "";
|
|
6256
|
+
const lines = content.split("\n");
|
|
6257
|
+
const characterCount = content.length;
|
|
6258
|
+
const lineCount = lines.length;
|
|
6259
|
+
const tokenEstimate = Math.ceil(characterCount / 4);
|
|
6260
|
+
const entry = {
|
|
6261
|
+
id,
|
|
6262
|
+
timestamp,
|
|
6263
|
+
sessionId,
|
|
6264
|
+
projectRoot: opts.projectRoot,
|
|
6265
|
+
role: opts.role ?? (opts.category === "system_prompt" ? "system" : "user"),
|
|
6266
|
+
category: opts.category,
|
|
6267
|
+
content,
|
|
6268
|
+
rawContent: opts.rawContent,
|
|
6269
|
+
metadata: {
|
|
6270
|
+
model: opts.model,
|
|
6271
|
+
provider: opts.provider,
|
|
6272
|
+
iterationIndex: opts.iterationIndex,
|
|
6273
|
+
tokenEstimate,
|
|
6274
|
+
characterCount,
|
|
6275
|
+
lineCount,
|
|
6276
|
+
activeTools: opts.activeTools,
|
|
6277
|
+
contextFiles: opts.contextFiles,
|
|
6278
|
+
durationMs: opts.durationMs,
|
|
6279
|
+
decisionReason: opts.decisionReason,
|
|
6280
|
+
tags: opts.tags
|
|
6281
|
+
}
|
|
6282
|
+
};
|
|
6283
|
+
const basePromptsDir = path11.join(opts.projectRoot, ".wrongstack", "prompts");
|
|
6284
|
+
const dayDir = path11.join(basePromptsDir, monthStr, dateStr);
|
|
6285
|
+
try {
|
|
6286
|
+
await fs5.mkdir(dayDir, { recursive: true });
|
|
6287
|
+
await ensureGitignore(opts.projectRoot);
|
|
6288
|
+
const sessionJsonlFile = path11.join(dayDir, `session-${sessionFileId(sessionId)}.jsonl`);
|
|
6289
|
+
await fs5.appendFile(sessionJsonlFile, JSON.stringify(entry) + "\n", "utf8");
|
|
6290
|
+
const sessionMdFile = path11.join(dayDir, `session-${sessionFileId(sessionId)}.md`);
|
|
6291
|
+
const mdSection = formatEntryMarkdown(entry);
|
|
6292
|
+
await fs5.appendFile(sessionMdFile, mdSection, "utf8");
|
|
6293
|
+
const dailySummaryFile = path11.join(dayDir, "daily-summary.md");
|
|
6294
|
+
await updateDailySummary(dailySummaryFile, dateStr, entry);
|
|
6295
|
+
await updateRootCatalog(basePromptsDir, monthStr, dateStr, sessionId, entry);
|
|
6296
|
+
} catch (err) {
|
|
6297
|
+
console.error?.(`Failed to write hierarchical prompt journal: ${err}`);
|
|
6298
|
+
}
|
|
6299
|
+
return entry;
|
|
6300
|
+
}
|
|
6301
|
+
function formatEntryMarkdown(entry) {
|
|
6302
|
+
const tagList = [
|
|
6303
|
+
`**Category:** \`${entry.category}\``,
|
|
6304
|
+
`**Role:** \`${entry.role}\``,
|
|
6305
|
+
entry.metadata.model ? `**Model:** \`${entry.metadata.model}\`` : null,
|
|
6306
|
+
`**Tokens (est):** ~${entry.metadata.tokenEstimate}`,
|
|
6307
|
+
entry.metadata.iterationIndex !== void 0 ? `**Iteration:** #${entry.metadata.iterationIndex}` : null,
|
|
6308
|
+
`**Session:** \`${entry.sessionId}\``
|
|
6309
|
+
].filter(Boolean).join(" | ");
|
|
6310
|
+
let md = `
|
|
6311
|
+
### \u{1F4DD} [${entry.timestamp}] \`${entry.id}\`
|
|
6312
|
+
${tagList}
|
|
6313
|
+
|
|
6314
|
+
`;
|
|
6315
|
+
if (entry.metadata.decisionReason) {
|
|
6316
|
+
md += `> **Rationale:** ${entry.metadata.decisionReason}
|
|
6317
|
+
|
|
6318
|
+
`;
|
|
6017
6319
|
}
|
|
6018
|
-
|
|
6019
|
-
}
|
|
6020
|
-
|
|
6021
|
-
|
|
6022
|
-
state.thinking.push({
|
|
6023
|
-
textBuf: "",
|
|
6024
|
-
...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
|
|
6025
|
-
});
|
|
6026
|
-
state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
|
|
6027
|
-
state.currentTextIndex = -1;
|
|
6028
|
-
}
|
|
6029
|
-
function handleThinkingDelta(state, text) {
|
|
6030
|
-
if (state.currentThinkingIndex === -1) {
|
|
6031
|
-
handleThinkingStart(state, {});
|
|
6320
|
+
if (entry.metadata.activeTools && entry.metadata.activeTools.length > 0) {
|
|
6321
|
+
md += `*Active Tools:* \`${entry.metadata.activeTools.join("`, `")}\`
|
|
6322
|
+
|
|
6323
|
+
`;
|
|
6032
6324
|
}
|
|
6033
|
-
|
|
6034
|
-
|
|
6035
|
-
|
|
6036
|
-
|
|
6037
|
-
|
|
6038
|
-
|
|
6325
|
+
if (entry.rawContent && entry.rawContent !== entry.content) {
|
|
6326
|
+
md += `**Raw Input:**
|
|
6327
|
+
\`\`\`text
|
|
6328
|
+
${entry.rawContent.trim()}
|
|
6329
|
+
\`\`\`
|
|
6330
|
+
|
|
6331
|
+
`;
|
|
6332
|
+
md += `**Refined / Injected Prompt:**
|
|
6333
|
+
\`\`\`text
|
|
6334
|
+
${entry.content.trim()}
|
|
6335
|
+
\`\`\`
|
|
6336
|
+
|
|
6337
|
+
`;
|
|
6338
|
+
} else {
|
|
6339
|
+
md += `**Prompt Content:**
|
|
6340
|
+
\`\`\`text
|
|
6341
|
+
${entry.content.trim()}
|
|
6342
|
+
\`\`\`
|
|
6343
|
+
|
|
6344
|
+
`;
|
|
6039
6345
|
}
|
|
6040
|
-
|
|
6041
|
-
|
|
6042
|
-
|
|
6043
|
-
function handleThinkingStop(state) {
|
|
6044
|
-
state.currentThinkingIndex = -1;
|
|
6346
|
+
md += `---
|
|
6347
|
+
`;
|
|
6348
|
+
return md;
|
|
6045
6349
|
}
|
|
6046
|
-
function
|
|
6047
|
-
state.stopReason = ev.stopReason ?? "end_turn";
|
|
6048
|
-
state.usage = ev.usage ?? { input: 0, output: 0 };
|
|
6049
|
-
}
|
|
6050
|
-
async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
|
|
6051
|
-
const state = createStreamingState(req.model);
|
|
6052
|
-
const sessionId = resolveEventSessionId(ctx);
|
|
6053
|
-
logger.debug("Stream started", { providerId: provider.id, model: req.model });
|
|
6054
|
-
const TEXT_BATCH_SIZE = 4;
|
|
6055
|
-
let pendingText = "";
|
|
6056
|
-
let pendingCount = 0;
|
|
6057
|
-
const flushText = () => {
|
|
6058
|
-
if (pendingCount > 0) {
|
|
6059
|
-
events.emit("provider.text_delta", { sessionId, ctx, text: pendingText });
|
|
6060
|
-
pendingText = "";
|
|
6061
|
-
pendingCount = 0;
|
|
6062
|
-
}
|
|
6063
|
-
};
|
|
6064
|
-
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
6350
|
+
async function updateDailySummary(summaryFile, dateStr, entry) {
|
|
6065
6351
|
try {
|
|
6066
|
-
|
|
6067
|
-
const next = await iter.next();
|
|
6068
|
-
if (next.done) break;
|
|
6069
|
-
const ev = next.value;
|
|
6070
|
-
try {
|
|
6071
|
-
switch (ev.type) {
|
|
6072
|
-
case "message_start":
|
|
6073
|
-
handleMessageStart(state, ev.model);
|
|
6074
|
-
break;
|
|
6075
|
-
case "content_block_start":
|
|
6076
|
-
handleContentBlockStart(state, ev);
|
|
6077
|
-
break;
|
|
6078
|
-
case "content_block_stop":
|
|
6079
|
-
handleContentBlockStop(state, ev);
|
|
6080
|
-
break;
|
|
6081
|
-
case "text_delta":
|
|
6082
|
-
handleTextDelta(state, ev.text);
|
|
6083
|
-
pendingText += ev.text;
|
|
6084
|
-
pendingCount++;
|
|
6085
|
-
if (pendingCount >= TEXT_BATCH_SIZE) flushText();
|
|
6086
|
-
break;
|
|
6087
|
-
case "tool_use_start": {
|
|
6088
|
-
flushText();
|
|
6089
|
-
const idVal = ev.id;
|
|
6090
|
-
const nameVal = ev.name;
|
|
6091
|
-
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
6092
|
-
const emittedPayload = { sessionId, ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
6093
|
-
events.emit("provider.tool_use_start", emittedPayload);
|
|
6094
|
-
break;
|
|
6095
|
-
}
|
|
6096
|
-
case "tool_use_input_delta":
|
|
6097
|
-
handleToolUseInputDelta(state, ev);
|
|
6098
|
-
break;
|
|
6099
|
-
case "tool_use_stop": {
|
|
6100
|
-
flushText();
|
|
6101
|
-
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
6102
|
-
handleToolUseStop(state, ev);
|
|
6103
|
-
events.emit("provider.tool_use_stop", { sessionId, ctx, id: ev.id, name: stoppedName });
|
|
6104
|
-
break;
|
|
6105
|
-
}
|
|
6106
|
-
case "thinking_start":
|
|
6107
|
-
handleThinkingStart(state, ev);
|
|
6108
|
-
break;
|
|
6109
|
-
case "thinking_delta":
|
|
6110
|
-
flushText();
|
|
6111
|
-
handleThinkingDelta(state, ev.text);
|
|
6112
|
-
events.emit("provider.thinking_delta", { sessionId, ctx, text: ev.text });
|
|
6113
|
-
break;
|
|
6114
|
-
case "thinking_signature":
|
|
6115
|
-
handleThinkingSignature(state, ev.signature);
|
|
6116
|
-
break;
|
|
6117
|
-
case "thinking_stop":
|
|
6118
|
-
handleThinkingStop(state);
|
|
6119
|
-
break;
|
|
6120
|
-
case "message_stop":
|
|
6121
|
-
handleMessageStop(state, ev);
|
|
6122
|
-
break;
|
|
6123
|
-
default: {
|
|
6124
|
-
const unknownEv = ev;
|
|
6125
|
-
logger.warn(`Stream received unknown event type: "${String(unknownEv.type)}"`, {
|
|
6126
|
-
providerId: provider.id,
|
|
6127
|
-
model: req.model,
|
|
6128
|
-
eventType: String(unknownEv.type)
|
|
6129
|
-
});
|
|
6130
|
-
break;
|
|
6131
|
-
}
|
|
6132
|
-
}
|
|
6133
|
-
} catch (handlerErr) {
|
|
6134
|
-
const errMsg = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
|
|
6135
|
-
const evAny = ev;
|
|
6136
|
-
logger.warn(`Stream handler error for event type "${String(evAny.type)}": ${errMsg}`, {
|
|
6137
|
-
providerId: provider.id,
|
|
6138
|
-
model: req.model,
|
|
6139
|
-
eventType: String(evAny.type),
|
|
6140
|
-
errorMessage: errMsg
|
|
6141
|
-
});
|
|
6142
|
-
flushText();
|
|
6143
|
-
events.emit("provider.stream_error", {
|
|
6144
|
-
sessionId,
|
|
6145
|
-
ctx,
|
|
6146
|
-
eventType: String(evAny.type),
|
|
6147
|
-
msg: errMsg
|
|
6148
|
-
});
|
|
6149
|
-
}
|
|
6150
|
-
}
|
|
6151
|
-
} catch (err) {
|
|
6152
|
-
if (signal.aborted) {
|
|
6153
|
-
state.stopReason = "end_turn";
|
|
6154
|
-
flushText();
|
|
6155
|
-
logger.debug("Stream aborted \u2014 returning partial state", {
|
|
6156
|
-
providerId: provider.id,
|
|
6157
|
-
model: req.model,
|
|
6158
|
-
textBlockCount: state.textBuffers.length,
|
|
6159
|
-
toolBlockCount: state.tools.size,
|
|
6160
|
-
thinkingBlockCount: state.thinking.length
|
|
6161
|
-
});
|
|
6162
|
-
return buildResponse(state);
|
|
6163
|
-
}
|
|
6164
|
-
throw err;
|
|
6165
|
-
} finally {
|
|
6352
|
+
let content = "";
|
|
6166
6353
|
try {
|
|
6167
|
-
|
|
6168
|
-
try {
|
|
6169
|
-
const drainPromise = Promise.resolve(iter.return?.()).catch(() => {
|
|
6170
|
-
});
|
|
6171
|
-
await Promise.race([
|
|
6172
|
-
drainPromise,
|
|
6173
|
-
new Promise((resolve10) => {
|
|
6174
|
-
drainTimer = setTimeout(resolve10, STREAM_DRAIN_TIMEOUT_MS);
|
|
6175
|
-
})
|
|
6176
|
-
]);
|
|
6177
|
-
} finally {
|
|
6178
|
-
if (drainTimer) clearTimeout(drainTimer);
|
|
6179
|
-
}
|
|
6354
|
+
content = await fs5.readFile(summaryFile, "utf8");
|
|
6180
6355
|
} catch {
|
|
6356
|
+
content = `# \u{1F4C5} Daily Prompt Summary \u2014 ${dateStr}
|
|
6357
|
+
|
|
6358
|
+
| Time | ID | Session | Category | Model | Tokens | Rationale |
|
|
6359
|
+
| :--- | :--- | :--- | :--- | :--- | :--- | :--- |
|
|
6360
|
+
`;
|
|
6181
6361
|
}
|
|
6362
|
+
const time = entry.timestamp.slice(11, 19);
|
|
6363
|
+
const model = entry.metadata.model ?? "-";
|
|
6364
|
+
const reason = entry.metadata.decisionReason ? entry.metadata.decisionReason.slice(0, 40) : "-";
|
|
6365
|
+
const row = `| ${time} | [\`${entry.id}\`](session-${sessionFileId(entry.sessionId)}.md) | \`${entry.sessionId}\` | \`${entry.category}\` | ${model} | ~${entry.metadata.tokenEstimate} | ${reason} |
|
|
6366
|
+
`;
|
|
6367
|
+
await fs5.writeFile(summaryFile, content + row, "utf8");
|
|
6368
|
+
} catch {
|
|
6182
6369
|
}
|
|
6183
|
-
flushText();
|
|
6184
|
-
logger.debug("Stream completed", {
|
|
6185
|
-
providerId: provider.id,
|
|
6186
|
-
model: req.model,
|
|
6187
|
-
stopReason: state.stopReason,
|
|
6188
|
-
textBlockCount: state.textBuffers.length,
|
|
6189
|
-
toolBlockCount: state.tools.size,
|
|
6190
|
-
thinkingBlockCount: state.thinking.length,
|
|
6191
|
-
usageInput: state.usage.input,
|
|
6192
|
-
usageOutput: state.usage.output
|
|
6193
|
-
});
|
|
6194
|
-
return buildResponse(state);
|
|
6195
6370
|
}
|
|
6371
|
+
async function updateRootCatalog(baseDir, monthStr, dateStr, sessionId, entry) {
|
|
6372
|
+
const indexJsonFile = path11.join(baseDir, "index.json");
|
|
6373
|
+
const indexMdFile = path11.join(baseDir, "index.md");
|
|
6374
|
+
let catalog;
|
|
6375
|
+
try {
|
|
6376
|
+
const raw = await fs5.readFile(indexJsonFile, "utf8");
|
|
6377
|
+
catalog = JSON.parse(raw);
|
|
6378
|
+
} catch {
|
|
6379
|
+
catalog = {
|
|
6380
|
+
updatedAt: entry.timestamp,
|
|
6381
|
+
totalPrompts: 0,
|
|
6382
|
+
totalTokensEstimated: 0,
|
|
6383
|
+
months: {}
|
|
6384
|
+
};
|
|
6385
|
+
}
|
|
6386
|
+
catalog.updatedAt = entry.timestamp;
|
|
6387
|
+
catalog.totalPrompts += 1;
|
|
6388
|
+
catalog.totalTokensEstimated += entry.metadata.tokenEstimate;
|
|
6389
|
+
if (!catalog.months[monthStr]) {
|
|
6390
|
+
catalog.months[monthStr] = { days: {} };
|
|
6391
|
+
}
|
|
6392
|
+
const monthData = catalog.months[monthStr];
|
|
6393
|
+
if (!monthData.days[dateStr]) {
|
|
6394
|
+
monthData.days[dateStr] = { sessions: {} };
|
|
6395
|
+
}
|
|
6396
|
+
const dayData = monthData.days[dateStr];
|
|
6397
|
+
if (!dayData.sessions[sessionId]) {
|
|
6398
|
+
dayData.sessions[sessionId] = {
|
|
6399
|
+
promptCount: 0,
|
|
6400
|
+
tokenEstimate: 0,
|
|
6401
|
+
lastTimestamp: entry.timestamp,
|
|
6402
|
+
categories: {}
|
|
6403
|
+
};
|
|
6404
|
+
}
|
|
6405
|
+
const sessionData = dayData.sessions[sessionId];
|
|
6406
|
+
sessionData.promptCount += 1;
|
|
6407
|
+
sessionData.tokenEstimate += entry.metadata.tokenEstimate;
|
|
6408
|
+
sessionData.lastTimestamp = entry.timestamp;
|
|
6409
|
+
sessionData.categories[entry.category] = (sessionData.categories[entry.category] ?? 0) + 1;
|
|
6410
|
+
try {
|
|
6411
|
+
await fs5.writeFile(indexJsonFile, JSON.stringify(catalog, null, 2), "utf8");
|
|
6412
|
+
let md = `# \u{1F5C2}\uFE0F Prompt Journal Navigation Index
|
|
6196
6413
|
|
|
6197
|
-
|
|
6198
|
-
|
|
6199
|
-
|
|
6200
|
-
|
|
6201
|
-
|
|
6202
|
-
|
|
6203
|
-
|
|
6204
|
-
|
|
6205
|
-
|
|
6206
|
-
|
|
6207
|
-
|
|
6208
|
-
|
|
6209
|
-
|
|
6210
|
-
|
|
6211
|
-
|
|
6212
|
-
|
|
6213
|
-
|
|
6214
|
-
|
|
6215
|
-
|
|
6216
|
-
|
|
6217
|
-
|
|
6218
|
-
|
|
6219
|
-
|
|
6220
|
-
|
|
6221
|
-
seed: request.seed,
|
|
6222
|
-
toolChoice: request.toolChoice,
|
|
6223
|
-
reasoning: request.reasoning,
|
|
6224
|
-
cache: request.cache,
|
|
6225
|
-
responseFormat: request.responseFormat?.type
|
|
6226
|
-
};
|
|
6227
|
-
return {
|
|
6228
|
-
manifestId: `prompt_${hash(stable(core))}`,
|
|
6229
|
-
messageCount: messages.length,
|
|
6230
|
-
estimatedMessageTokens: messages.reduce((sum, message) => sum + (message.estimatedTokens ?? 0), 0),
|
|
6231
|
-
contentBytes: messages.reduce((sum, message) => sum + message.bytes, 0) + Buffer.byteLength(systemText),
|
|
6232
|
-
roleCounts,
|
|
6233
|
-
blockCounts,
|
|
6234
|
-
system: { blockCount: request.system?.length ?? 0, bytes: Buffer.byteLength(systemText), hash: hash(systemText) },
|
|
6235
|
-
messages,
|
|
6236
|
-
tools: {
|
|
6237
|
-
count: toolRecords.length,
|
|
6238
|
-
estimatedDefinitionTokens: toolRecords.reduce((sum, tool) => sum + tool.estimatedTokens, 0),
|
|
6239
|
-
manifestHash: hash(stable(toolRecords)),
|
|
6240
|
-
names: toolRecords.map((tool) => tool.name),
|
|
6241
|
-
mutating: toolRecords.filter((tool) => tool.mutating).length,
|
|
6242
|
-
destructive: toolRecords.filter((tool) => tool.riskTier === "destructive").length
|
|
6243
|
-
},
|
|
6244
|
-
request: {
|
|
6245
|
-
maxTokens: request.maxTokens,
|
|
6246
|
-
temperature: request.temperature,
|
|
6247
|
-
topP: request.topP,
|
|
6248
|
-
topK: request.topK,
|
|
6249
|
-
frequencyPenalty: request.frequencyPenalty,
|
|
6250
|
-
presencePenalty: request.presencePenalty,
|
|
6251
|
-
seed: request.seed,
|
|
6252
|
-
candidateCount: request.candidateCount,
|
|
6253
|
-
logprobs: request.logprobs,
|
|
6254
|
-
topLogprobs: request.topLogprobs,
|
|
6255
|
-
stopSequenceCount: request.stopSequences?.length ?? 0,
|
|
6256
|
-
toolChoice: request.toolChoice,
|
|
6257
|
-
reasoning: request.reasoning,
|
|
6258
|
-
cache: request.cache,
|
|
6259
|
-
responseFormat: request.responseFormat?.type,
|
|
6260
|
-
safetySettingCount: request.safetySettings?.length ?? 0,
|
|
6261
|
-
userHash: request.user ? hash(request.user) : void 0
|
|
6414
|
+
`;
|
|
6415
|
+
md += `* **Total Prompts Logged:** ${catalog.totalPrompts}
|
|
6416
|
+
`;
|
|
6417
|
+
md += `* **Total Tokens (est):** ~${catalog.totalTokensEstimated.toLocaleString()}
|
|
6418
|
+
`;
|
|
6419
|
+
md += `* **Last Recorded Activity:** ${catalog.updatedAt}
|
|
6420
|
+
|
|
6421
|
+
`;
|
|
6422
|
+
md += `## \u{1F4C5} Recorded Dates & Sessions
|
|
6423
|
+
|
|
6424
|
+
`;
|
|
6425
|
+
md += `| Date | Session | Prompts | Tokens (est) | Daily Log | Session Log |
|
|
6426
|
+
`;
|
|
6427
|
+
md += `| :--- | :--- | :--- | :--- | :--- | :--- |
|
|
6428
|
+
`;
|
|
6429
|
+
for (const [m, mObj] of Object.entries(catalog.months).sort().reverse()) {
|
|
6430
|
+
for (const [d, dObj] of Object.entries(mObj.days).sort().reverse()) {
|
|
6431
|
+
for (const [sId, sData] of Object.entries(dObj.sessions)) {
|
|
6432
|
+
const dailyLink = `[daily-summary.md](./${m}/${d}/daily-summary.md)`;
|
|
6433
|
+
const sessionLink = `[session-${sessionFileId(sId)}.md](./${m}/${d}/session-${sessionFileId(sId)}.md)`;
|
|
6434
|
+
md += `| **${d}** | \`${sId}\` | ${sData.promptCount} | ~${sData.tokenEstimate.toLocaleString()} | ${dailyLink} | ${sessionLink} |
|
|
6435
|
+
`;
|
|
6436
|
+
}
|
|
6437
|
+
}
|
|
6262
6438
|
}
|
|
6263
|
-
|
|
6439
|
+
await fs5.writeFile(indexMdFile, md, "utf8");
|
|
6440
|
+
} catch {
|
|
6441
|
+
}
|
|
6264
6442
|
}
|
|
6265
|
-
|
|
6266
|
-
|
|
6267
|
-
|
|
6268
|
-
|
|
6269
|
-
|
|
6270
|
-
|
|
6271
|
-
|
|
6272
|
-
|
|
6273
|
-
|
|
6274
|
-
...message._estTokens !== void 0 ? { estimatedTokens: message._estTokens } : {},
|
|
6275
|
-
hash: content.hash,
|
|
6276
|
-
blocks
|
|
6277
|
-
};
|
|
6443
|
+
|
|
6444
|
+
// src/core/streaming-response-builder.ts
|
|
6445
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
6446
|
+
|
|
6447
|
+
// src/utils/json-repair.ts
|
|
6448
|
+
function completePartialObject(s) {
|
|
6449
|
+
if (!s.trim().startsWith("{")) return s;
|
|
6450
|
+
if (tryParse(s).ok) return s;
|
|
6451
|
+
return repairTruncated(s);
|
|
6278
6452
|
}
|
|
6279
|
-
function
|
|
6280
|
-
const
|
|
6281
|
-
|
|
6282
|
-
|
|
6283
|
-
|
|
6284
|
-
|
|
6285
|
-
|
|
6286
|
-
|
|
6287
|
-
|
|
6288
|
-
|
|
6289
|
-
|
|
6290
|
-
|
|
6291
|
-
|
|
6292
|
-
|
|
6293
|
-
|
|
6294
|
-
|
|
6295
|
-
|
|
6296
|
-
|
|
6297
|
-
|
|
6298
|
-
|
|
6299
|
-
|
|
6300
|
-
|
|
6301
|
-
|
|
6302
|
-
|
|
6303
|
-
|
|
6304
|
-
|
|
6305
|
-
|
|
6306
|
-
|
|
6307
|
-
|
|
6308
|
-
|
|
6309
|
-
|
|
6310
|
-
|
|
6311
|
-
|
|
6312
|
-
|
|
6313
|
-
|
|
6314
|
-
|
|
6315
|
-
|
|
6453
|
+
function repairTruncated(s) {
|
|
6454
|
+
const stack = [];
|
|
6455
|
+
let inString = false;
|
|
6456
|
+
let escaped = false;
|
|
6457
|
+
let sawKey = false;
|
|
6458
|
+
let prevSig = "";
|
|
6459
|
+
let contentEnd = 0;
|
|
6460
|
+
let stringBraceDepth = 0;
|
|
6461
|
+
for (let i = 0; i < s.length; i++) {
|
|
6462
|
+
const ch = expectDefined(s[i]);
|
|
6463
|
+
if (inString) {
|
|
6464
|
+
contentEnd = i + 1;
|
|
6465
|
+
if (escaped) {
|
|
6466
|
+
escaped = false;
|
|
6467
|
+
continue;
|
|
6468
|
+
}
|
|
6469
|
+
if (ch === "\\") {
|
|
6470
|
+
escaped = true;
|
|
6471
|
+
continue;
|
|
6472
|
+
}
|
|
6473
|
+
if (ch === '"') {
|
|
6474
|
+
inString = false;
|
|
6475
|
+
prevSig = '"';
|
|
6476
|
+
stringBraceDepth = 0;
|
|
6477
|
+
continue;
|
|
6478
|
+
}
|
|
6479
|
+
if (ch === "{") stringBraceDepth++;
|
|
6480
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
6481
|
+
continue;
|
|
6482
|
+
}
|
|
6483
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
6484
|
+
contentEnd = i + 1;
|
|
6485
|
+
if (ch === '"') {
|
|
6486
|
+
inString = true;
|
|
6487
|
+
sawKey = true;
|
|
6488
|
+
stringBraceDepth = 0;
|
|
6489
|
+
prevSig = '"';
|
|
6490
|
+
} else if (ch === "{" || ch === "[") {
|
|
6491
|
+
stack.push(ch);
|
|
6492
|
+
prevSig = ch;
|
|
6493
|
+
} else if (ch === "}" || ch === "]") {
|
|
6494
|
+
stack.pop();
|
|
6495
|
+
prevSig = ch;
|
|
6496
|
+
} else {
|
|
6497
|
+
prevSig = ch;
|
|
6316
6498
|
}
|
|
6317
6499
|
}
|
|
6318
|
-
|
|
6319
|
-
|
|
6320
|
-
|
|
6321
|
-
|
|
6322
|
-
|
|
6323
|
-
|
|
6324
|
-
|
|
6325
|
-
}
|
|
6326
|
-
|
|
6327
|
-
|
|
6328
|
-
|
|
6329
|
-
|
|
6330
|
-
function runWithNetworkTelemetry(context, run) {
|
|
6331
|
-
return storage.run(context, run);
|
|
6332
|
-
}
|
|
6333
|
-
|
|
6334
|
-
// src/security/error-sanitize.ts
|
|
6335
|
-
import { homedir as homedir2 } from "node:os";
|
|
6336
|
-
var scrubber = new DefaultSecretScrubber();
|
|
6337
|
-
function scrubErrorText(text) {
|
|
6338
|
-
if (!text) return text;
|
|
6339
|
-
let out = scrubber.scrub(text);
|
|
6340
|
-
const home = safeHomedir();
|
|
6341
|
-
if (home && home.length > 2) {
|
|
6342
|
-
out = replaceAllCaseInsensitive(out, home, "~");
|
|
6343
|
-
const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
|
|
6344
|
-
if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
|
|
6500
|
+
if (!sawKey && !inString) return s;
|
|
6501
|
+
let result = s.slice(0, contentEnd);
|
|
6502
|
+
if (inString) {
|
|
6503
|
+
if (escaped) {
|
|
6504
|
+
result = result.slice(0, -1);
|
|
6505
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
6506
|
+
result = result.slice(0, -2);
|
|
6507
|
+
}
|
|
6508
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
6509
|
+
result += '"';
|
|
6510
|
+
} else if (prevSig === ":") {
|
|
6511
|
+
result += "null";
|
|
6345
6512
|
}
|
|
6346
|
-
|
|
6513
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
6514
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
6515
|
+
}
|
|
6516
|
+
if (!tryParse(result).ok) {
|
|
6517
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
6518
|
+
if (tryParse(patched).ok) result = patched;
|
|
6519
|
+
}
|
|
6520
|
+
return result;
|
|
6347
6521
|
}
|
|
6348
|
-
|
|
6522
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
6523
|
+
function endsWithInvalidEscape(str) {
|
|
6524
|
+
const last = str[str.length - 1];
|
|
6525
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
6526
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
6527
|
+
let backslashes = 0;
|
|
6528
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
6529
|
+
return backslashes % 2 === 1;
|
|
6530
|
+
}
|
|
6531
|
+
function tryParse(s) {
|
|
6349
6532
|
try {
|
|
6350
|
-
return
|
|
6533
|
+
return { ok: true, value: JSON.parse(s) };
|
|
6351
6534
|
} catch {
|
|
6352
|
-
return
|
|
6535
|
+
return { ok: false };
|
|
6353
6536
|
}
|
|
6354
6537
|
}
|
|
6355
|
-
|
|
6356
|
-
|
|
6357
|
-
|
|
6358
|
-
|
|
6359
|
-
|
|
6360
|
-
|
|
6361
|
-
|
|
6362
|
-
|
|
6363
|
-
|
|
6364
|
-
|
|
6365
|
-
|
|
6538
|
+
|
|
6539
|
+
// src/core/streaming-response-builder.ts
|
|
6540
|
+
var STREAM_DRAIN_TIMEOUT_MS = 500;
|
|
6541
|
+
function buildResponse(state) {
|
|
6542
|
+
const content = [];
|
|
6543
|
+
for (const b of state.blockOrder) {
|
|
6544
|
+
if (b.kind === "text") {
|
|
6545
|
+
const txt = state.textBuffers[b.idx] ?? "";
|
|
6546
|
+
if (txt) content.push({ type: "text", text: txt });
|
|
6547
|
+
} else if (b.kind === "thinking") {
|
|
6548
|
+
const t2 = state.thinking[b.idx];
|
|
6549
|
+
if (!t2) continue;
|
|
6550
|
+
if (!t2.textBuf && !t2.signature) continue;
|
|
6551
|
+
const block = { type: "thinking", thinking: t2.textBuf };
|
|
6552
|
+
if (t2.signature) block.signature = t2.signature;
|
|
6553
|
+
if (t2.providerMeta && Object.keys(t2.providerMeta).length > 0) {
|
|
6554
|
+
block.providerMeta = t2.providerMeta;
|
|
6555
|
+
}
|
|
6556
|
+
content.push(block);
|
|
6557
|
+
} else {
|
|
6558
|
+
const tb = state.tools.get(b.id);
|
|
6559
|
+
if (tb) {
|
|
6560
|
+
const block = {
|
|
6561
|
+
type: "tool_use",
|
|
6562
|
+
id: b.id,
|
|
6563
|
+
name: tb.name,
|
|
6564
|
+
input: tb.input ?? {}
|
|
6565
|
+
};
|
|
6566
|
+
if (tb.providerMeta && Object.keys(tb.providerMeta).length > 0) {
|
|
6567
|
+
block.providerMeta = tb.providerMeta;
|
|
6568
|
+
}
|
|
6569
|
+
content.push(block);
|
|
6570
|
+
}
|
|
6571
|
+
}
|
|
6366
6572
|
}
|
|
6367
|
-
|
|
6573
|
+
if (content.length === 0) content.push({ type: "text", text: "" });
|
|
6574
|
+
return { content, stopReason: state.stopReason, usage: state.usage, model: state.model };
|
|
6368
6575
|
}
|
|
6369
|
-
|
|
6370
|
-
// src/core/provider-runner.ts
|
|
6371
|
-
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
6372
|
-
function scrubProviderBody(body) {
|
|
6373
|
-
if (!body) return void 0;
|
|
6576
|
+
function createStreamingState(model) {
|
|
6374
6577
|
return {
|
|
6375
|
-
|
|
6376
|
-
|
|
6377
|
-
|
|
6378
|
-
|
|
6379
|
-
|
|
6578
|
+
model,
|
|
6579
|
+
stopReason: "end_turn",
|
|
6580
|
+
usage: { input: 0, output: 0 },
|
|
6581
|
+
textBuffers: [],
|
|
6582
|
+
currentTextIndex: -1,
|
|
6583
|
+
tools: /* @__PURE__ */ new Map(),
|
|
6584
|
+
thinking: [],
|
|
6585
|
+
currentThinkingIndex: -1,
|
|
6586
|
+
blockOrder: []
|
|
6380
6587
|
};
|
|
6381
6588
|
}
|
|
6382
|
-
function
|
|
6383
|
-
|
|
6384
|
-
providerId: p.id,
|
|
6385
|
-
model: r.model,
|
|
6386
|
-
streaming: p.capabilities.streaming,
|
|
6387
|
-
msgCount: r.messages.length,
|
|
6388
|
-
toolCount: r.tools?.length ?? 0
|
|
6389
|
-
};
|
|
6589
|
+
function handleMessageStart(state, model) {
|
|
6590
|
+
state.model = model;
|
|
6390
6591
|
}
|
|
6391
|
-
|
|
6392
|
-
const
|
|
6393
|
-
|
|
6394
|
-
|
|
6395
|
-
|
|
6396
|
-
|
|
6397
|
-
|
|
6398
|
-
const
|
|
6399
|
-
|
|
6400
|
-
|
|
6401
|
-
|
|
6402
|
-
|
|
6403
|
-
|
|
6404
|
-
|
|
6405
|
-
|
|
6406
|
-
|
|
6407
|
-
|
|
6408
|
-
|
|
6409
|
-
|
|
6410
|
-
boardId: ctx.currentKanbanBoardId
|
|
6411
|
-
};
|
|
6412
|
-
events.emit("provider.attempt.started", {
|
|
6413
|
-
...correlation,
|
|
6414
|
-
streaming: provider.capabilities.streaming,
|
|
6415
|
-
messageCount: request.messages.length,
|
|
6416
|
-
toolCount: request.tools?.length ?? 0,
|
|
6417
|
-
promptManifest,
|
|
6418
|
-
startedAt
|
|
6419
|
-
});
|
|
6420
|
-
const span = tracer?.startSpan("provider.complete", {
|
|
6421
|
-
"provider.id": provider.id,
|
|
6422
|
-
"provider.model": request.model,
|
|
6423
|
-
"provider.streaming": provider.capabilities.streaming,
|
|
6424
|
-
"provider.attempt": attempt
|
|
6425
|
-
});
|
|
6426
|
-
logger.debug(`Provider attempt ${attempt + 1} starting`, providerLogCtx(provider, request));
|
|
6427
|
-
try {
|
|
6428
|
-
const res = await runWithNetworkTelemetry({
|
|
6429
|
-
events,
|
|
6430
|
-
sessionId: resolveEventSessionId(ctx),
|
|
6431
|
-
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
6432
|
-
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
6433
|
-
attemptId,
|
|
6434
|
-
initiator: "provider",
|
|
6435
|
-
operationName: `${provider.id}.complete`
|
|
6436
|
-
}, () => provider.capabilities.streaming ? streamProviderToResponse(provider, request, signal, ctx, events, logger) : provider.complete(request, { signal }));
|
|
6437
|
-
span?.setAttribute("provider.stopReason", res.stopReason);
|
|
6438
|
-
span?.setAttribute("provider.usage_in", res.usage.input);
|
|
6439
|
-
span?.setAttribute("provider.usage_out", res.usage.output);
|
|
6440
|
-
span?.end();
|
|
6441
|
-
events.emit("provider.attempt.completed", {
|
|
6442
|
-
...correlation,
|
|
6443
|
-
startedAt,
|
|
6444
|
-
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6445
|
-
durationMs: elapsedMs(startedNs),
|
|
6446
|
-
stopReason: res.stopReason,
|
|
6447
|
-
usage: res.usage
|
|
6448
|
-
});
|
|
6449
|
-
logger.debug("Provider call succeeded", {
|
|
6450
|
-
...providerLogCtx(provider, request),
|
|
6451
|
-
stopReason: res.stopReason,
|
|
6452
|
-
usageInput: res.usage.input,
|
|
6453
|
-
usageOutput: res.usage.output,
|
|
6454
|
-
cacheRead: res.usage.cacheRead,
|
|
6455
|
-
cacheWrite: res.usage.cacheWrite,
|
|
6456
|
-
attempts: attempt + 1
|
|
6457
|
-
});
|
|
6458
|
-
return res;
|
|
6459
|
-
} catch (err) {
|
|
6460
|
-
if (err instanceof Error) span?.recordError(err);
|
|
6461
|
-
span?.end();
|
|
6462
|
-
if (signal.aborted) throw err;
|
|
6463
|
-
const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
|
|
6464
|
-
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
6465
|
-
const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
6466
|
-
const providerErrorBody = isProviderErr ? scrubProviderBody(err.body) : void 0;
|
|
6467
|
-
const description = scrubErrorText(
|
|
6468
|
-
isProviderErr ? err.describe() : errAsErr.message
|
|
6469
|
-
);
|
|
6470
|
-
const delay2 = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
|
|
6471
|
-
events.emit("provider.attempt.failed", {
|
|
6472
|
-
...correlation,
|
|
6473
|
-
startedAt,
|
|
6474
|
-
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
6475
|
-
durationMs: elapsedMs(startedNs),
|
|
6476
|
-
status: isProviderErr ? err.status : 0,
|
|
6477
|
-
failureKind: isProviderErr ? err.kind : "unknown",
|
|
6478
|
-
description,
|
|
6479
|
-
retryable: canRetry,
|
|
6480
|
-
retryScheduled: canRetry,
|
|
6481
|
-
...delay2 !== void 0 ? { retryDelayMs: delay2 } : {},
|
|
6482
|
-
...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
|
|
6483
|
-
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
6484
|
-
});
|
|
6485
|
-
if (!canRetry) {
|
|
6486
|
-
events.emit("provider.error", {
|
|
6487
|
-
sessionId: resolveEventSessionId(ctx),
|
|
6488
|
-
providerId: isProviderErr ? err.providerId : provider.id,
|
|
6489
|
-
status: isProviderErr ? err.status : 0,
|
|
6490
|
-
description,
|
|
6491
|
-
retryable: false,
|
|
6492
|
-
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
6493
|
-
});
|
|
6494
|
-
logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
|
|
6495
|
-
...providerLogCtx(provider, request),
|
|
6496
|
-
attempts: attempt + 1,
|
|
6497
|
-
errorDescription: description,
|
|
6498
|
-
status: isProviderErr ? err.status : void 0,
|
|
6499
|
-
errorBody: providerErrorBody,
|
|
6500
|
-
errorName: err instanceof Error ? err.name : void 0,
|
|
6501
|
-
errorStack: err instanceof Error ? err.stack?.split("\n").slice(0, 3).join("\n") : void 0
|
|
6502
|
-
});
|
|
6503
|
-
throw toWrongStackError(err);
|
|
6504
|
-
}
|
|
6505
|
-
const attemptNum = attempt + 1;
|
|
6506
|
-
const maxAttempts = retry.maxAttempts(isProviderErr ? err : errAsErr);
|
|
6507
|
-
logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${delay2}ms \u2014 ${description}`, {
|
|
6508
|
-
...providerLogCtx(provider, request),
|
|
6509
|
-
attempt: attemptNum,
|
|
6510
|
-
maxAttempts,
|
|
6511
|
-
delayMs: delay2,
|
|
6512
|
-
errorDescription: description,
|
|
6513
|
-
status: isProviderErr ? err.status : void 0,
|
|
6514
|
-
errorBody: providerErrorBody
|
|
6515
|
-
});
|
|
6516
|
-
events.emit("provider.retry", {
|
|
6517
|
-
sessionId: resolveEventSessionId(ctx),
|
|
6518
|
-
providerId: isProviderErr ? err.providerId : provider.id,
|
|
6519
|
-
attempt: attemptNum,
|
|
6520
|
-
delayMs: delay2,
|
|
6521
|
-
status: isProviderErr ? err.status : 0,
|
|
6522
|
-
description,
|
|
6523
|
-
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
6524
|
-
});
|
|
6525
|
-
await new Promise((resolve10, reject) => {
|
|
6526
|
-
let settled = false;
|
|
6527
|
-
const cleanup = () => {
|
|
6528
|
-
clearTimeout(t2);
|
|
6529
|
-
signal.removeEventListener("abort", onAbort);
|
|
6530
|
-
};
|
|
6531
|
-
const onAbort = () => {
|
|
6532
|
-
if (settled) return;
|
|
6533
|
-
settled = true;
|
|
6534
|
-
cleanup();
|
|
6535
|
-
reject(new Error("aborted"));
|
|
6536
|
-
};
|
|
6537
|
-
const t2 = setTimeout(() => {
|
|
6538
|
-
if (settled) return;
|
|
6539
|
-
settled = true;
|
|
6540
|
-
cleanup();
|
|
6541
|
-
resolve10();
|
|
6542
|
-
}, delay2);
|
|
6543
|
-
if (signal.aborted) {
|
|
6544
|
-
onAbort();
|
|
6545
|
-
return;
|
|
6546
|
-
}
|
|
6547
|
-
signal.addEventListener("abort", onAbort);
|
|
6548
|
-
});
|
|
6549
|
-
attempt++;
|
|
6550
|
-
}
|
|
6592
|
+
function handleContentBlockStart(state, ev) {
|
|
6593
|
+
const kind = ev.kind ?? "text";
|
|
6594
|
+
if (kind === "text") {
|
|
6595
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
6596
|
+
state.textBuffers.push("");
|
|
6597
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
6598
|
+
} else if (kind === "tool_use") {
|
|
6599
|
+
const id = ev.id ?? randomUUID4();
|
|
6600
|
+
state.tools.set(id, { name: ev.name ?? "unknown", partial: "" });
|
|
6601
|
+
state.blockOrder.push({ kind: "tool", id });
|
|
6602
|
+
state.currentTextIndex = -1;
|
|
6603
|
+
} else if (kind === "thinking") {
|
|
6604
|
+
state.currentThinkingIndex = state.thinking.length;
|
|
6605
|
+
state.thinking.push({
|
|
6606
|
+
textBuf: "",
|
|
6607
|
+
...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
|
|
6608
|
+
});
|
|
6609
|
+
state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
|
|
6610
|
+
state.currentTextIndex = -1;
|
|
6551
6611
|
}
|
|
6552
6612
|
}
|
|
6553
|
-
function
|
|
6554
|
-
|
|
6613
|
+
function handleContentBlockStop(state, ev) {
|
|
6614
|
+
void state;
|
|
6615
|
+
void ev;
|
|
6555
6616
|
}
|
|
6556
|
-
|
|
6557
|
-
|
|
6558
|
-
|
|
6559
|
-
|
|
6560
|
-
|
|
6561
|
-
|
|
6562
|
-
|
|
6563
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
6564
|
-
const s = raw;
|
|
6565
|
-
return Array.isArray(s.items) && Array.isArray(s.seen) ? s : void 0;
|
|
6617
|
+
function handleTextDelta(state, text) {
|
|
6618
|
+
if (state.currentTextIndex === -1) {
|
|
6619
|
+
state.currentTextIndex = state.textBuffers.length;
|
|
6620
|
+
state.textBuffers.push("");
|
|
6621
|
+
state.blockOrder.push({ kind: "text", idx: state.currentTextIndex });
|
|
6622
|
+
}
|
|
6623
|
+
state.textBuffers[state.currentTextIndex] = (state.textBuffers[state.currentTextIndex] ?? "") + text;
|
|
6566
6624
|
}
|
|
6567
|
-
function
|
|
6568
|
-
|
|
6625
|
+
function handleToolUseStart(state, ev) {
|
|
6626
|
+
state.currentTextIndex = -1;
|
|
6627
|
+
state.tools.set(ev.id, { name: ev.name, partial: "" });
|
|
6628
|
+
state.blockOrder.push({ kind: "tool", id: ev.id });
|
|
6569
6629
|
}
|
|
6570
|
-
function
|
|
6571
|
-
const
|
|
6572
|
-
if (
|
|
6573
|
-
return `${trimmed.slice(0, MAX_PREVIEW_CHARS - 1)}\u2026`;
|
|
6630
|
+
function handleToolUseInputDelta(state, ev) {
|
|
6631
|
+
const t2 = state.tools.get(ev.id);
|
|
6632
|
+
if (t2) t2.partial += ev.partial;
|
|
6574
6633
|
}
|
|
6575
|
-
function
|
|
6576
|
-
|
|
6577
|
-
|
|
6578
|
-
|
|
6579
|
-
|
|
6580
|
-
|
|
6634
|
+
function safeJsonOrRaw(s) {
|
|
6635
|
+
if (!s) return {};
|
|
6636
|
+
try {
|
|
6637
|
+
return JSON.parse(s);
|
|
6638
|
+
} catch {
|
|
6639
|
+
const repaired = completePartialObject(s);
|
|
6640
|
+
try {
|
|
6641
|
+
return JSON.parse(repaired);
|
|
6642
|
+
} catch {
|
|
6643
|
+
return { _raw: repaired };
|
|
6644
|
+
}
|
|
6581
6645
|
}
|
|
6582
|
-
ctx.meta[META_KEY3] = { items, seen: prev?.seen ?? [] };
|
|
6583
6646
|
}
|
|
6584
|
-
function
|
|
6585
|
-
const
|
|
6586
|
-
if (
|
|
6587
|
-
|
|
6588
|
-
|
|
6589
|
-
delete ctx.meta[META_KEY3];
|
|
6590
|
-
return null;
|
|
6647
|
+
function handleToolUseStop(state, ev) {
|
|
6648
|
+
const t2 = state.tools.get(ev.id);
|
|
6649
|
+
if (t2) {
|
|
6650
|
+
t2.input = ev.input !== void 0 ? ev.input : safeJsonOrRaw(t2.partial);
|
|
6651
|
+
if (ev.providerMeta) t2.providerMeta = ev.providerMeta;
|
|
6591
6652
|
}
|
|
6592
|
-
|
|
6593
|
-
return s.items;
|
|
6653
|
+
state.currentTextIndex = -1;
|
|
6594
6654
|
}
|
|
6595
|
-
function
|
|
6596
|
-
|
|
6597
|
-
|
|
6598
|
-
|
|
6599
|
-
|
|
6600
|
-
|
|
6601
|
-
|
|
6602
|
-
|
|
6603
|
-
"is just an FYI, fold it in. The queue may still change before delivery.",
|
|
6604
|
-
"This notice replaces any earlier one \u2014 the queue now holds exactly:",
|
|
6605
|
-
"",
|
|
6606
|
-
body,
|
|
6607
|
-
"]"
|
|
6608
|
-
].join("\n");
|
|
6655
|
+
function handleThinkingStart(state, ev) {
|
|
6656
|
+
state.currentThinkingIndex = state.thinking.length;
|
|
6657
|
+
state.thinking.push({
|
|
6658
|
+
textBuf: "",
|
|
6659
|
+
...ev.providerMeta ? { providerMeta: ev.providerMeta } : {}
|
|
6660
|
+
});
|
|
6661
|
+
state.blockOrder.push({ kind: "thinking", idx: state.currentThinkingIndex });
|
|
6662
|
+
state.currentTextIndex = -1;
|
|
6609
6663
|
}
|
|
6610
|
-
|
|
6611
|
-
|
|
6612
|
-
|
|
6613
|
-
|
|
6614
|
-
|
|
6664
|
+
function handleThinkingDelta(state, text) {
|
|
6665
|
+
if (state.currentThinkingIndex === -1) {
|
|
6666
|
+
handleThinkingStart(state, {});
|
|
6667
|
+
}
|
|
6668
|
+
const t2 = state.thinking[state.currentThinkingIndex];
|
|
6669
|
+
if (t2) t2.textBuf += text;
|
|
6615
6670
|
}
|
|
6616
|
-
function
|
|
6617
|
-
|
|
6671
|
+
function handleThinkingSignature(state, signature) {
|
|
6672
|
+
if (state.currentThinkingIndex === -1) {
|
|
6673
|
+
handleThinkingStart(state, {});
|
|
6674
|
+
}
|
|
6675
|
+
const t2 = state.thinking[state.currentThinkingIndex];
|
|
6676
|
+
if (t2) t2.signature = signature;
|
|
6618
6677
|
}
|
|
6619
|
-
|
|
6620
|
-
|
|
6621
|
-
function toError(err) {
|
|
6622
|
-
return err instanceof Error ? err : new Error(String(err));
|
|
6678
|
+
function handleThinkingStop(state) {
|
|
6679
|
+
state.currentThinkingIndex = -1;
|
|
6623
6680
|
}
|
|
6624
|
-
function
|
|
6625
|
-
|
|
6626
|
-
|
|
6627
|
-
if (typeof r === "string" && r.length > 0) return r;
|
|
6628
|
-
return "aborted";
|
|
6681
|
+
function handleMessageStop(state, ev) {
|
|
6682
|
+
state.stopReason = ev.stopReason ?? "end_turn";
|
|
6683
|
+
state.usage = ev.usage ?? { input: 0, output: 0 };
|
|
6629
6684
|
}
|
|
6630
|
-
function
|
|
6631
|
-
const
|
|
6632
|
-
const
|
|
6633
|
-
|
|
6634
|
-
|
|
6635
|
-
|
|
6636
|
-
|
|
6685
|
+
async function streamProviderToResponse(provider, req, signal, ctx, events, logger) {
|
|
6686
|
+
const state = createStreamingState(req.model);
|
|
6687
|
+
const sessionId = resolveEventSessionId(ctx);
|
|
6688
|
+
logger.debug("Stream started", { providerId: provider.id, model: req.model });
|
|
6689
|
+
const TEXT_BATCH_SIZE = 4;
|
|
6690
|
+
let pendingText = "";
|
|
6691
|
+
let pendingCount = 0;
|
|
6692
|
+
const flushText = () => {
|
|
6693
|
+
if (pendingCount > 0) {
|
|
6694
|
+
events.emit("provider.text_delta", { sessionId, ctx, text: pendingText });
|
|
6695
|
+
pendingText = "";
|
|
6696
|
+
pendingCount = 0;
|
|
6637
6697
|
}
|
|
6638
|
-
}
|
|
6639
|
-
const
|
|
6640
|
-
|
|
6641
|
-
|
|
6642
|
-
|
|
6643
|
-
|
|
6644
|
-
|
|
6645
|
-
|
|
6646
|
-
|
|
6647
|
-
|
|
6648
|
-
|
|
6649
|
-
|
|
6650
|
-
|
|
6651
|
-
|
|
6652
|
-
|
|
6653
|
-
|
|
6654
|
-
|
|
6655
|
-
|
|
6698
|
+
};
|
|
6699
|
+
const iter = provider.stream(req, { signal })[Symbol.asyncIterator]();
|
|
6700
|
+
try {
|
|
6701
|
+
for (; ; ) {
|
|
6702
|
+
const next = await iter.next();
|
|
6703
|
+
if (next.done) break;
|
|
6704
|
+
const ev = next.value;
|
|
6705
|
+
try {
|
|
6706
|
+
switch (ev.type) {
|
|
6707
|
+
case "message_start":
|
|
6708
|
+
handleMessageStart(state, ev.model);
|
|
6709
|
+
break;
|
|
6710
|
+
case "content_block_start":
|
|
6711
|
+
handleContentBlockStart(state, ev);
|
|
6712
|
+
break;
|
|
6713
|
+
case "content_block_stop":
|
|
6714
|
+
handleContentBlockStop(state, ev);
|
|
6715
|
+
break;
|
|
6716
|
+
case "text_delta":
|
|
6717
|
+
handleTextDelta(state, ev.text);
|
|
6718
|
+
pendingText += ev.text;
|
|
6719
|
+
pendingCount++;
|
|
6720
|
+
if (pendingCount >= TEXT_BATCH_SIZE) flushText();
|
|
6721
|
+
break;
|
|
6722
|
+
case "tool_use_start": {
|
|
6723
|
+
flushText();
|
|
6724
|
+
const idVal = ev.id;
|
|
6725
|
+
const nameVal = ev.name;
|
|
6726
|
+
handleToolUseStart(state, { id: idVal, name: nameVal });
|
|
6727
|
+
const emittedPayload = { sessionId, ctx, id: idVal ?? "unknown", name: nameVal ?? "unknown" };
|
|
6728
|
+
events.emit("provider.tool_use_start", emittedPayload);
|
|
6729
|
+
break;
|
|
6730
|
+
}
|
|
6731
|
+
case "tool_use_input_delta":
|
|
6732
|
+
handleToolUseInputDelta(state, ev);
|
|
6733
|
+
break;
|
|
6734
|
+
case "tool_use_stop": {
|
|
6735
|
+
flushText();
|
|
6736
|
+
const stoppedName = state.tools.get(ev.id)?.name ?? "unknown";
|
|
6737
|
+
handleToolUseStop(state, ev);
|
|
6738
|
+
events.emit("provider.tool_use_stop", { sessionId, ctx, id: ev.id, name: stoppedName });
|
|
6739
|
+
break;
|
|
6740
|
+
}
|
|
6741
|
+
case "thinking_start":
|
|
6742
|
+
handleThinkingStart(state, ev);
|
|
6743
|
+
break;
|
|
6744
|
+
case "thinking_delta":
|
|
6745
|
+
flushText();
|
|
6746
|
+
handleThinkingDelta(state, ev.text);
|
|
6747
|
+
events.emit("provider.thinking_delta", { sessionId, ctx, text: ev.text });
|
|
6748
|
+
break;
|
|
6749
|
+
case "thinking_signature":
|
|
6750
|
+
handleThinkingSignature(state, ev.signature);
|
|
6751
|
+
break;
|
|
6752
|
+
case "thinking_stop":
|
|
6753
|
+
handleThinkingStop(state);
|
|
6754
|
+
break;
|
|
6755
|
+
case "message_stop":
|
|
6756
|
+
handleMessageStop(state, ev);
|
|
6757
|
+
break;
|
|
6758
|
+
default: {
|
|
6759
|
+
const unknownEv = ev;
|
|
6760
|
+
logger.warn(`Stream received unknown event type: "${String(unknownEv.type)}"`, {
|
|
6761
|
+
providerId: provider.id,
|
|
6762
|
+
model: req.model,
|
|
6763
|
+
eventType: String(unknownEv.type)
|
|
6764
|
+
});
|
|
6765
|
+
break;
|
|
6766
|
+
}
|
|
6767
|
+
}
|
|
6768
|
+
} catch (handlerErr) {
|
|
6769
|
+
const errMsg = handlerErr instanceof Error ? handlerErr.message : String(handlerErr);
|
|
6770
|
+
const evAny = ev;
|
|
6771
|
+
logger.warn(`Stream handler error for event type "${String(evAny.type)}": ${errMsg}`, {
|
|
6772
|
+
providerId: provider.id,
|
|
6773
|
+
model: req.model,
|
|
6774
|
+
eventType: String(evAny.type),
|
|
6775
|
+
errorMessage: errMsg
|
|
6776
|
+
});
|
|
6777
|
+
flushText();
|
|
6778
|
+
events.emit("provider.stream_error", {
|
|
6779
|
+
sessionId,
|
|
6780
|
+
ctx,
|
|
6781
|
+
eventType: String(evAny.type),
|
|
6782
|
+
msg: errMsg
|
|
6783
|
+
});
|
|
6784
|
+
}
|
|
6656
6785
|
}
|
|
6657
|
-
|
|
6658
|
-
|
|
6659
|
-
|
|
6660
|
-
|
|
6661
|
-
|
|
6662
|
-
const lastEmitted = a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
6663
|
-
const lastEmittedSource = a.ctx.meta["contextLimitLastEmittedSource"];
|
|
6664
|
-
if (lastEmitted === effective2 && lastEmittedSource === source) return;
|
|
6665
|
-
const eventPrevious = typeof lastEmitted === "number" && Number.isFinite(lastEmitted) && lastEmitted > 0 ? Math.floor(lastEmitted) : previousMaxContext;
|
|
6666
|
-
a.ctx.meta["contextLimitLastEmittedMaxContext"] = effective2;
|
|
6667
|
-
a.ctx.meta["contextLimitLastEmittedSource"] = source;
|
|
6668
|
-
a.events.emit("ctx.max_context", {
|
|
6669
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
6786
|
+
} catch (err) {
|
|
6787
|
+
if (signal.aborted) {
|
|
6788
|
+
state.stopReason = "end_turn";
|
|
6789
|
+
flushText();
|
|
6790
|
+
logger.debug("Stream aborted \u2014 returning partial state", {
|
|
6670
6791
|
providerId: provider.id,
|
|
6671
|
-
|
|
6672
|
-
|
|
6673
|
-
|
|
6674
|
-
|
|
6675
|
-
decreased: eventPrevious > 0 && effective2 < eventPrevious
|
|
6792
|
+
model: req.model,
|
|
6793
|
+
textBlockCount: state.textBuffers.length,
|
|
6794
|
+
toolBlockCount: state.tools.size,
|
|
6795
|
+
thinkingBlockCount: state.thinking.length
|
|
6676
6796
|
});
|
|
6677
|
-
|
|
6678
|
-
const emitNativeAfterRouteChange = () => {
|
|
6679
|
-
if (routeChanged && currentEffective > 0) {
|
|
6680
|
-
a.ctx.meta["effectiveMaxContext"] = currentEffective;
|
|
6681
|
-
a.ctx.meta["effectiveMaxContextSource"] = "configured";
|
|
6682
|
-
emitEffectiveLimit(currentEffective, "configured");
|
|
6683
|
-
}
|
|
6684
|
-
};
|
|
6685
|
-
const refresh = provider.refreshContextLimit;
|
|
6686
|
-
if (opts.probe === false || !refresh) {
|
|
6687
|
-
emitNativeAfterRouteChange();
|
|
6688
|
-
return;
|
|
6797
|
+
return buildResponse(state);
|
|
6689
6798
|
}
|
|
6690
|
-
|
|
6799
|
+
throw err;
|
|
6800
|
+
} finally {
|
|
6691
6801
|
try {
|
|
6692
|
-
|
|
6802
|
+
let drainTimer = null;
|
|
6803
|
+
try {
|
|
6804
|
+
const drainPromise = Promise.resolve(iter.return?.()).catch(() => {
|
|
6805
|
+
});
|
|
6806
|
+
await Promise.race([
|
|
6807
|
+
drainPromise,
|
|
6808
|
+
new Promise((resolve10) => {
|
|
6809
|
+
drainTimer = setTimeout(resolve10, STREAM_DRAIN_TIMEOUT_MS);
|
|
6810
|
+
})
|
|
6811
|
+
]);
|
|
6812
|
+
} finally {
|
|
6813
|
+
if (drainTimer) clearTimeout(drainTimer);
|
|
6814
|
+
}
|
|
6693
6815
|
} catch {
|
|
6694
|
-
emitNativeAfterRouteChange();
|
|
6695
|
-
return;
|
|
6696
|
-
}
|
|
6697
|
-
const providerLimit = discovered?.maxContext;
|
|
6698
|
-
if (typeof providerLimit !== "number" || !Number.isFinite(providerLimit) || providerLimit <= 0) {
|
|
6699
|
-
emitNativeAfterRouteChange();
|
|
6700
|
-
return;
|
|
6701
|
-
}
|
|
6702
|
-
const providerLimitFloored = Math.floor(providerLimit);
|
|
6703
|
-
const existingBaseline = a.ctx.meta["contextLimitBaseline"];
|
|
6704
|
-
const lastApplied = a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
6705
|
-
const existingOverflow = a.ctx.meta["providerOverflowMaxContext"];
|
|
6706
|
-
const effectiveChangedOutsideProbe = typeof lastApplied === "number" && Number.isFinite(lastApplied) && currentEffective !== Math.floor(lastApplied) && currentEffective !== existingOverflow;
|
|
6707
|
-
const baseline = effectiveChangedOutsideProbe || typeof existingBaseline !== "number" || !Number.isFinite(existingBaseline) || existingBaseline <= 0 ? currentEffective : Math.floor(existingBaseline);
|
|
6708
|
-
a.ctx.meta["contextLimitBaseline"] = baseline;
|
|
6709
|
-
const overflowLimit = a.ctx.meta["providerOverflowMaxContext"];
|
|
6710
|
-
const overflowFloored = typeof overflowLimit === "number" && Number.isFinite(overflowLimit) && overflowLimit > 0 ? Math.floor(overflowLimit) : void 0;
|
|
6711
|
-
const ceilings = [baseline, providerLimitFloored];
|
|
6712
|
-
if (overflowFloored !== void 0) ceilings.push(overflowFloored);
|
|
6713
|
-
const effective = Math.min(...ceilings.filter((limit) => limit > 0));
|
|
6714
|
-
let effectiveSource;
|
|
6715
|
-
if (overflowFloored !== void 0 && effective === overflowFloored) {
|
|
6716
|
-
effectiveSource = "provider_overflow";
|
|
6717
|
-
} else if (effective === providerLimitFloored) {
|
|
6718
|
-
effectiveSource = "provider";
|
|
6719
|
-
} else {
|
|
6720
|
-
effectiveSource = "configured";
|
|
6721
|
-
}
|
|
6722
|
-
a.ctx.meta["effectiveMaxContext"] = effective;
|
|
6723
|
-
a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
|
|
6724
|
-
a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
|
|
6725
|
-
emitEffectiveLimit(effective, effectiveSource);
|
|
6726
|
-
}
|
|
6727
|
-
async function compactContextIfNeeded() {
|
|
6728
|
-
const msgCount = a.ctx.messages.length;
|
|
6729
|
-
const maxContext = currentMaxContext();
|
|
6730
|
-
const revision = a.ctx.state.revision;
|
|
6731
|
-
const toolsRef = a.ctx.tools;
|
|
6732
|
-
const systemRef = a.ctx.systemPrompt;
|
|
6733
|
-
const requestTokens = a.ctx.lastRequestTokens;
|
|
6734
|
-
if (_lastCompactionMsgCount === msgCount && _lastCompactionRevision === revision && _lastCompactionToolsRef === toolsRef && _lastCompactionSystemRef === systemRef && _lastCompactionRequestTokens === requestTokens && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
|
|
6735
|
-
return false;
|
|
6736
|
-
}
|
|
6737
|
-
const beforeMessages = a.ctx.messages;
|
|
6738
|
-
const beforeMsgCount = a.ctx.messages.length;
|
|
6739
|
-
const beforeRevision = a.ctx.state.revision;
|
|
6740
|
-
await a.pipelines.contextWindow.run(a.ctx);
|
|
6741
|
-
_lastCompactionMsgCount = a.ctx.messages.length;
|
|
6742
|
-
_lastCompactionRevision = a.ctx.state.revision;
|
|
6743
|
-
_lastCompactionToolsRef = a.ctx.tools;
|
|
6744
|
-
_lastCompactionSystemRef = a.ctx.systemPrompt;
|
|
6745
|
-
_lastCompactionMaxContext = maxContext;
|
|
6746
|
-
const changed = a.ctx.state.revision !== beforeRevision || a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
|
|
6747
|
-
const tokens = refreshContextRequestTokenStash({ force: changed });
|
|
6748
|
-
_lastCompactionRequestTokens = tokens;
|
|
6749
|
-
const load = maxContext > 0 ? tokens / maxContext : 0;
|
|
6750
|
-
_lastCompactionWasNoop = tokens > 0 && load < 0.5;
|
|
6751
|
-
if (changed) {
|
|
6752
|
-
_lastEmittedMsgCount = -1;
|
|
6753
|
-
_lastEmittedToolCount = -1;
|
|
6754
|
-
_lastEmittedMaxContext = -1;
|
|
6755
6816
|
}
|
|
6756
|
-
return changed;
|
|
6757
6817
|
}
|
|
6758
|
-
|
|
6759
|
-
|
|
6760
|
-
|
|
6761
|
-
|
|
6762
|
-
|
|
6763
|
-
|
|
6764
|
-
|
|
6765
|
-
|
|
6766
|
-
|
|
6818
|
+
flushText();
|
|
6819
|
+
logger.debug("Stream completed", {
|
|
6820
|
+
providerId: provider.id,
|
|
6821
|
+
model: req.model,
|
|
6822
|
+
stopReason: state.stopReason,
|
|
6823
|
+
textBlockCount: state.textBuffers.length,
|
|
6824
|
+
toolBlockCount: state.tools.size,
|
|
6825
|
+
thinkingBlockCount: state.thinking.length,
|
|
6826
|
+
usageInput: state.usage.input,
|
|
6827
|
+
usageOutput: state.usage.output
|
|
6828
|
+
});
|
|
6829
|
+
return buildResponse(state);
|
|
6830
|
+
}
|
|
6831
|
+
|
|
6832
|
+
// src/chronicle/prompt-manifest.ts
|
|
6833
|
+
import { createHash as createHash5 } from "node:crypto";
|
|
6834
|
+
function createChroniclePromptManifest(request) {
|
|
6835
|
+
const roleCounts = {}, blockCounts = {};
|
|
6836
|
+
const messages = request.messages.map((message, index) => messageSummary(message, index, roleCounts, blockCounts));
|
|
6837
|
+
const systemText = (request.system ?? []).map((block) => block.text).join("\n");
|
|
6838
|
+
const toolRecords = (request.tools ?? []).map((tool) => ({
|
|
6839
|
+
name: tool.name,
|
|
6840
|
+
schemaHash: hash(stable(tool.inputSchema)),
|
|
6841
|
+
permission: tool.permission,
|
|
6842
|
+
mutating: tool.mutating,
|
|
6843
|
+
riskTier: tool.riskTier,
|
|
6844
|
+
capabilities: tool.capabilities,
|
|
6845
|
+
estimatedTokens: tool._estDefTokens ?? 0
|
|
6846
|
+
}));
|
|
6847
|
+
const core = {
|
|
6848
|
+
systemHash: hash(systemText),
|
|
6849
|
+
messages: messages.map(({ hash: contentHash, ...rest }) => ({ ...rest, contentHash })),
|
|
6850
|
+
tools: toolRecords,
|
|
6851
|
+
model: request.model,
|
|
6852
|
+
maxTokens: request.maxTokens,
|
|
6853
|
+
temperature: request.temperature,
|
|
6854
|
+
topP: request.topP,
|
|
6855
|
+
topK: request.topK,
|
|
6856
|
+
seed: request.seed,
|
|
6857
|
+
toolChoice: request.toolChoice,
|
|
6858
|
+
reasoning: request.reasoning,
|
|
6859
|
+
cache: request.cache,
|
|
6860
|
+
responseFormat: request.responseFormat?.type
|
|
6861
|
+
};
|
|
6862
|
+
return {
|
|
6863
|
+
manifestId: `prompt_${hash(stable(core))}`,
|
|
6864
|
+
messageCount: messages.length,
|
|
6865
|
+
estimatedMessageTokens: messages.reduce((sum, message) => sum + (message.estimatedTokens ?? 0), 0),
|
|
6866
|
+
contentBytes: messages.reduce((sum, message) => sum + message.bytes, 0) + Buffer.byteLength(systemText),
|
|
6867
|
+
roleCounts,
|
|
6868
|
+
blockCounts,
|
|
6869
|
+
system: { blockCount: request.system?.length ?? 0, bytes: Buffer.byteLength(systemText), hash: hash(systemText) },
|
|
6870
|
+
messages,
|
|
6871
|
+
tools: {
|
|
6872
|
+
count: toolRecords.length,
|
|
6873
|
+
estimatedDefinitionTokens: toolRecords.reduce((sum, tool) => sum + tool.estimatedTokens, 0),
|
|
6874
|
+
manifestHash: hash(stable(toolRecords)),
|
|
6875
|
+
names: toolRecords.map((tool) => tool.name),
|
|
6876
|
+
mutating: toolRecords.filter((tool) => tool.mutating).length,
|
|
6877
|
+
destructive: toolRecords.filter((tool) => tool.riskTier === "destructive").length
|
|
6878
|
+
},
|
|
6879
|
+
request: {
|
|
6880
|
+
maxTokens: request.maxTokens,
|
|
6881
|
+
temperature: request.temperature,
|
|
6882
|
+
topP: request.topP,
|
|
6883
|
+
topK: request.topK,
|
|
6884
|
+
frequencyPenalty: request.frequencyPenalty,
|
|
6885
|
+
presencePenalty: request.presencePenalty,
|
|
6886
|
+
seed: request.seed,
|
|
6887
|
+
candidateCount: request.candidateCount,
|
|
6888
|
+
logprobs: request.logprobs,
|
|
6889
|
+
topLogprobs: request.topLogprobs,
|
|
6890
|
+
stopSequenceCount: request.stopSequences?.length ?? 0,
|
|
6891
|
+
toolChoice: request.toolChoice,
|
|
6892
|
+
reasoning: request.reasoning,
|
|
6893
|
+
cache: request.cache,
|
|
6894
|
+
responseFormat: request.responseFormat?.type,
|
|
6895
|
+
safetySettingCount: request.safetySettings?.length ?? 0,
|
|
6896
|
+
userHash: request.user ? hash(request.user) : void 0
|
|
6897
|
+
}
|
|
6898
|
+
};
|
|
6899
|
+
}
|
|
6900
|
+
function messageSummary(message, index, roles, totals) {
|
|
6901
|
+
roles[message.role] = (roles[message.role] ?? 0) + 1;
|
|
6902
|
+
const blocks = typeof message.content === "string" ? { text: 1 } : countBlocks(message.content);
|
|
6903
|
+
for (const [type, count] of Object.entries(blocks)) totals[type] = (totals[type] ?? 0) + count;
|
|
6904
|
+
const content = contentIdentity(message.content);
|
|
6905
|
+
return {
|
|
6906
|
+
index,
|
|
6907
|
+
role: message.role,
|
|
6908
|
+
bytes: content.bytes,
|
|
6909
|
+
...message._estTokens !== void 0 ? { estimatedTokens: message._estTokens } : {},
|
|
6910
|
+
hash: content.hash,
|
|
6911
|
+
blocks
|
|
6912
|
+
};
|
|
6913
|
+
}
|
|
6914
|
+
function countBlocks(blocks) {
|
|
6915
|
+
const counts = {};
|
|
6916
|
+
for (const block of blocks) counts[block.type] = (counts[block.type] ?? 0) + 1;
|
|
6917
|
+
return counts;
|
|
6918
|
+
}
|
|
6919
|
+
function hash(value) {
|
|
6920
|
+
return createHash5("sha256").update(value).digest("hex");
|
|
6921
|
+
}
|
|
6922
|
+
function contentIdentity(content) {
|
|
6923
|
+
if (typeof content === "string") return { hash: hash(content), bytes: Buffer.byteLength(content) };
|
|
6924
|
+
const digest = createHash5("sha256");
|
|
6925
|
+
let bytes = 0;
|
|
6926
|
+
const add = (value) => {
|
|
6927
|
+
if (!value) return;
|
|
6928
|
+
digest.update(value);
|
|
6929
|
+
bytes += Buffer.byteLength(value);
|
|
6930
|
+
};
|
|
6931
|
+
for (const block of content) {
|
|
6932
|
+
add(block.type);
|
|
6933
|
+
if (block.type === "text") add(block.text);
|
|
6934
|
+
else if (block.type === "thinking") {
|
|
6935
|
+
add(block.thinking);
|
|
6936
|
+
add(block.signature);
|
|
6937
|
+
} else if (block.type === "tool_use") {
|
|
6938
|
+
add(block.id);
|
|
6939
|
+
add(block.name);
|
|
6940
|
+
add(stable(block.input));
|
|
6941
|
+
} else if (block.type === "tool_result") {
|
|
6942
|
+
add(block.tool_use_id);
|
|
6943
|
+
add(block.name);
|
|
6944
|
+
add(block.content);
|
|
6945
|
+
add(String(block.is_error ?? false));
|
|
6946
|
+
} else if (block.type === "image") {
|
|
6947
|
+
add(block.source.type);
|
|
6948
|
+
add(block.source.media_type);
|
|
6949
|
+
add(block.source.url);
|
|
6950
|
+
add(block.source.data);
|
|
6767
6951
|
}
|
|
6768
|
-
const breakdown = estimateRequestTokens([], sysRef, toolsRef ?? [], calibrationKey());
|
|
6769
|
-
_cachedSysRef = sysRef;
|
|
6770
|
-
_cachedToolsRef = toolsRef;
|
|
6771
|
-
_cachedOverheadTokens = breakdown.systemPrompt + breakdown.tools;
|
|
6772
|
-
return _cachedOverheadTokens;
|
|
6773
6952
|
}
|
|
6774
|
-
|
|
6775
|
-
|
|
6776
|
-
|
|
6777
|
-
|
|
6778
|
-
|
|
6779
|
-
|
|
6780
|
-
|
|
6781
|
-
|
|
6782
|
-
|
|
6783
|
-
|
|
6784
|
-
|
|
6785
|
-
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6953
|
+
return { hash: digest.digest("hex"), bytes };
|
|
6954
|
+
}
|
|
6955
|
+
function stable(value) {
|
|
6956
|
+
if (value === void 0) return "undefined";
|
|
6957
|
+
if (value === null || typeof value !== "object") return JSON.stringify(value);
|
|
6958
|
+
if (Array.isArray(value)) return `[${value.map(stable).join(",")}]`;
|
|
6959
|
+
return `{${Object.entries(value).sort(([a], [b]) => a.localeCompare(b)).map(([key, child]) => `${JSON.stringify(key)}:${stable(child)}`).join(",")}}`;
|
|
6960
|
+
}
|
|
6961
|
+
|
|
6962
|
+
// src/observability/network-telemetry.ts
|
|
6963
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6964
|
+
var storage = new AsyncLocalStorage();
|
|
6965
|
+
function runWithNetworkTelemetry(context, run) {
|
|
6966
|
+
return storage.run(context, run);
|
|
6967
|
+
}
|
|
6968
|
+
|
|
6969
|
+
// src/security/error-sanitize.ts
|
|
6970
|
+
import { homedir as homedir2 } from "node:os";
|
|
6971
|
+
var scrubber = new DefaultSecretScrubber();
|
|
6972
|
+
function scrubErrorText(text) {
|
|
6973
|
+
if (!text) return text;
|
|
6974
|
+
let out = scrubber.scrub(text);
|
|
6975
|
+
const home = safeHomedir();
|
|
6976
|
+
if (home && home.length > 2) {
|
|
6977
|
+
out = replaceAllCaseInsensitive(out, home, "~");
|
|
6978
|
+
const alt = home.includes("\\") ? home.replace(/\\/g, "/") : home.replace(/\//g, "\\");
|
|
6979
|
+
if (alt !== home) out = replaceAllCaseInsensitive(out, alt, "~");
|
|
6794
6980
|
}
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
const meta = stashedAt;
|
|
6803
|
-
if (meta.msgCount === msgCount && meta.toolCount === toolCount && meta.revision === revision && _lastPreFlightRevision === revision) {
|
|
6804
|
-
return stashed;
|
|
6805
|
-
}
|
|
6806
|
-
}
|
|
6807
|
-
const refreshed = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
|
|
6808
|
-
a.ctx.lastRequestTokens = refreshed;
|
|
6809
|
-
_lastPreFlightMsgCount = msgCount;
|
|
6810
|
-
_lastPreFlightToolCount = toolCount;
|
|
6811
|
-
_lastPreFlightRevision = revision;
|
|
6812
|
-
a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount, revision };
|
|
6813
|
-
return refreshed;
|
|
6981
|
+
return out;
|
|
6982
|
+
}
|
|
6983
|
+
function safeHomedir() {
|
|
6984
|
+
try {
|
|
6985
|
+
return homedir2();
|
|
6986
|
+
} catch {
|
|
6987
|
+
return "";
|
|
6814
6988
|
}
|
|
6815
|
-
|
|
6816
|
-
|
|
6817
|
-
|
|
6818
|
-
|
|
6819
|
-
|
|
6820
|
-
|
|
6821
|
-
|
|
6822
|
-
|
|
6823
|
-
|
|
6824
|
-
|
|
6825
|
-
|
|
6826
|
-
|
|
6827
|
-
probedRouteKey = rebuiltRouteKey;
|
|
6828
|
-
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
6829
|
-
if (await compactContextIfNeeded()) {
|
|
6830
|
-
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
6831
|
-
req = prepared.request;
|
|
6832
|
-
preFlight = stashRequestTokens(req);
|
|
6833
|
-
}
|
|
6834
|
-
}
|
|
6835
|
-
}
|
|
6836
|
-
return { req, provider: prepared.provider, preFlight };
|
|
6989
|
+
}
|
|
6990
|
+
function replaceAllCaseInsensitive(haystack, needle, replacement) {
|
|
6991
|
+
const lowerHay = haystack.toLowerCase();
|
|
6992
|
+
const lowerNeedle = needle.toLowerCase();
|
|
6993
|
+
let idx = lowerHay.indexOf(lowerNeedle);
|
|
6994
|
+
if (idx === -1) return haystack;
|
|
6995
|
+
let out = "";
|
|
6996
|
+
let from = 0;
|
|
6997
|
+
while (idx !== -1) {
|
|
6998
|
+
out += haystack.slice(from, idx) + replacement;
|
|
6999
|
+
from = idx + needle.length;
|
|
7000
|
+
idx = lowerHay.indexOf(lowerNeedle, from);
|
|
6837
7001
|
}
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
6847
|
-
|
|
6848
|
-
|
|
6849
|
-
|
|
6850
|
-
|
|
6851
|
-
|
|
6852
|
-
|
|
6853
|
-
|
|
6854
|
-
|
|
6855
|
-
|
|
6856
|
-
|
|
6857
|
-
|
|
6858
|
-
|
|
6859
|
-
|
|
6860
|
-
|
|
6861
|
-
|
|
6862
|
-
|
|
6863
|
-
|
|
6864
|
-
|
|
6865
|
-
|
|
6866
|
-
|
|
6867
|
-
|
|
6868
|
-
|
|
7002
|
+
return out + haystack.slice(from);
|
|
7003
|
+
}
|
|
7004
|
+
|
|
7005
|
+
// src/core/provider-runner.ts
|
|
7006
|
+
import { randomUUID as randomUUID5 } from "node:crypto";
|
|
7007
|
+
function scrubProviderBody(body) {
|
|
7008
|
+
if (!body) return void 0;
|
|
7009
|
+
return {
|
|
7010
|
+
...body,
|
|
7011
|
+
...body.type !== void 0 ? { type: scrubErrorText(body.type) } : {},
|
|
7012
|
+
...body.message !== void 0 ? { message: scrubErrorText(body.message) } : {},
|
|
7013
|
+
...body.raw !== void 0 ? { raw: scrubErrorText(body.raw) } : {},
|
|
7014
|
+
...body.requestId !== void 0 ? { requestId: scrubErrorText(body.requestId) } : {}
|
|
7015
|
+
};
|
|
7016
|
+
}
|
|
7017
|
+
function providerLogCtx(p, r) {
|
|
7018
|
+
return {
|
|
7019
|
+
providerId: p.id,
|
|
7020
|
+
model: r.model,
|
|
7021
|
+
streaming: p.capabilities.streaming,
|
|
7022
|
+
msgCount: r.messages.length,
|
|
7023
|
+
toolCount: r.tools?.length ?? 0
|
|
7024
|
+
};
|
|
7025
|
+
}
|
|
7026
|
+
async function runProviderWithRetry(opts) {
|
|
7027
|
+
const { provider, request, signal, ctx, events, retry, logger, tracer } = opts;
|
|
7028
|
+
const logicalRequestId = randomUUID5();
|
|
7029
|
+
const promptManifest = createChroniclePromptManifest(request);
|
|
7030
|
+
let attempt = 0;
|
|
7031
|
+
for (; ; ) {
|
|
7032
|
+
const attemptId = randomUUID5();
|
|
7033
|
+
const startedAt = (/* @__PURE__ */ new Date()).toISOString();
|
|
7034
|
+
const startedNs = process.hrtime.bigint();
|
|
7035
|
+
const correlation = {
|
|
7036
|
+
sessionId: resolveEventSessionId(ctx),
|
|
7037
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
7038
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
7039
|
+
logicalRequestId,
|
|
7040
|
+
attemptId,
|
|
7041
|
+
attempt,
|
|
7042
|
+
providerId: provider.id,
|
|
7043
|
+
model: request.model,
|
|
7044
|
+
taskId: ctx.currentKanbanTaskId,
|
|
7045
|
+
boardId: ctx.currentKanbanBoardId
|
|
7046
|
+
};
|
|
7047
|
+
events.emit("provider.attempt.started", {
|
|
7048
|
+
...correlation,
|
|
7049
|
+
streaming: provider.capabilities.streaming,
|
|
7050
|
+
messageCount: request.messages.length,
|
|
7051
|
+
toolCount: request.tools?.length ?? 0,
|
|
7052
|
+
promptManifest,
|
|
7053
|
+
startedAt
|
|
7054
|
+
});
|
|
7055
|
+
const span = tracer?.startSpan("provider.complete", {
|
|
7056
|
+
"provider.id": provider.id,
|
|
7057
|
+
"provider.model": request.model,
|
|
7058
|
+
"provider.streaming": provider.capabilities.streaming,
|
|
7059
|
+
"provider.attempt": attempt
|
|
7060
|
+
});
|
|
7061
|
+
logger.debug(`Provider attempt ${attempt + 1} starting`, providerLogCtx(provider, request));
|
|
7062
|
+
try {
|
|
7063
|
+
const res = await runWithNetworkTelemetry({
|
|
7064
|
+
events,
|
|
7065
|
+
sessionId: resolveEventSessionId(ctx),
|
|
7066
|
+
...ctx.traceId ? { traceId: ctx.traceId } : {},
|
|
7067
|
+
...ctx.agentId ? { agentId: ctx.agentId } : {},
|
|
7068
|
+
attemptId,
|
|
7069
|
+
initiator: "provider",
|
|
7070
|
+
operationName: `${provider.id}.complete`
|
|
7071
|
+
}, () => provider.capabilities.streaming ? streamProviderToResponse(provider, request, signal, ctx, events, logger) : provider.complete(request, { signal }));
|
|
7072
|
+
span?.setAttribute("provider.stopReason", res.stopReason);
|
|
7073
|
+
span?.setAttribute("provider.usage_in", res.usage.input);
|
|
7074
|
+
span?.setAttribute("provider.usage_out", res.usage.output);
|
|
7075
|
+
span?.end();
|
|
7076
|
+
events.emit("provider.attempt.completed", {
|
|
7077
|
+
...correlation,
|
|
7078
|
+
startedAt,
|
|
7079
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7080
|
+
durationMs: elapsedMs(startedNs),
|
|
7081
|
+
stopReason: res.stopReason,
|
|
7082
|
+
usage: res.usage
|
|
7083
|
+
});
|
|
7084
|
+
logger.debug("Provider call succeeded", {
|
|
7085
|
+
...providerLogCtx(provider, request),
|
|
7086
|
+
stopReason: res.stopReason,
|
|
7087
|
+
usageInput: res.usage.input,
|
|
7088
|
+
usageOutput: res.usage.output,
|
|
7089
|
+
cacheRead: res.usage.cacheRead,
|
|
7090
|
+
cacheWrite: res.usage.cacheWrite,
|
|
7091
|
+
attempts: attempt + 1
|
|
7092
|
+
});
|
|
7093
|
+
return res;
|
|
7094
|
+
} catch (err) {
|
|
7095
|
+
if (err instanceof Error) span?.recordError(err);
|
|
7096
|
+
span?.end();
|
|
7097
|
+
if (signal.aborted) throw err;
|
|
7098
|
+
const isProviderErr = err instanceof ProviderError || ProviderError.isProviderError(err);
|
|
7099
|
+
const errAsErr = err instanceof Error ? err : new Error(String(err));
|
|
7100
|
+
const canRetry = retry.shouldRetry(isProviderErr ? err : errAsErr, attempt);
|
|
7101
|
+
const providerErrorBody = isProviderErr ? scrubProviderBody(err.body) : void 0;
|
|
7102
|
+
const description = scrubErrorText(
|
|
7103
|
+
isProviderErr ? err.describe() : errAsErr.message
|
|
7104
|
+
);
|
|
7105
|
+
const delay2 = canRetry ? Math.round(retry.delayMs(attempt, isProviderErr ? err : errAsErr)) : void 0;
|
|
7106
|
+
events.emit("provider.attempt.failed", {
|
|
7107
|
+
...correlation,
|
|
7108
|
+
startedAt,
|
|
7109
|
+
endedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
7110
|
+
durationMs: elapsedMs(startedNs),
|
|
7111
|
+
status: isProviderErr ? err.status : 0,
|
|
7112
|
+
failureKind: isProviderErr ? err.kind : "unknown",
|
|
7113
|
+
description,
|
|
7114
|
+
retryable: canRetry,
|
|
7115
|
+
retryScheduled: canRetry,
|
|
7116
|
+
...delay2 !== void 0 ? { retryDelayMs: delay2 } : {},
|
|
7117
|
+
...providerErrorBody?.requestId ? { providerRequestId: providerErrorBody.requestId } : {},
|
|
7118
|
+
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
7119
|
+
});
|
|
7120
|
+
if (!canRetry) {
|
|
7121
|
+
events.emit("provider.error", {
|
|
7122
|
+
sessionId: resolveEventSessionId(ctx),
|
|
7123
|
+
providerId: isProviderErr ? err.providerId : provider.id,
|
|
7124
|
+
status: isProviderErr ? err.status : 0,
|
|
7125
|
+
description,
|
|
7126
|
+
retryable: false,
|
|
7127
|
+
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
7128
|
+
});
|
|
7129
|
+
logger.error(`Provider call failed after ${attempt + 1} attempt(s) \u2014 ${description}`, {
|
|
7130
|
+
...providerLogCtx(provider, request),
|
|
7131
|
+
attempts: attempt + 1,
|
|
7132
|
+
errorDescription: description,
|
|
7133
|
+
status: isProviderErr ? err.status : void 0,
|
|
7134
|
+
errorBody: providerErrorBody,
|
|
7135
|
+
errorName: err instanceof Error ? err.name : void 0,
|
|
7136
|
+
errorStack: err instanceof Error ? err.stack?.split("\n").slice(0, 3).join("\n") : void 0
|
|
7137
|
+
});
|
|
7138
|
+
throw toWrongStackError(err);
|
|
7139
|
+
}
|
|
7140
|
+
const attemptNum = attempt + 1;
|
|
7141
|
+
const maxAttempts = retry.maxAttempts(isProviderErr ? err : errAsErr);
|
|
7142
|
+
logger.warn(`Provider retry ${attemptNum}/${maxAttempts} in ${delay2}ms \u2014 ${description}`, {
|
|
7143
|
+
...providerLogCtx(provider, request),
|
|
7144
|
+
attempt: attemptNum,
|
|
7145
|
+
maxAttempts,
|
|
7146
|
+
delayMs: delay2,
|
|
7147
|
+
errorDescription: description,
|
|
7148
|
+
status: isProviderErr ? err.status : void 0,
|
|
7149
|
+
errorBody: providerErrorBody
|
|
7150
|
+
});
|
|
7151
|
+
events.emit("provider.retry", {
|
|
7152
|
+
sessionId: resolveEventSessionId(ctx),
|
|
7153
|
+
providerId: isProviderErr ? err.providerId : provider.id,
|
|
7154
|
+
attempt: attemptNum,
|
|
7155
|
+
delayMs: delay2,
|
|
7156
|
+
status: isProviderErr ? err.status : 0,
|
|
7157
|
+
description,
|
|
7158
|
+
...providerErrorBody ? { errorBody: providerErrorBody } : {}
|
|
7159
|
+
});
|
|
7160
|
+
await new Promise((resolve10, reject) => {
|
|
7161
|
+
let settled = false;
|
|
7162
|
+
const cleanup = () => {
|
|
7163
|
+
clearTimeout(t2);
|
|
7164
|
+
signal.removeEventListener("abort", onAbort);
|
|
7165
|
+
};
|
|
7166
|
+
const onAbort = () => {
|
|
7167
|
+
if (settled) return;
|
|
7168
|
+
settled = true;
|
|
7169
|
+
cleanup();
|
|
7170
|
+
reject(new Error("aborted"));
|
|
7171
|
+
};
|
|
7172
|
+
const t2 = setTimeout(() => {
|
|
7173
|
+
if (settled) return;
|
|
7174
|
+
settled = true;
|
|
7175
|
+
cleanup();
|
|
7176
|
+
resolve10();
|
|
7177
|
+
}, delay2);
|
|
7178
|
+
if (signal.aborted) {
|
|
7179
|
+
onAbort();
|
|
7180
|
+
return;
|
|
7181
|
+
}
|
|
7182
|
+
signal.addEventListener("abort", onAbort);
|
|
7183
|
+
});
|
|
7184
|
+
attempt++;
|
|
6869
7185
|
}
|
|
6870
|
-
const rawLoad = maxContext > 0 ? total / maxContext : 0;
|
|
6871
|
-
const load = Math.max(0, Math.min(1, rawLoad));
|
|
6872
|
-
a.events.emit("ctx.pct", {
|
|
6873
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
6874
|
-
load,
|
|
6875
|
-
rawLoad,
|
|
6876
|
-
tokens: total,
|
|
6877
|
-
maxContext
|
|
6878
|
-
});
|
|
6879
7186
|
}
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
7187
|
+
}
|
|
7188
|
+
function elapsedMs(startedNs) {
|
|
7189
|
+
return Number(process.hrtime.bigint() - startedNs) / 1e6;
|
|
7190
|
+
}
|
|
7191
|
+
|
|
7192
|
+
// src/core/queued-messages.ts
|
|
7193
|
+
var META_KEY3 = "_queuedMessagesAwareness";
|
|
7194
|
+
var MAX_ITEMS = 20;
|
|
7195
|
+
var MAX_PREVIEW_CHARS = 500;
|
|
7196
|
+
function read(ctx) {
|
|
7197
|
+
const raw = ctx.meta[META_KEY3];
|
|
7198
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
7199
|
+
const s = raw;
|
|
7200
|
+
return Array.isArray(s.items) && Array.isArray(s.seen) ? s : void 0;
|
|
7201
|
+
}
|
|
7202
|
+
function sameList(a, b) {
|
|
7203
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
7204
|
+
}
|
|
7205
|
+
function toPreview(text) {
|
|
7206
|
+
const trimmed = text.trim();
|
|
7207
|
+
if (trimmed.length <= MAX_PREVIEW_CHARS) return trimmed;
|
|
7208
|
+
return `${trimmed.slice(0, MAX_PREVIEW_CHARS - 1)}\u2026`;
|
|
7209
|
+
}
|
|
7210
|
+
function setQueuedMessagesSnapshot(ctx, texts) {
|
|
7211
|
+
const items = texts.map(toPreview).filter((t2) => t2.length > 0).slice(0, MAX_ITEMS);
|
|
7212
|
+
const prev = read(ctx);
|
|
7213
|
+
if (items.length === 0 && (!prev || prev.seen.length === 0)) {
|
|
7214
|
+
if (prev) delete ctx.meta[META_KEY3];
|
|
7215
|
+
return;
|
|
6884
7216
|
}
|
|
6885
|
-
|
|
6886
|
-
|
|
6887
|
-
|
|
6888
|
-
|
|
6889
|
-
|
|
6890
|
-
|
|
6891
|
-
|
|
6892
|
-
|
|
6893
|
-
|
|
6894
|
-
|
|
6895
|
-
|
|
6896
|
-
|
|
6897
|
-
|
|
6898
|
-
|
|
7217
|
+
ctx.meta[META_KEY3] = { items, seen: prev?.seen ?? [] };
|
|
7218
|
+
}
|
|
7219
|
+
function consumeQueuedMessagesUpdate(ctx) {
|
|
7220
|
+
const s = read(ctx);
|
|
7221
|
+
if (!s) return null;
|
|
7222
|
+
if (sameList(s.items, s.seen)) return null;
|
|
7223
|
+
if (s.items.length === 0) {
|
|
7224
|
+
delete ctx.meta[META_KEY3];
|
|
7225
|
+
return null;
|
|
7226
|
+
}
|
|
7227
|
+
ctx.meta[META_KEY3] = { items: s.items, seen: s.items };
|
|
7228
|
+
return s.items;
|
|
7229
|
+
}
|
|
7230
|
+
function buildQueuedMessagesBlock(items) {
|
|
7231
|
+
const body = items.map((t2, i) => `${i + 1}. ${t2}`).join("\n");
|
|
7232
|
+
return [
|
|
7233
|
+
"[QUEUED MESSAGES \u2014 the user typed these while you were working. They are",
|
|
7234
|
+
"waiting in the input queue and each will be delivered as its own turn",
|
|
7235
|
+
"after you finish, so do NOT answer or act on them as new tasks now. Read",
|
|
7236
|
+
"them as a heads-up: if one changes what you should be doing right now",
|
|
7237
|
+
"(new constraint, invalidated approach), adapt your current work; if one",
|
|
7238
|
+
"is just an FYI, fold it in. The queue may still change before delivery.",
|
|
7239
|
+
"This notice replaces any earlier one \u2014 the queue now holds exactly:",
|
|
7240
|
+
"",
|
|
7241
|
+
body,
|
|
7242
|
+
"]"
|
|
7243
|
+
].join("\n");
|
|
7244
|
+
}
|
|
7245
|
+
|
|
7246
|
+
// src/core/request-provider-binding.ts
|
|
7247
|
+
var requestProviders = /* @__PURE__ */ new WeakMap();
|
|
7248
|
+
function bindRequestProvider(request, provider) {
|
|
7249
|
+
requestProviders.set(request, provider);
|
|
7250
|
+
}
|
|
7251
|
+
function providerBoundToRequest(request) {
|
|
7252
|
+
return requestProviders.get(request);
|
|
7253
|
+
}
|
|
7254
|
+
|
|
7255
|
+
// src/core/agent-loop.ts
|
|
7256
|
+
function toError(err) {
|
|
7257
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
7258
|
+
}
|
|
7259
|
+
function recordSelfHealingRetry(a, err, reason) {
|
|
7260
|
+
const projectRoot = a.ctx.projectRoot;
|
|
7261
|
+
if (!projectRoot) return;
|
|
7262
|
+
void recordPromptJournalEntry({
|
|
7263
|
+
projectRoot,
|
|
7264
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
7265
|
+
category: "self_healing_retry",
|
|
7266
|
+
content: toErrorMessage(err),
|
|
7267
|
+
decisionReason: reason,
|
|
7268
|
+
model: a.ctx.model,
|
|
7269
|
+
provider: a.ctx.provider?.id,
|
|
7270
|
+
activeTools: a.ctx.tools?.map((tool) => tool.name) ?? []
|
|
7271
|
+
}).catch(() => {
|
|
7272
|
+
});
|
|
7273
|
+
}
|
|
7274
|
+
function recordAutonomousContinue(a, text) {
|
|
7275
|
+
const projectRoot = a.ctx.projectRoot;
|
|
7276
|
+
if (!projectRoot) return;
|
|
7277
|
+
void recordPromptJournalEntry({
|
|
7278
|
+
projectRoot,
|
|
7279
|
+
sessionId: resolveEventSessionId(a.ctx),
|
|
7280
|
+
category: "autonomous_next_step",
|
|
7281
|
+
content: text,
|
|
7282
|
+
decisionReason: "text-marker autonomous continue",
|
|
7283
|
+
model: a.ctx.model,
|
|
7284
|
+
provider: a.ctx.provider?.id,
|
|
7285
|
+
activeTools: a.ctx.tools?.map((tool) => tool.name) ?? []
|
|
7286
|
+
}).catch(() => {
|
|
7287
|
+
});
|
|
7288
|
+
}
|
|
7289
|
+
function signalAbortReason(signal) {
|
|
7290
|
+
const r = signal.reason;
|
|
7291
|
+
if (r instanceof Error) return r.message || r.name;
|
|
7292
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
7293
|
+
return "aborted";
|
|
7294
|
+
}
|
|
7295
|
+
function createAgentLoopHandler(a, handlers) {
|
|
7296
|
+
const checkMailbox = attachMailboxChecker(a);
|
|
7297
|
+
const fleetPulseCfg = (() => {
|
|
7298
|
+
try {
|
|
7299
|
+
return typeof a.container?.has === "function" && a.container.has(TOKENS.ConfigStore) ? a.container.resolve(TOKENS.ConfigStore).get().fleet?.pulse : void 0;
|
|
7300
|
+
} catch {
|
|
7301
|
+
return void 0;
|
|
7302
|
+
}
|
|
7303
|
+
})();
|
|
7304
|
+
const getFleetPulse = attachFleetPulse(a, fleetPulseCfg);
|
|
7305
|
+
const pulseEveryN = Math.max(1, fleetPulseCfg?.everyNIterations ?? 5);
|
|
7306
|
+
const backgroundCoordination = () => a.ctx.meta["coordinationContextMode"] === "background";
|
|
7307
|
+
const loopContext = createAgentLoopContextManager(a, handlers);
|
|
6899
7308
|
function foldBlockIntoConversation(block) {
|
|
6900
7309
|
if (!a.ctx.state.appendBlockToLastUserMessage(block)) {
|
|
6901
7310
|
a.ctx.state.appendMessage({ role: "user", content: [block], origin: "runtime" });
|
|
6902
7311
|
}
|
|
6903
7312
|
}
|
|
6904
|
-
function iterationFingerprint(blocks) {
|
|
6905
|
-
const toolUses = blocks.filter(isToolUseBlock);
|
|
6906
|
-
const texts = blocks.filter(isTextBlock);
|
|
6907
|
-
const toolNameSet = Array.from(new Set(toolUses.map((u) => u.name))).sort();
|
|
6908
|
-
const firstInputHash = toolUses[0] ? hashSmall(stableStringify(toolUses[0].input ?? {})) : "";
|
|
6909
|
-
const textBlob = texts.map((t2) => t2.text).join("").slice(0, 512);
|
|
6910
|
-
const hasContent = toolNameSet.length > 0 || textBlob.length > 0;
|
|
6911
|
-
if (!hasContent) return "__empty__";
|
|
6912
|
-
return [
|
|
6913
|
-
`tools=${toolNameSet.join("+") || "-"}`,
|
|
6914
|
-
`in0=${firstInputHash}`,
|
|
6915
|
-
`txt=${textBlob}`
|
|
6916
|
-
].join("\n");
|
|
6917
|
-
}
|
|
6918
|
-
function stableStringify(value) {
|
|
6919
|
-
return JSON.stringify(canonicalize(value));
|
|
6920
|
-
}
|
|
6921
|
-
function canonicalize(value) {
|
|
6922
|
-
if (Array.isArray(value)) return value.map(canonicalize);
|
|
6923
|
-
if (value && typeof value === "object") {
|
|
6924
|
-
const src = value;
|
|
6925
|
-
const out = {};
|
|
6926
|
-
for (const k of Object.keys(src).sort()) out[k] = canonicalize(src[k]);
|
|
6927
|
-
return out;
|
|
6928
|
-
}
|
|
6929
|
-
return value;
|
|
6930
|
-
}
|
|
6931
|
-
function hashSmall(s) {
|
|
6932
|
-
let h = 2166136261;
|
|
6933
|
-
for (let i = 0; i < s.length; i++) {
|
|
6934
|
-
h ^= s.charCodeAt(i);
|
|
6935
|
-
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
6936
|
-
}
|
|
6937
|
-
return h.toString(36);
|
|
6938
|
-
}
|
|
6939
7313
|
function injectPendingBtwNotes(onMailboxBlock) {
|
|
6940
7314
|
const notes = consumeBtwNotes(a.ctx);
|
|
6941
7315
|
if (notes.length === 0) return;
|
|
@@ -7015,15 +7389,10 @@ function createAgentLoopHandler(a, handlers) {
|
|
|
7015
7389
|
a.ctx.state.replaceMessages(cleaned.messages);
|
|
7016
7390
|
a.ctx.lastRealInputTokens = void 0;
|
|
7017
7391
|
delete a.ctx.meta["realAnchorMsgCount"];
|
|
7018
|
-
refreshContextRequestTokenStash({ force: true });
|
|
7392
|
+
loopContext.refreshContextRequestTokenStash({ force: true });
|
|
7019
7393
|
}
|
|
7020
7394
|
}
|
|
7021
|
-
const
|
|
7022
|
-
let lastToolSignature = "";
|
|
7023
|
-
let toolLoopCount = 0;
|
|
7024
|
-
let iterationSteerDone = false;
|
|
7025
|
-
const recentCallKeys = [];
|
|
7026
|
-
const steeredCallKeys = /* @__PURE__ */ new Set();
|
|
7395
|
+
const loopDetector = new AgentLoopDetector(a);
|
|
7027
7396
|
let pendingLoopSteer = null;
|
|
7028
7397
|
let todoReconcileSteers = 0;
|
|
7029
7398
|
function queueLoopSteer(text) {
|
|
@@ -7130,7 +7499,7 @@ ${text}` : text;
|
|
|
7130
7499
|
req,
|
|
7131
7500
|
provider: requestProvider,
|
|
7132
7501
|
preFlight
|
|
7133
|
-
} = await buildRequestWithPreflightCompaction(opts);
|
|
7502
|
+
} = await loopContext.buildRequestWithPreflightCompaction(opts);
|
|
7134
7503
|
await a.ctx.session.append({
|
|
7135
7504
|
type: "llm_request",
|
|
7136
7505
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7143,7 +7512,7 @@ ${text}` : text;
|
|
|
7143
7512
|
let res;
|
|
7144
7513
|
try {
|
|
7145
7514
|
res = await customRunner(a.ctx, req);
|
|
7146
|
-
const key = calibrationKey(req.model);
|
|
7515
|
+
const key = loopContext.calibrationKey(req.model);
|
|
7147
7516
|
const cal = getCalibrationState(key);
|
|
7148
7517
|
const calibratedTotal = cal.calibrated ? Math.round(preFlight.total * Math.min(1.5, Math.max(0.5, cal.ratio))) : preFlight.total;
|
|
7149
7518
|
const realInputTokens = effectiveInputTokens(res.usage);
|
|
@@ -7151,10 +7520,10 @@ ${text}` : text;
|
|
|
7151
7520
|
const previousRealInput = a.ctx.lastRealInputTokens;
|
|
7152
7521
|
const previousAnchorMsgCount = typeof a.ctx.meta?.["realAnchorMsgCount"] === "number" ? a.ctx.meta["realAnchorMsgCount"] : void 0;
|
|
7153
7522
|
const anchorIsPlausible = realInputTokens >= calibratedTotal * 0.5;
|
|
7154
|
-
const anchorAdvanced = previousRealInput === void 0 || realInputTokens > previousRealInput || previousAnchorMsgCount !== void 0 &&
|
|
7523
|
+
const anchorAdvanced = previousRealInput === void 0 || realInputTokens > previousRealInput || previousAnchorMsgCount !== void 0 && loopContext.lastPreFlightMsgCount < previousAnchorMsgCount;
|
|
7155
7524
|
if (realInputTokens > 0 && anchorIsPlausible && anchorAdvanced) {
|
|
7156
7525
|
a.ctx.lastRealInputTokens = realInputTokens;
|
|
7157
|
-
a.ctx.meta["realAnchorMsgCount"] =
|
|
7526
|
+
a.ctx.meta["realAnchorMsgCount"] = loopContext.lastPreFlightMsgCount;
|
|
7158
7527
|
}
|
|
7159
7528
|
recoveryRetries = 0;
|
|
7160
7529
|
} catch (err) {
|
|
@@ -7207,6 +7576,7 @@ ${text}` : text;
|
|
|
7207
7576
|
}
|
|
7208
7577
|
if (extDecision.model) a.ctx.model = extDecision.model;
|
|
7209
7578
|
a.logger.info("Extension requested retry; retrying turn");
|
|
7579
|
+
recordSelfHealingRetry(a, err, "extension-requested provider retry");
|
|
7210
7580
|
continue;
|
|
7211
7581
|
}
|
|
7212
7582
|
}
|
|
@@ -7236,6 +7606,7 @@ ${text}` : text;
|
|
|
7236
7606
|
}
|
|
7237
7607
|
if (recovered.model) a.ctx.model = recovered.model;
|
|
7238
7608
|
a.logger.info(`Recovered provider error via ${recovered.reason}; retrying turn`);
|
|
7609
|
+
recordSelfHealingRetry(a, err, recovered.reason);
|
|
7239
7610
|
continue;
|
|
7240
7611
|
}
|
|
7241
7612
|
recoveryRetries = 0;
|
|
@@ -7244,7 +7615,9 @@ ${text}` : text;
|
|
|
7244
7615
|
clearEvaluatedMailboxBlocks();
|
|
7245
7616
|
const responseProvider = providerBoundToRequest(req) ?? requestProvider;
|
|
7246
7617
|
const responseResult = await handlers.response.processResponse(res, req, responseProvider);
|
|
7247
|
-
await refreshProviderContextLimit(responseProvider, req.model, {
|
|
7618
|
+
await loopContext.refreshProviderContextLimit(responseProvider, req.model, {
|
|
7619
|
+
probe: false
|
|
7620
|
+
});
|
|
7248
7621
|
if (responseResult.finalText) {
|
|
7249
7622
|
a.ctx.meta["lastAgentOutput"] = responseResult.finalText;
|
|
7250
7623
|
}
|
|
@@ -7267,103 +7640,18 @@ ${text}` : text;
|
|
|
7267
7640
|
}
|
|
7268
7641
|
finalText = responseResult.finalText;
|
|
7269
7642
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
7270
|
-
|
|
7271
|
-
|
|
7272
|
-
|
|
7273
|
-
|
|
7274
|
-
|
|
7275
|
-
|
|
7276
|
-
|
|
7277
|
-
|
|
7278
|
-
iterationSteerDone = false;
|
|
7279
|
-
}
|
|
7280
|
-
const names = toolUses.map((t2) => t2.name).join(", ");
|
|
7281
|
-
const hasText2 = res.content.some(isTextBlock);
|
|
7282
|
-
const kind = toolUses.length > 0 && hasText2 ? "mixed" : toolUses.length > 0 ? "tool" : "message";
|
|
7283
|
-
const observationRepeat = loopCfg.mode === "steer-then-cut" && toolUses.length > 0 && toolUses.every((use) => {
|
|
7284
|
-
const tool = a.tools.get(use.name);
|
|
7285
|
-
return tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0);
|
|
7286
|
-
});
|
|
7287
|
-
const repeatMultiplier = observationRepeat ? 2 : 1;
|
|
7288
|
-
const detail = kind === "tool" ? `"${names}" called with effectively identical inputs ${toolLoopCount} times in a row` : kind === "mixed" ? `"${names}" + same text repeated ${toolLoopCount} times in a row` : `same assistant text repeated ${toolLoopCount} times in a row`;
|
|
7289
|
-
const cutAt = (loopCfg.mode === "cut" ? loopCfg.steerThreshold : loopCfg.cutThreshold) * repeatMultiplier;
|
|
7290
|
-
if (toolLoopCount >= cutAt) {
|
|
7291
|
-
a.logger.warn(`Loop detected: ${detail} \u2014 stopping to prevent infinite loop.`);
|
|
7292
|
-
a.events.emit("tool.loop_detected", {
|
|
7293
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7294
|
-
ctx: a.ctx,
|
|
7295
|
-
tools: names,
|
|
7296
|
-
repeatCount: toolLoopCount,
|
|
7297
|
-
iteration: i,
|
|
7298
|
-
kind,
|
|
7299
|
-
action: "cut",
|
|
7300
|
-
scope: "iteration"
|
|
7301
|
-
});
|
|
7302
|
-
const summary = kind === "message" ? `[Loop detected: same assistant message repeated ${toolLoopCount}\xD7 \u2014 stopping to prevent infinite repetition.]` : `[Loop detected: ${detail} \u2014 stopping to prevent infinite repetition.]`;
|
|
7303
|
-
return {
|
|
7304
|
-
status: "max_iterations",
|
|
7305
|
-
iterations,
|
|
7306
|
-
finalText: finalText || summary,
|
|
7307
|
-
delegateSummaries
|
|
7308
|
-
};
|
|
7309
|
-
}
|
|
7310
|
-
if (loopCfg.mode === "steer-then-cut" && toolLoopCount >= loopCfg.steerThreshold * repeatMultiplier && !iterationSteerDone) {
|
|
7311
|
-
iterationSteerDone = true;
|
|
7312
|
-
a.logger.warn(`Loop detected: ${detail} \u2014 steering the model to change approach.`);
|
|
7313
|
-
a.events.emit("tool.loop_detected", {
|
|
7314
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7315
|
-
ctx: a.ctx,
|
|
7316
|
-
tools: names,
|
|
7317
|
-
repeatCount: toolLoopCount,
|
|
7318
|
-
iteration: i,
|
|
7319
|
-
kind,
|
|
7320
|
-
action: "steer",
|
|
7321
|
-
scope: "iteration"
|
|
7322
|
-
});
|
|
7323
|
-
queueLoopSteer(
|
|
7324
|
-
`[loop-detector] Your last ${toolLoopCount} responses were effectively identical (${detail}). This approach is not working. Change strategy: use a different tool, different arguments, or a different plan \u2014 or explain what is blocking you and stop. Repeating the same response ${cutAt - toolLoopCount} more time(s) will terminate the turn.`
|
|
7325
|
-
);
|
|
7326
|
-
}
|
|
7327
|
-
} else {
|
|
7328
|
-
lastToolSignature = "";
|
|
7329
|
-
toolLoopCount = 0;
|
|
7330
|
-
iterationSteerDone = false;
|
|
7331
|
-
}
|
|
7332
|
-
if (loopCfg.mode === "steer-then-cut") {
|
|
7333
|
-
for (const u of toolUses) {
|
|
7334
|
-
const key = `${u.name}:${hashSmall(stableStringify(u.input ?? {}))}`;
|
|
7335
|
-
recentCallKeys.push(key);
|
|
7336
|
-
if (recentCallKeys.length > loopCfg.windowSize) recentCallKeys.shift();
|
|
7337
|
-
if (steeredCallKeys.has(key)) continue;
|
|
7338
|
-
let count = 0;
|
|
7339
|
-
for (const k of recentCallKeys) if (k === key) count++;
|
|
7340
|
-
const tool = a.tools.get(u.name);
|
|
7341
|
-
const observationThreshold = tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0) ? loopCfg.callRepeatThreshold * 2 : loopCfg.callRepeatThreshold;
|
|
7342
|
-
if (count < observationThreshold) continue;
|
|
7343
|
-
steeredCallKeys.add(key);
|
|
7344
|
-
const preview2 = JSON.stringify(u.input ?? {}).slice(0, 160);
|
|
7345
|
-
a.logger.warn(
|
|
7346
|
-
`Loop detected: "${u.name}" called with identical arguments ${count}\xD7 within the last ${loopCfg.windowSize} tool calls \u2014 steering the model to change approach.`
|
|
7347
|
-
);
|
|
7348
|
-
a.events.emit("tool.loop_detected", {
|
|
7349
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7350
|
-
ctx: a.ctx,
|
|
7351
|
-
tools: u.name,
|
|
7352
|
-
repeatCount: count,
|
|
7353
|
-
iteration: i,
|
|
7354
|
-
kind: "tool",
|
|
7355
|
-
action: "steer",
|
|
7356
|
-
scope: "call"
|
|
7357
|
-
});
|
|
7358
|
-
queueLoopSteer(
|
|
7359
|
-
`[loop-detector] You have called ${u.name}(${preview2}) ${count} times with identical arguments within the last ${loopCfg.windowSize} tool calls. The result will not change. Do not repeat this call \u2014 use what you already know, try a different approach, or explain the blocker.`
|
|
7360
|
-
);
|
|
7361
|
-
}
|
|
7362
|
-
}
|
|
7643
|
+
const loopCheck = loopDetector.checkIteration(i, res.content, toolUses, queueLoopSteer);
|
|
7644
|
+
if (loopCheck.cut) {
|
|
7645
|
+
return {
|
|
7646
|
+
status: "max_iterations",
|
|
7647
|
+
iterations,
|
|
7648
|
+
finalText: finalText || loopCheck.cutSummary || "",
|
|
7649
|
+
delegateSummaries
|
|
7650
|
+
};
|
|
7363
7651
|
}
|
|
7364
7652
|
if (toolUses.length === 0) {
|
|
7365
|
-
await compactContextIfNeeded();
|
|
7366
|
-
emitContextPct();
|
|
7653
|
+
await loopContext.compactContextIfNeeded();
|
|
7654
|
+
loopContext.emitContextPct();
|
|
7367
7655
|
a.events.emit("iteration.completed", {
|
|
7368
7656
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7369
7657
|
ctx: a.ctx,
|
|
@@ -7378,6 +7666,7 @@ ${text}` : text;
|
|
|
7378
7666
|
continue;
|
|
7379
7667
|
}
|
|
7380
7668
|
if (autonomousContinue && responseResult.directive === "continue") {
|
|
7669
|
+
recordAutonomousContinue(a, finalText);
|
|
7381
7670
|
await a.extensions.runAfterIteration(a.ctx, i);
|
|
7382
7671
|
continue;
|
|
7383
7672
|
}
|
|
@@ -7401,8 +7690,8 @@ ${text}` : text;
|
|
|
7401
7690
|
throw toolErr;
|
|
7402
7691
|
}
|
|
7403
7692
|
if (autonomousContinue && consumeAutonomousContinue(a.ctx)) {
|
|
7404
|
-
await compactContextIfNeeded();
|
|
7405
|
-
emitContextPct();
|
|
7693
|
+
await loopContext.compactContextIfNeeded();
|
|
7694
|
+
loopContext.emitContextPct();
|
|
7406
7695
|
a.events.emit("iteration.completed", {
|
|
7407
7696
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7408
7697
|
ctx: a.ctx,
|
|
@@ -7411,8 +7700,8 @@ ${text}` : text;
|
|
|
7411
7700
|
await a.extensions.runAfterIteration(a.ctx, i);
|
|
7412
7701
|
continue;
|
|
7413
7702
|
}
|
|
7414
|
-
await compactContextIfNeeded();
|
|
7415
|
-
emitContextPct();
|
|
7703
|
+
await loopContext.compactContextIfNeeded();
|
|
7704
|
+
loopContext.emitContextPct();
|
|
7416
7705
|
a.events.emit("iteration.completed", {
|
|
7417
7706
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7418
7707
|
ctx: a.ctx,
|
|
@@ -7611,6 +7900,10 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7611
7900
|
"codebase-incoming-calls",
|
|
7612
7901
|
"codebase-outgoing-calls",
|
|
7613
7902
|
"codebase-index",
|
|
7903
|
+
"codebase-skeleton",
|
|
7904
|
+
"codebase-repo-map",
|
|
7905
|
+
"codebase-impact-analysis",
|
|
7906
|
+
"codebase-invariant-check",
|
|
7614
7907
|
"dead-code-scan",
|
|
7615
7908
|
"diff",
|
|
7616
7909
|
"json",
|
|
@@ -7621,7 +7914,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7621
7914
|
id: "filesystem.write",
|
|
7622
7915
|
pack: "development",
|
|
7623
7916
|
exposure: "direct",
|
|
7624
|
-
tools: ["write", "edit", "replace", "patch"]
|
|
7917
|
+
tools: ["write", "edit", "replace", "patch", "codebase-ast-replace"]
|
|
7625
7918
|
},
|
|
7626
7919
|
{
|
|
7627
7920
|
id: "execution.shell",
|
|
@@ -7633,7 +7926,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7633
7926
|
id: "verification.run",
|
|
7634
7927
|
pack: "development",
|
|
7635
7928
|
exposure: "direct",
|
|
7636
|
-
tools: ["lint", "format", "typecheck", "test", "e2e_plan"]
|
|
7929
|
+
tools: ["lint", "format", "typecheck", "test", "e2e_plan", "codebase-targeted-test"]
|
|
7637
7930
|
},
|
|
7638
7931
|
{
|
|
7639
7932
|
id: "version-control.manage",
|
|
@@ -7770,7 +8063,13 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7770
8063
|
id: "quality.analyze",
|
|
7771
8064
|
pack: "development",
|
|
7772
8065
|
exposure: "on-demand",
|
|
7773
|
-
tools: [
|
|
8066
|
+
tools: [
|
|
8067
|
+
"dead_code_scan",
|
|
8068
|
+
"detect_duplicate_code",
|
|
8069
|
+
"secret_scanner_test",
|
|
8070
|
+
"error_lens_history",
|
|
8071
|
+
"security-ast-scan"
|
|
8072
|
+
]
|
|
7774
8073
|
},
|
|
7775
8074
|
{
|
|
7776
8075
|
id: "release.manage",
|
|
@@ -7788,7 +8087,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7788
8087
|
id: "tools.discover",
|
|
7789
8088
|
pack: "admin",
|
|
7790
8089
|
exposure: "direct",
|
|
7791
|
-
tools: ["tool_search", "tool_use", "tool_help", "batch_tool_use"]
|
|
8090
|
+
tools: ["tool_search", "tool_use", "tool_help", "batch_tool_use", "clarify"]
|
|
7792
8091
|
},
|
|
7793
8092
|
{
|
|
7794
8093
|
id: "runtime.admin",
|
|
@@ -8228,7 +8527,7 @@ function createAgentResponseHandler(a) {
|
|
|
8228
8527
|
...repaired.report
|
|
8229
8528
|
});
|
|
8230
8529
|
a.logger.warn(
|
|
8231
|
-
`Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)`
|
|
8530
|
+
`Repaired context tool adjacency: removed ${repaired.report.removedToolUses.length} tool_use block(s), ${repaired.report.removedToolResults.length} tool_result block(s), ${repaired.report.removedMessages} empty message(s)` + formatAdjacencyRepairIds(repaired.report.removedToolUses, repaired.report.removedToolResults)
|
|
8232
8531
|
);
|
|
8233
8532
|
}
|
|
8234
8533
|
}
|
|
@@ -8348,6 +8647,10 @@ function createAgentResponseHandler(a) {
|
|
|
8348
8647
|
}
|
|
8349
8648
|
return { buildAndRunRequestPipeline, processResponse };
|
|
8350
8649
|
}
|
|
8650
|
+
function formatAdjacencyRepairIds(toolUses, toolResults) {
|
|
8651
|
+
const ids = [.../* @__PURE__ */ new Set([...toolUses, ...toolResults])].slice(0, 8);
|
|
8652
|
+
return ids.length > 0 ? `; affected tool ids: ${ids.join(", ")}${toolUses.length + toolResults.length > ids.length ? ", \u2026" : ""}` : "";
|
|
8653
|
+
}
|
|
8351
8654
|
|
|
8352
8655
|
// src/utils/sage-output-block.ts
|
|
8353
8656
|
var SAGE_INJECTOR_HEADINGS = /* @__PURE__ */ new Set([
|
|
@@ -8919,7 +9222,7 @@ var Agent = class {
|
|
|
8919
9222
|
this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
8920
9223
|
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
8921
9224
|
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
8922
|
-
this.autoExtendLimit = init.autoExtendLimit ??
|
|
9225
|
+
this.autoExtendLimit = init.autoExtendLimit ?? false;
|
|
8923
9226
|
this.loopDetection = resolveLoopDetection(init.loopDetection);
|
|
8924
9227
|
this.autonomousContinue = init.autonomousContinue ?? false;
|
|
8925
9228
|
this.refreshSystemPrompt = init.refreshSystemPrompt ?? false;
|
|
@@ -9286,9 +9589,6 @@ function resolveContinuation(input) {
|
|
|
9286
9589
|
return { source: "open", text, label: "\u25B6 Continue \u2192 (no pending task \u2014 choosing next step)" };
|
|
9287
9590
|
}
|
|
9288
9591
|
|
|
9289
|
-
// src/core/fallback-model.ts
|
|
9290
|
-
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9291
|
-
|
|
9292
9592
|
// src/core/model-availability-calendar.ts
|
|
9293
9593
|
function logicalCalendarTarget(providerId, model) {
|
|
9294
9594
|
if (providerId !== "omniroute") return { providerId, model };
|
|
@@ -9776,7 +10076,206 @@ function dedupeChain(entries, current) {
|
|
|
9776
10076
|
);
|
|
9777
10077
|
}
|
|
9778
10078
|
|
|
10079
|
+
// src/core/fallback-doctor.ts
|
|
10080
|
+
function diagnoseFallbackConfig(config, tracker, opts) {
|
|
10081
|
+
const mgr = new FallbackProfileManager(config, { statusTracker: tracker });
|
|
10082
|
+
const primary = { providerId: config.provider, model: config.model };
|
|
10083
|
+
const activeProfile = mgr.activeProfileName();
|
|
10084
|
+
const explicitChain = Object.freeze([...config.fallbackModels ?? []]);
|
|
10085
|
+
const effectiveCandidates = mgr.resolveCandidates(primary, {
|
|
10086
|
+
fallbackProfile: activeProfile
|
|
10087
|
+
});
|
|
10088
|
+
const effectiveOrder = Object.freeze(
|
|
10089
|
+
effectiveCandidates.map((c) => `${c.providerId}/${c.model}`)
|
|
10090
|
+
);
|
|
10091
|
+
const autoEnabled = config.fallbackAuto !== false;
|
|
10092
|
+
const warnings = [];
|
|
10093
|
+
const providersInChain = /* @__PURE__ */ new Set();
|
|
10094
|
+
for (const entry of effectiveCandidates) {
|
|
10095
|
+
providersInChain.add(entry.providerId);
|
|
10096
|
+
}
|
|
10097
|
+
if (effectiveCandidates.length === 0) {
|
|
10098
|
+
warnings.push({
|
|
10099
|
+
code: "EMPTY_CHAIN",
|
|
10100
|
+
severity: "critical",
|
|
10101
|
+
message: "The effective fallback chain is empty. Any rate limit or outage on the primary model will immediately fail without recovery.",
|
|
10102
|
+
recommendation: "Add fallback models using /fallback add <provider/model> or enable smart defaults with /fallback auto on."
|
|
10103
|
+
});
|
|
10104
|
+
}
|
|
10105
|
+
if (effectiveCandidates.length > 0) {
|
|
10106
|
+
const onlyPrimaryProvider = providersInChain.size === 1 && providersInChain.has(primary.providerId);
|
|
10107
|
+
if (onlyPrimaryProvider) {
|
|
10108
|
+
warnings.push({
|
|
10109
|
+
code: "SIBLING_QUARANTINE_RISK",
|
|
10110
|
+
severity: "warning",
|
|
10111
|
+
message: `All fallback models belong to the same provider ("${primary.providerId}"). An account-level quota or token exhaustion will quarantine all sibling models at once.`,
|
|
10112
|
+
recommendation: "Add at least one cross-provider model (e.g. OpenAI, Anthropic, or Google) to your fallback profile."
|
|
10113
|
+
});
|
|
10114
|
+
}
|
|
10115
|
+
}
|
|
10116
|
+
for (const entry of effectiveCandidates) {
|
|
10117
|
+
const health = mgr.checkProvider(entry.providerId);
|
|
10118
|
+
if (!health.usable) {
|
|
10119
|
+
warnings.push({
|
|
10120
|
+
code: "PROVIDER_UNUSABLE",
|
|
10121
|
+
severity: "critical",
|
|
10122
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
10123
|
+
message: `Provider "${entry.providerId}" is missing an API key or baseUrl in configuration.`,
|
|
10124
|
+
recommendation: `Configure API credentials for "${entry.providerId}" or remove it from the fallback chain.`
|
|
10125
|
+
});
|
|
10126
|
+
}
|
|
10127
|
+
if (tracker && !tracker.isAvailable(entry.providerId, entry.model)) {
|
|
10128
|
+
const status2 = tracker.getStatus(entry.providerId, entry.model);
|
|
10129
|
+
const remainingMs = status2?.stateExpiresAt ? Math.max(0, status2.stateExpiresAt - Date.now()) : 0;
|
|
10130
|
+
const remainingSec = Math.round(remainingMs / 1e3);
|
|
10131
|
+
warnings.push({
|
|
10132
|
+
code: "MODEL_QUARANTINED",
|
|
10133
|
+
severity: "warning",
|
|
10134
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
10135
|
+
message: `Model is currently in quarantine (waiting room) due to ${status2?.lastErrorKind ?? "failures"}.${remainingSec > 0 ? ` Resets in ${remainingSec}s.` : ""}`,
|
|
10136
|
+
recommendation: "Model will be automatically re-admitted once cooldown/reset expires."
|
|
10137
|
+
});
|
|
10138
|
+
}
|
|
10139
|
+
const cal = evaluateModelCalendar(
|
|
10140
|
+
config.modelAvailabilitySchedule,
|
|
10141
|
+
entry.providerId,
|
|
10142
|
+
entry.model
|
|
10143
|
+
);
|
|
10144
|
+
if (!cal.allowed) {
|
|
10145
|
+
warnings.push({
|
|
10146
|
+
code: "CALENDAR_BLOCKED",
|
|
10147
|
+
severity: "warning",
|
|
10148
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
10149
|
+
message: `Model is blocked by the model availability calendar (${cal.rule?.label ?? "schedule restriction"}).`
|
|
10150
|
+
});
|
|
10151
|
+
}
|
|
10152
|
+
if (opts?.modelContextLimitMap) {
|
|
10153
|
+
const primaryLimit = opts.modelContextLimitMap[`${primary.providerId}/${primary.model}`] ?? opts.modelContextLimitMap[primary.model];
|
|
10154
|
+
const entryLimit = opts.modelContextLimitMap[`${entry.providerId}/${entry.model}`] ?? opts.modelContextLimitMap[entry.model];
|
|
10155
|
+
if (typeof primaryLimit === "number" && typeof entryLimit === "number" && entryLimit > 0 && primaryLimit > 0 && entryLimit < primaryLimit) {
|
|
10156
|
+
warnings.push({
|
|
10157
|
+
code: "CONTEXT_WINDOW_WARNING",
|
|
10158
|
+
severity: "warning",
|
|
10159
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
10160
|
+
message: `Context window downgrade: primary model has ${primaryLimit.toLocaleString()} tokens capacity, but fallback candidate has only ${entryLimit.toLocaleString()} tokens. Long sessions will fail failover or require aggressive compaction.`,
|
|
10161
|
+
recommendation: `Ensure fallback models have equal or greater context capacity (>= ${primaryLimit.toLocaleString()} tokens) or keep sessions below ${entryLimit.toLocaleString()} tokens.`
|
|
10162
|
+
});
|
|
10163
|
+
}
|
|
10164
|
+
}
|
|
10165
|
+
}
|
|
10166
|
+
const uniqueProviders = Object.freeze([...providersInChain]);
|
|
10167
|
+
const crossProviderCount = uniqueProviders.filter((p) => p !== primary.providerId).length;
|
|
10168
|
+
const hasCritical = warnings.some((w) => w.severity === "critical");
|
|
10169
|
+
const hasWarning = warnings.some((w) => w.severity === "warning");
|
|
10170
|
+
const status = hasCritical ? "critical" : hasWarning ? "warning" : "healthy";
|
|
10171
|
+
return Object.freeze({
|
|
10172
|
+
primary: Object.freeze(primary),
|
|
10173
|
+
activeProfile,
|
|
10174
|
+
explicitChain,
|
|
10175
|
+
effectiveOrder,
|
|
10176
|
+
autoEnabled,
|
|
10177
|
+
status,
|
|
10178
|
+
warnings: Object.freeze(warnings),
|
|
10179
|
+
crossProviderCount,
|
|
10180
|
+
uniqueProviders
|
|
10181
|
+
});
|
|
10182
|
+
}
|
|
10183
|
+
function simulateFallbackFailover(config, opts = {}) {
|
|
10184
|
+
const errorKind = opts.errorKind ?? "rate_limit";
|
|
10185
|
+
const isEligible = isFallbackWorthy(errorKind);
|
|
10186
|
+
const primary = { providerId: config.provider, model: config.model };
|
|
10187
|
+
if (!isEligible) {
|
|
10188
|
+
return Object.freeze({
|
|
10189
|
+
triggeringError: errorKind,
|
|
10190
|
+
isFallbackEligible: false,
|
|
10191
|
+
primary: Object.freeze(primary),
|
|
10192
|
+
steps: Object.freeze([]),
|
|
10193
|
+
finalTarget: null,
|
|
10194
|
+
summary: `Error kind "${errorKind}" is not fallback-worthy (request-shaped error, e.g. context_overflow/auth). The agent loop will not attempt fallback.`
|
|
10195
|
+
});
|
|
10196
|
+
}
|
|
10197
|
+
const mgr = new FallbackProfileManager(config, { statusTracker: opts.statusTracker });
|
|
10198
|
+
const candidates = mgr.resolveCandidates(primary);
|
|
10199
|
+
const steps = [];
|
|
10200
|
+
let finalTarget = null;
|
|
10201
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
10202
|
+
const entry = candidates[i];
|
|
10203
|
+
const key = `${entry.providerId}/${entry.model}`;
|
|
10204
|
+
const providerSwitched = entry.providerId !== primary.providerId;
|
|
10205
|
+
const cal = evaluateModelCalendar(
|
|
10206
|
+
config.modelAvailabilitySchedule,
|
|
10207
|
+
entry.providerId,
|
|
10208
|
+
entry.model
|
|
10209
|
+
);
|
|
10210
|
+
if (!cal.allowed) {
|
|
10211
|
+
steps.push({
|
|
10212
|
+
index: i + 1,
|
|
10213
|
+
providerId: entry.providerId,
|
|
10214
|
+
model: entry.model,
|
|
10215
|
+
skipped: true,
|
|
10216
|
+
reason: `Blocked by model availability calendar (${cal.rule?.label ?? "schedule"})`,
|
|
10217
|
+
providerSwitched
|
|
10218
|
+
});
|
|
10219
|
+
continue;
|
|
10220
|
+
}
|
|
10221
|
+
if (opts.statusTracker && !opts.statusTracker.isAvailable(entry.providerId, entry.model)) {
|
|
10222
|
+
steps.push({
|
|
10223
|
+
index: i + 1,
|
|
10224
|
+
providerId: entry.providerId,
|
|
10225
|
+
model: entry.model,
|
|
10226
|
+
skipped: true,
|
|
10227
|
+
reason: "Quarantined in status tracker waiting room",
|
|
10228
|
+
providerSwitched
|
|
10229
|
+
});
|
|
10230
|
+
continue;
|
|
10231
|
+
}
|
|
10232
|
+
const contextLimit = opts.modelContextLimitMap?.[key];
|
|
10233
|
+
if (typeof opts.currentTokens === "number" && opts.currentTokens > 0 && typeof contextLimit === "number" && contextLimit > 0 && opts.currentTokens > contextLimit) {
|
|
10234
|
+
steps.push({
|
|
10235
|
+
index: i + 1,
|
|
10236
|
+
providerId: entry.providerId,
|
|
10237
|
+
model: entry.model,
|
|
10238
|
+
skipped: true,
|
|
10239
|
+
reason: `Context pre-filter: request tokens (${opts.currentTokens}) exceeds model window (${contextLimit})`,
|
|
10240
|
+
providerSwitched
|
|
10241
|
+
});
|
|
10242
|
+
continue;
|
|
10243
|
+
}
|
|
10244
|
+
const health = mgr.checkProvider(entry.providerId);
|
|
10245
|
+
if (!health.usable) {
|
|
10246
|
+
steps.push({
|
|
10247
|
+
index: i + 1,
|
|
10248
|
+
providerId: entry.providerId,
|
|
10249
|
+
model: entry.model,
|
|
10250
|
+
skipped: true,
|
|
10251
|
+
reason: `Cannot construct provider "${entry.providerId}" (missing API key/endpoint)`,
|
|
10252
|
+
providerSwitched
|
|
10253
|
+
});
|
|
10254
|
+
continue;
|
|
10255
|
+
}
|
|
10256
|
+
steps.push({
|
|
10257
|
+
index: i + 1,
|
|
10258
|
+
providerId: entry.providerId,
|
|
10259
|
+
model: entry.model,
|
|
10260
|
+
skipped: false,
|
|
10261
|
+
providerSwitched
|
|
10262
|
+
});
|
|
10263
|
+
finalTarget = { providerId: entry.providerId, model: entry.model };
|
|
10264
|
+
break;
|
|
10265
|
+
}
|
|
10266
|
+
const summary = finalTarget ? `Failover succeeded: will rotate from ${primary.providerId}/${primary.model} to ${finalTarget.providerId}/${finalTarget.model}.` : "Failover failed: no usable fallback candidate could be reached.";
|
|
10267
|
+
return Object.freeze({
|
|
10268
|
+
triggeringError: errorKind,
|
|
10269
|
+
isFallbackEligible: true,
|
|
10270
|
+
primary: Object.freeze(primary),
|
|
10271
|
+
steps: Object.freeze(steps),
|
|
10272
|
+
finalTarget: finalTarget ? Object.freeze(finalTarget) : null,
|
|
10273
|
+
summary
|
|
10274
|
+
});
|
|
10275
|
+
}
|
|
10276
|
+
|
|
9779
10277
|
// src/core/fallback-model.ts
|
|
10278
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9780
10279
|
function fallbackProfileChain(config, profileName) {
|
|
9781
10280
|
if (!profileName) return [];
|
|
9782
10281
|
const mgr = new FallbackProfileManager(config);
|
|
@@ -9911,7 +10410,25 @@ function createFallbackModelExtension(deps) {
|
|
|
9911
10410
|
if (!evaluateModelCalendar(cfg.modelAvailabilitySchedule, primary.providerId, primary.model).allowed || deps.statusTracker && !deps.statusTracker.isAvailable(primary.providerId, primary.model))
|
|
9912
10411
|
return;
|
|
9913
10412
|
try {
|
|
9914
|
-
|
|
10413
|
+
const primaryProvider = await deps.buildProvider(primary.providerId, primary.model);
|
|
10414
|
+
const currentTokens = ctx.lastRequestTokens;
|
|
10415
|
+
const maxContext = maxContextOf(primaryProvider);
|
|
10416
|
+
if (maxContext > 0 && typeof currentTokens === "number" && currentTokens > maxContext) {
|
|
10417
|
+
deps.events.emit("provider.primary_probe_context_blocked", {
|
|
10418
|
+
sessionId: resolveEventSessionId(ctx),
|
|
10419
|
+
providerId: primary.providerId,
|
|
10420
|
+
model: primary.model,
|
|
10421
|
+
currentTokens,
|
|
10422
|
+
maxContext,
|
|
10423
|
+
timestamp: now()
|
|
10424
|
+
});
|
|
10425
|
+
deps.logger?.warn(
|
|
10426
|
+
`fallback-model: deferring primary probe "${primary.providerId}/${primary.model}" \u2014 context (${currentTokens}) exceeds target window (${maxContext})`
|
|
10427
|
+
);
|
|
10428
|
+
markPrimaryFailure(cfg);
|
|
10429
|
+
return;
|
|
10430
|
+
}
|
|
10431
|
+
ctx.provider = primaryProvider;
|
|
9915
10432
|
ctx.model = primary.model;
|
|
9916
10433
|
await deps.onModelSwitch?.(primary.providerId, primary.model);
|
|
9917
10434
|
primaryBlockedUntil = 0;
|
|
@@ -10025,9 +10542,10 @@ function createFallbackModelExtension(deps) {
|
|
|
10025
10542
|
const status = shouldFallback(firstErr_);
|
|
10026
10543
|
if (status === null) throw firstErr_;
|
|
10027
10544
|
let gateRequestId;
|
|
10028
|
-
|
|
10545
|
+
const configuredGateSeconds = cfg.fallbackGateSeconds ?? deps.fallbackGateSeconds;
|
|
10546
|
+
if (configuredGateSeconds !== 0 && deps.fallbackGate && usableChain.length > 0) {
|
|
10029
10547
|
gateRequestId = randomUUID6();
|
|
10030
|
-
const autoSwitchSeconds = Math.max(1,
|
|
10548
|
+
const autoSwitchSeconds = Math.max(1, configuredGateSeconds ?? 7);
|
|
10031
10549
|
const gateCandidates = usableChain.map((e) => ({
|
|
10032
10550
|
providerId: e.providerId,
|
|
10033
10551
|
model: e.model
|
|
@@ -10270,8 +10788,8 @@ var InputBuilder = class {
|
|
|
10270
10788
|
async registerFile(input) {
|
|
10271
10789
|
const ref = await this.store.add({ ...input, kind: "file" });
|
|
10272
10790
|
this.refs.push(ref);
|
|
10273
|
-
const
|
|
10274
|
-
return `[file:${
|
|
10791
|
+
const path16 = ref.meta.filename ?? ref.meta.label ?? String(ref.seq);
|
|
10792
|
+
return `[file:${path16}]`;
|
|
10275
10793
|
}
|
|
10276
10794
|
/**
|
|
10277
10795
|
* Whether `appendPaste(text)` would collapse the text to a placeholder
|
|
@@ -10318,8 +10836,8 @@ function paragraphLabel(text) {
|
|
|
10318
10836
|
|
|
10319
10837
|
// src/core/instruction-bundle.ts
|
|
10320
10838
|
import { statSync as statSync2 } from "node:fs";
|
|
10321
|
-
import * as
|
|
10322
|
-
import * as
|
|
10839
|
+
import * as fs6 from "node:fs/promises";
|
|
10840
|
+
import * as path12 from "node:path";
|
|
10323
10841
|
import { fileURLToPath as fileURLToPath2 } from "node:url";
|
|
10324
10842
|
async function loadInstructionBundle(paths) {
|
|
10325
10843
|
let bundle = {};
|
|
@@ -10375,17 +10893,17 @@ function resolveSystemInstructionFile(paths) {
|
|
|
10375
10893
|
}
|
|
10376
10894
|
function sanitizeSystemInstructionFile(file) {
|
|
10377
10895
|
const trimmed = file.trim();
|
|
10378
|
-
if (trimmed.length === 0 || trimmed !==
|
|
10896
|
+
if (trimmed.length === 0 || trimmed !== path12.basename(trimmed) || path12.extname(trimmed).toLowerCase() !== ".md") {
|
|
10379
10897
|
throw new Error(`Invalid system instruction file: ${file}`);
|
|
10380
10898
|
}
|
|
10381
10899
|
return trimmed;
|
|
10382
10900
|
}
|
|
10383
10901
|
async function readInstructionDir(dir, options) {
|
|
10384
10902
|
const [json, identity, leaderAfterTask, sections] = await Promise.all([
|
|
10385
|
-
readInstructionJson(
|
|
10386
|
-
readOptionalText(
|
|
10387
|
-
readOptionalText(
|
|
10388
|
-
readSections(
|
|
10903
|
+
readInstructionJson(path12.join(dir, "instructions.json")),
|
|
10904
|
+
readOptionalText(path12.join(dir, options.systemFile)),
|
|
10905
|
+
readOptionalText(path12.join(dir, "leader-after-task.md")),
|
|
10906
|
+
readSections(path12.join(dir, "sections"))
|
|
10389
10907
|
]);
|
|
10390
10908
|
const fromMarkdown = {
|
|
10391
10909
|
system: {
|
|
@@ -10404,13 +10922,13 @@ async function readSections(root) {
|
|
|
10404
10922
|
async function readSectionsInto(root, dir, out) {
|
|
10405
10923
|
let entries;
|
|
10406
10924
|
try {
|
|
10407
|
-
entries = await
|
|
10925
|
+
entries = await fs6.readdir(dir, { withFileTypes: true });
|
|
10408
10926
|
} catch {
|
|
10409
10927
|
return;
|
|
10410
10928
|
}
|
|
10411
10929
|
await Promise.all(
|
|
10412
10930
|
entries.map(async (entry) => {
|
|
10413
|
-
const file =
|
|
10931
|
+
const file = path12.join(dir, entry.name);
|
|
10414
10932
|
if (entry.isDirectory()) {
|
|
10415
10933
|
await readSectionsInto(root, file, out);
|
|
10416
10934
|
return;
|
|
@@ -10418,7 +10936,7 @@ async function readSectionsInto(root, dir, out) {
|
|
|
10418
10936
|
if (!entry.isFile() || !entry.name.endsWith(".md")) return;
|
|
10419
10937
|
const text = await readOptionalText(file);
|
|
10420
10938
|
if (text === void 0) return;
|
|
10421
|
-
const rel =
|
|
10939
|
+
const rel = path12.relative(root, file).replace(/\\/g, "/").replace(/\.md$/i, "");
|
|
10422
10940
|
const key = rel.split("/").join(".").replace(/-/g, ".");
|
|
10423
10941
|
out[key] = text;
|
|
10424
10942
|
})
|
|
@@ -10427,7 +10945,7 @@ async function readSectionsInto(root, dir, out) {
|
|
|
10427
10945
|
async function readInstructionJson(file) {
|
|
10428
10946
|
let raw;
|
|
10429
10947
|
try {
|
|
10430
|
-
raw = await
|
|
10948
|
+
raw = await fs6.readFile(file, "utf8");
|
|
10431
10949
|
} catch {
|
|
10432
10950
|
return {};
|
|
10433
10951
|
}
|
|
@@ -10459,18 +10977,18 @@ function normalizeInstructionBundle(value) {
|
|
|
10459
10977
|
}
|
|
10460
10978
|
async function readOptionalText(file) {
|
|
10461
10979
|
try {
|
|
10462
|
-
const text = await
|
|
10980
|
+
const text = await fs6.readFile(file, "utf8");
|
|
10463
10981
|
return text.trimEnd();
|
|
10464
10982
|
} catch {
|
|
10465
10983
|
return void 0;
|
|
10466
10984
|
}
|
|
10467
10985
|
}
|
|
10468
10986
|
function defaultBundledInstructionDir() {
|
|
10469
|
-
const here =
|
|
10987
|
+
const here = path12.dirname(fileURLToPath2(import.meta.url));
|
|
10470
10988
|
return firstExistingDirSync([
|
|
10471
|
-
|
|
10472
|
-
|
|
10473
|
-
|
|
10989
|
+
path12.resolve(here, "../../instructions"),
|
|
10990
|
+
path12.resolve(here, "../instructions"),
|
|
10991
|
+
path12.resolve(here, "instructions")
|
|
10474
10992
|
]);
|
|
10475
10993
|
}
|
|
10476
10994
|
function definedPick(obj, keys) {
|
|
@@ -10524,13 +11042,13 @@ function flattenSystemPromptRegions(regions) {
|
|
|
10524
11042
|
|
|
10525
11043
|
// src/core/modes/default.ts
|
|
10526
11044
|
import { readFileSync as readFileSync6, statSync as statSync3 } from "node:fs";
|
|
10527
|
-
import * as
|
|
11045
|
+
import * as path13 from "node:path";
|
|
10528
11046
|
import { fileURLToPath as fileURLToPath3 } from "node:url";
|
|
10529
11047
|
var PROMPT = readBundledInstructionFile("system.md");
|
|
10530
11048
|
var LEADER_AFTER_TASK_PROMPT = readBundledInstructionFile("leader-after-task.md");
|
|
10531
11049
|
function readBundledInstructionFile(name) {
|
|
10532
11050
|
for (const dir of bundledInstructionDirCandidates()) {
|
|
10533
|
-
const file =
|
|
11051
|
+
const file = path13.join(dir, name);
|
|
10534
11052
|
try {
|
|
10535
11053
|
return readFileSync6(file, "utf8").trimEnd();
|
|
10536
11054
|
} catch {
|
|
@@ -10539,12 +11057,12 @@ function readBundledInstructionFile(name) {
|
|
|
10539
11057
|
return "";
|
|
10540
11058
|
}
|
|
10541
11059
|
function bundledInstructionDirCandidates() {
|
|
10542
|
-
const here =
|
|
11060
|
+
const here = path13.dirname(fileURLToPath3(import.meta.url));
|
|
10543
11061
|
const candidates = [
|
|
10544
|
-
|
|
10545
|
-
|
|
10546
|
-
|
|
10547
|
-
|
|
11062
|
+
path13.resolve(here, "../../../instructions"),
|
|
11063
|
+
path13.resolve(here, "../../instructions"),
|
|
11064
|
+
path13.resolve(here, "../instructions"),
|
|
11065
|
+
path13.resolve(here, "instructions")
|
|
10548
11066
|
];
|
|
10549
11067
|
return candidates.sort((a, b) => Number(!isDirectory(a)) - Number(!isDirectory(b)));
|
|
10550
11068
|
}
|
|
@@ -10558,12 +11076,12 @@ function isDirectory(candidate) {
|
|
|
10558
11076
|
|
|
10559
11077
|
// src/core/system-prompt-environment.ts
|
|
10560
11078
|
import * as os3 from "node:os";
|
|
10561
|
-
import * as
|
|
11079
|
+
import * as path15 from "node:path";
|
|
10562
11080
|
|
|
10563
11081
|
// src/core/system-prompt-environment-probes.ts
|
|
10564
11082
|
import { spawn as spawn2 } from "node:child_process";
|
|
10565
|
-
import * as
|
|
10566
|
-
import * as
|
|
11083
|
+
import * as fs7 from "node:fs/promises";
|
|
11084
|
+
import * as path14 from "node:path";
|
|
10567
11085
|
|
|
10568
11086
|
// src/utils/child-env.ts
|
|
10569
11087
|
var ALLOWED_KEYS = /* @__PURE__ */ new Set([
|
|
@@ -10704,7 +11222,7 @@ function buildChildEnv(optsOrSessionId) {
|
|
|
10704
11222
|
// src/core/system-prompt-environment-probes.ts
|
|
10705
11223
|
async function dirExists(p) {
|
|
10706
11224
|
try {
|
|
10707
|
-
const stat3 = await
|
|
11225
|
+
const stat3 = await fs7.stat(p);
|
|
10708
11226
|
return stat3.isDirectory();
|
|
10709
11227
|
} catch {
|
|
10710
11228
|
return false;
|
|
@@ -10791,7 +11309,7 @@ async function detectLanguages(root) {
|
|
|
10791
11309
|
const hits = await Promise.all(
|
|
10792
11310
|
checks.map(async ([marker, lang]) => {
|
|
10793
11311
|
try {
|
|
10794
|
-
await
|
|
11312
|
+
await fs7.access(path14.join(root, marker));
|
|
10795
11313
|
return lang;
|
|
10796
11314
|
} catch {
|
|
10797
11315
|
return null;
|
|
@@ -10854,6 +11372,7 @@ function shellGuidanceBlock(shell, detail) {
|
|
|
10854
11372
|
var MAX_ENVIRONMENT_CACHE_ENTRIES = 16;
|
|
10855
11373
|
async function buildEnvironment(ctx, env) {
|
|
10856
11374
|
const modelCapabilities = env.modelCapabilities;
|
|
11375
|
+
const today = env.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10857
11376
|
const cacheKey = [
|
|
10858
11377
|
ctx.projectRoot,
|
|
10859
11378
|
ctx.provider ?? "",
|
|
@@ -10862,7 +11381,9 @@ async function buildEnvironment(ctx, env) {
|
|
|
10862
11381
|
modelCapabilities?.supportsTools ? 1 : 0,
|
|
10863
11382
|
modelCapabilities?.supportsVision ? 1 : 0,
|
|
10864
11383
|
modelCapabilities?.supportsReasoning ? 1 : 0,
|
|
10865
|
-
env.skillCache ?? ""
|
|
11384
|
+
env.skillCache ?? "",
|
|
11385
|
+
today,
|
|
11386
|
+
env.modeId ?? ""
|
|
10866
11387
|
].join("\0");
|
|
10867
11388
|
const cached = env.envCacheByRoot.get(cacheKey);
|
|
10868
11389
|
if (cached) {
|
|
@@ -10870,12 +11391,11 @@ async function buildEnvironment(ctx, env) {
|
|
|
10870
11391
|
env.envCacheByRoot.set(cacheKey, cached);
|
|
10871
11392
|
return cached;
|
|
10872
11393
|
}
|
|
10873
|
-
const today = env.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10874
11394
|
const platform2 = `${os3.platform()} ${os3.release()}`;
|
|
10875
11395
|
const effShell = effectiveShell(os3.platform(), process.env["WRONGSTACK_SHELL"]);
|
|
10876
11396
|
const shell = effShell === "posix" ? process.env.SHELL ?? process.env.ComSpec ?? "unknown" : SHELL_DISPLAY[effShell];
|
|
10877
11397
|
const node = process.version;
|
|
10878
|
-
const isGit = await dirExists(
|
|
11398
|
+
const isGit = await dirExists(path15.join(ctx.projectRoot, ".git"));
|
|
10879
11399
|
const [git, langs] = await Promise.all([
|
|
10880
11400
|
isGit ? gitStatus(ctx.projectRoot) : Promise.resolve("not a git repo"),
|
|
10881
11401
|
detectLanguages(ctx.projectRoot)
|
|
@@ -11351,16 +11871,16 @@ ${skillBodyCache}`);
|
|
|
11351
11871
|
}
|
|
11352
11872
|
|
|
11353
11873
|
// src/core/system-prompt-plan.ts
|
|
11354
|
-
import * as
|
|
11874
|
+
import * as fs8 from "node:fs/promises";
|
|
11355
11875
|
async function readActivePlanBlock(args) {
|
|
11356
11876
|
const { planPath, cache } = args;
|
|
11357
11877
|
if (!planPath) return { text: "", cache };
|
|
11358
11878
|
try {
|
|
11359
|
-
const stat3 = await
|
|
11879
|
+
const stat3 = await fs8.stat(planPath);
|
|
11360
11880
|
if (cache && cache.path === planPath && cache.mtimeMs === stat3.mtimeMs) {
|
|
11361
11881
|
return { text: cache.text, cache };
|
|
11362
11882
|
}
|
|
11363
|
-
const raw = await
|
|
11883
|
+
const raw = await fs8.readFile(planPath, "utf8");
|
|
11364
11884
|
const text = formatActivePlan(raw);
|
|
11365
11885
|
return { text, cache: { path: planPath, mtimeMs: stat3.mtimeMs, text } };
|
|
11366
11886
|
} catch {
|
|
@@ -11716,7 +12236,9 @@ ${peers}
|
|
|
11716
12236
|
const tpl = tplCtx ?? this.templateContext(ctx);
|
|
11717
12237
|
const section = (key, vars = {}) => instructionSection(instructions, key, vars, tpl);
|
|
11718
12238
|
const tier = this.tier;
|
|
11719
|
-
|
|
12239
|
+
const subagent = ctx.subagent === true;
|
|
12240
|
+
const maxContextTokens = this.modelCapabilities()?.maxContextTokens ?? 0;
|
|
12241
|
+
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.tier === tier && this._toolsUsageCache?.subagent === subagent && this._toolsUsageCache?.maxContextTokens === maxContextTokens) {
|
|
11720
12242
|
return this._toolsUsageCache.text;
|
|
11721
12243
|
}
|
|
11722
12244
|
const byCat = /* @__PURE__ */ new Map();
|
|
@@ -11843,7 +12365,7 @@ ${hint.trim()}`);
|
|
|
11843
12365
|
}
|
|
11844
12366
|
}
|
|
11845
12367
|
const text = lines.join("\n");
|
|
11846
|
-
this._toolsUsageCache = { toolsRef: tools, tier, text };
|
|
12368
|
+
this._toolsUsageCache = { toolsRef: tools, tier, subagent, maxContextTokens, text };
|
|
11847
12369
|
return text;
|
|
11848
12370
|
}
|
|
11849
12371
|
renderOnlineAgents(agents) {
|
|
@@ -11906,6 +12428,7 @@ export {
|
|
|
11906
12428
|
createDefaultPipelines,
|
|
11907
12429
|
createFallbackModelExtension,
|
|
11908
12430
|
detectContinueIntent,
|
|
12431
|
+
diagnoseFallbackConfig,
|
|
11909
12432
|
effectiveFallbackChain,
|
|
11910
12433
|
fallbackProfileChain,
|
|
11911
12434
|
formatModelRef,
|
|
@@ -11924,6 +12447,7 @@ export {
|
|
|
11924
12447
|
runtimeFallbackChain,
|
|
11925
12448
|
setBtwNote,
|
|
11926
12449
|
setQueuedMessagesSnapshot,
|
|
12450
|
+
simulateFallbackFailover,
|
|
11927
12451
|
smartDefaultFallbackChain,
|
|
11928
12452
|
wrapAsState,
|
|
11929
12453
|
writePendingNextSteps
|