@wrongstack/core 0.306.3 → 0.307.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/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 +1738 -1690
- 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/index.js +6103 -5890
- 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/conversation-state.d.ts +1 -2
- package/dist/core/fallback-doctor.d.ts +72 -0
- package/dist/core/fallback-model.d.ts +19 -1
- package/dist/core/fallback-profile-manager.d.ts +21 -3
- package/dist/core/index.d.ts +2 -1
- package/dist/core/index.js +1143 -766
- package/dist/defaults/index.js +3833 -3595
- package/dist/execution/auto-compaction-middleware.d.ts +64 -0
- 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 -166
- 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.d.ts +1 -1
- package/dist/execution/index.js +10016 -9903
- package/dist/execution/prompt-enhancer.js +11 -3
- package/dist/execution/retry-policy.d.ts +27 -0
- 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 +58 -17
- package/dist/index.d.ts +2 -1
- package/dist/index.js +8731 -7756
- package/dist/infrastructure/index.js +24 -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 +2123 -1918
- 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/prompt-journal.d.ts +94 -0
- package/dist/security/index.js +677 -667
- 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 +270 -227
- package/dist/session-catalog/project-server.js +277 -234
- 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/cloud-config-sync/sanitize.d.ts +18 -0
- package/dist/storage/cloud-config-sync.d.ts +1 -1
- 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 +2121 -1736
- package/dist/storage/orphan-lock-cleaner.d.ts +15 -0
- package/dist/storage/provider-config-watcher.d.ts +9 -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/strict-empty-check.d.ts +7 -0
- package/dist/storage/session-store.d.ts +2 -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.d.ts +1 -1
- package/dist/tools/index.js +5150 -5090
- package/dist/types/config/root.d.ts +21 -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/types/session.d.ts +7 -0
- package/dist/worktree/worktree-manager.d.ts +1 -1
- package/instructions/coordination/subagent-baseline.md +2 -2
- package/instructions/llm/prompt-enhancer.md +2 -1
- package/instructions/sections/tool/common-patterns.md +21 -0
- package/instructions/sections/tool/delegation-compact.md +3 -1
- package/instructions/sections/tool/delegation-full.md +5 -1
- package/instructions/system-lite.md +28 -3
- package/instructions/system-pro.md +37 -8
- package/instructions/system.md +4 -2
- package/package.json +5 -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";
|
|
@@ -1722,6 +1724,24 @@ import { randomUUID } from "node:crypto";
|
|
|
1722
1724
|
import * as v8 from "node:v8";
|
|
1723
1725
|
|
|
1724
1726
|
// src/security/secret-scrubber.ts
|
|
1727
|
+
var JSON_CREDENTIAL_KEY_ANCHORS = [
|
|
1728
|
+
'Key"',
|
|
1729
|
+
'key"',
|
|
1730
|
+
'KEY"',
|
|
1731
|
+
'token"',
|
|
1732
|
+
'Token"',
|
|
1733
|
+
'TOKEN"',
|
|
1734
|
+
'secret"',
|
|
1735
|
+
'Secret"',
|
|
1736
|
+
'SECRET"',
|
|
1737
|
+
'password"',
|
|
1738
|
+
'Password"',
|
|
1739
|
+
'PASSWORD"',
|
|
1740
|
+
'authorization"',
|
|
1741
|
+
'Authorization"',
|
|
1742
|
+
'bearer"',
|
|
1743
|
+
'Bearer"'
|
|
1744
|
+
];
|
|
1725
1745
|
var PATTERNS = [
|
|
1726
1746
|
// Anchored at the start where possible so partial matches inside larger
|
|
1727
1747
|
// strings don't trigger false positives.
|
|
@@ -1824,6 +1844,30 @@ var PATTERNS = [
|
|
|
1824
1844
|
regex: /(^|\s)([A-Z_]{4,}(?:KEY|TOKEN|SECRET|PASSWORD|PWD))\s*[:=]\s*['"]?([A-Za-z0-9_/+=-]{20,512})['"]?(?=\s|$)/g,
|
|
1825
1845
|
anchor: ["KEY", "TOKEN", "SECRET", "PASSWORD", "PWD"]
|
|
1826
1846
|
},
|
|
1847
|
+
{
|
|
1848
|
+
type: "json_credential_key",
|
|
1849
|
+
// The JSON counterpart to `high_entropy_env`, and the pattern that the
|
|
1850
|
+
// now-deleted `JSON_KEY_ANCHORS` list was written for. Without it those
|
|
1851
|
+
// anchors only widened the cheap pre-scan — `hasCredentialAnchors` said
|
|
1852
|
+
// "this text may hold a secret", every pattern then declined to match, and
|
|
1853
|
+
// the value went out verbatim. `high_entropy_env` cannot cover these: it
|
|
1854
|
+
// requires an UPPERCASE unquoted key (`API_KEY=…`), so `{"apiKey":"…"}`
|
|
1855
|
+
// never matched.
|
|
1856
|
+
//
|
|
1857
|
+
// Tool results are routinely serialised as JSON, and a credential with no
|
|
1858
|
+
// recognisable prefix (Azure, self-hosted gateways, Anthropic/Codex OAuth)
|
|
1859
|
+
// has no other pattern that can catch it — this is the only thing standing
|
|
1860
|
+
// between such a value and the session JSONL, chronicle, HQ broadcast and
|
|
1861
|
+
// the model's own context.
|
|
1862
|
+
//
|
|
1863
|
+
// The key may carry a prefix (`"anthropicApiKey"`), but the credential word
|
|
1864
|
+
// must END the key: `"tokenCount"` and `"maxTokens"` do not match, because
|
|
1865
|
+
// the closing quote has to follow the word immediately.
|
|
1866
|
+
// Value floor of 8 chars keeps enum-ish values (`"authorization":"none"`)
|
|
1867
|
+
// out. Capture groups: 1=key + punctuation, 2=value, 3=closing quote.
|
|
1868
|
+
regex: /("[A-Za-z0-9_]*(?:apiKey|api_key|token|secret|password|authorization|bearer|private_key|access_token|refresh_token|client_secret)"\s*:\s*")([^"\\]{8,512})(")/gi,
|
|
1869
|
+
anchor: JSON_CREDENTIAL_KEY_ANCHORS
|
|
1870
|
+
},
|
|
1827
1871
|
// ── Ported from packages/plugins credential-patterns.ts (WS-034) ─────────
|
|
1828
1872
|
// The plugin runtime carried 37 patterns while this scrubber — the one that
|
|
1829
1873
|
// guards session JSONL, chronicle, HQ broadcast, WebUI events and the auth
|
|
@@ -1906,9 +1950,12 @@ var PATTERNS = [
|
|
|
1906
1950
|
anchor: "GOCSPX-"
|
|
1907
1951
|
}
|
|
1908
1952
|
];
|
|
1909
|
-
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1953
|
+
var SIMPLE_PATTERNS = PATTERNS.filter(
|
|
1954
|
+
(p) => p.type !== "high_entropy_env" && p.type !== "json_credential_key"
|
|
1955
|
+
);
|
|
1910
1956
|
var COMBINED_REGEX = new RegExp(SIMPLE_PATTERNS.map((p) => `(${p.regex.source})`).join("|"), "g");
|
|
1911
1957
|
var HIGH_ENTROPY_REGEX = PATTERNS.find((p) => p.type === "high_entropy_env").regex;
|
|
1958
|
+
var JSON_CREDENTIAL_REGEX = PATTERNS.find((p) => p.type === "json_credential_key").regex;
|
|
1912
1959
|
var COMBINED_REPLACEMENTS = SIMPLE_PATTERNS.map((p) => `[REDACTED:${p.type}]`);
|
|
1913
1960
|
var SCRUB_CHUNK_BYTES = 64 * 1024;
|
|
1914
1961
|
var SCRUB_OVERLAP_BYTES = 1024;
|
|
@@ -1919,20 +1966,7 @@ var PATTERN_ANCHORS = [
|
|
|
1919
1966
|
)
|
|
1920
1967
|
)
|
|
1921
1968
|
];
|
|
1922
|
-
var
|
|
1923
|
-
'"apiKey"',
|
|
1924
|
-
'"api_key"',
|
|
1925
|
-
'"token"',
|
|
1926
|
-
'"secret"',
|
|
1927
|
-
'"password"',
|
|
1928
|
-
'"authorization"',
|
|
1929
|
-
'"bearer"',
|
|
1930
|
-
'"private_key"',
|
|
1931
|
-
'"access_token"',
|
|
1932
|
-
'"refresh_token"',
|
|
1933
|
-
'"client_secret"'
|
|
1934
|
-
];
|
|
1935
|
-
var ALL_ANCHORS = [...PATTERN_ANCHORS, ...JSON_KEY_ANCHORS];
|
|
1969
|
+
var ALL_ANCHORS = PATTERN_ANCHORS;
|
|
1936
1970
|
function hasCredentialAnchors(text) {
|
|
1937
1971
|
for (const anchor of ALL_ANCHORS) {
|
|
1938
1972
|
if (text.includes(anchor)) return true;
|
|
@@ -1981,6 +2015,9 @@ var DefaultSecretScrubber = class {
|
|
|
1981
2015
|
out = out.replace(HIGH_ENTROPY_REGEX, (_match, lead, key, _value) => {
|
|
1982
2016
|
return `${lead}${key}=[REDACTED:high_entropy_env]`;
|
|
1983
2017
|
});
|
|
2018
|
+
out = out.replace(JSON_CREDENTIAL_REGEX, (_match, keyPrefix, _value, closingQuote) => {
|
|
2019
|
+
return `${keyPrefix}[REDACTED:json_credential_key]${closingQuote}`;
|
|
2020
|
+
});
|
|
1984
2021
|
return out;
|
|
1985
2022
|
}
|
|
1986
2023
|
/**
|
|
@@ -4669,6 +4706,7 @@ function getCalibrationState(calibrationKey = CALIBRATION_GLOBAL_KEY) {
|
|
|
4669
4706
|
}
|
|
4670
4707
|
|
|
4671
4708
|
// src/core/context.ts
|
|
4709
|
+
import { realpathSync } from "node:fs";
|
|
4672
4710
|
import * as path10 from "node:path";
|
|
4673
4711
|
|
|
4674
4712
|
// src/core/conversation-state.ts
|
|
@@ -4744,12 +4782,15 @@ var ConversationState = class {
|
|
|
4744
4782
|
* cap determines the starting index for the sum but does not gate it.
|
|
4745
4783
|
*/
|
|
4746
4784
|
overflowCount(arr) {
|
|
4747
|
-
|
|
4748
|
-
|
|
4785
|
+
const contextClass = this.ctx.constructor;
|
|
4786
|
+
const maxMessages = contextClass.MAX_MESSAGES;
|
|
4787
|
+
const maxMessageTokens = contextClass.MAX_MESSAGE_TOKENS;
|
|
4788
|
+
let drop = maxMessages > 0 ? Math.max(0, arr.length - maxMessages) : 0;
|
|
4789
|
+
if (maxMessageTokens <= 0) return this.protocolSafeDropCount(arr, drop);
|
|
4749
4790
|
let total = 0;
|
|
4750
4791
|
for (let i = drop; i < arr.length; i++) total += arr[i]?._estTokens ?? 0;
|
|
4751
|
-
if (total <=
|
|
4752
|
-
while (drop < arr.length - 1 && total >
|
|
4792
|
+
if (total <= maxMessageTokens) return this.protocolSafeDropCount(arr, drop);
|
|
4793
|
+
while (drop < arr.length - 1 && total > maxMessageTokens) {
|
|
4753
4794
|
total -= arr[drop]?._estTokens ?? 0;
|
|
4754
4795
|
drop++;
|
|
4755
4796
|
}
|
|
@@ -5575,6 +5616,19 @@ var Context = class _Context {
|
|
|
5575
5616
|
if (rel.startsWith("..") || path10.isAbsolute(rel)) {
|
|
5576
5617
|
throw new Error(`Working directory "${resolved}" is outside project root "${root}"`);
|
|
5577
5618
|
}
|
|
5619
|
+
let realTarget = resolved;
|
|
5620
|
+
let realRoot = root;
|
|
5621
|
+
try {
|
|
5622
|
+
realTarget = realpathSync.native(resolved);
|
|
5623
|
+
realRoot = realpathSync.native(root);
|
|
5624
|
+
} catch {
|
|
5625
|
+
}
|
|
5626
|
+
const realRel = path10.relative(realRoot, realTarget);
|
|
5627
|
+
if (realRel.startsWith("..") || path10.isAbsolute(realRel)) {
|
|
5628
|
+
throw new Error(
|
|
5629
|
+
`Working directory "${resolved}" resolves to "${realTarget}", outside project root "${realRoot}"`
|
|
5630
|
+
);
|
|
5631
|
+
}
|
|
5578
5632
|
}
|
|
5579
5633
|
const old = this.workingDir;
|
|
5580
5634
|
this.workingDir = resolved;
|
|
@@ -5609,237 +5663,651 @@ function resolveEventSessionId(ctx) {
|
|
|
5609
5663
|
return session?.id ?? "";
|
|
5610
5664
|
}
|
|
5611
5665
|
|
|
5612
|
-
// src/core/
|
|
5613
|
-
function
|
|
5614
|
-
|
|
5615
|
-
|
|
5616
|
-
let
|
|
5617
|
-
let
|
|
5618
|
-
|
|
5619
|
-
|
|
5620
|
-
|
|
5621
|
-
|
|
5622
|
-
|
|
5623
|
-
|
|
5624
|
-
|
|
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;
|
|
5625
5690
|
}
|
|
5626
|
-
|
|
5627
|
-
}
|
|
5628
|
-
|
|
5629
|
-
|
|
5630
|
-
|
|
5631
|
-
|
|
5632
|
-
|
|
5633
|
-
|
|
5634
|
-
|
|
5635
|
-
|
|
5636
|
-
|
|
5637
|
-
|
|
5638
|
-
|
|
5639
|
-
|
|
5640
|
-
|
|
5641
|
-
|
|
5642
|
-
|
|
5643
|
-
|
|
5644
|
-
|
|
5645
|
-
|
|
5646
|
-
|
|
5647
|
-
|
|
5648
|
-
|
|
5649
|
-
|
|
5650
|
-
|
|
5651
|
-
|
|
5652
|
-
|
|
5653
|
-
|
|
5654
|
-
|
|
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
|
+
});
|
|
5655
5726
|
};
|
|
5656
|
-
const
|
|
5657
|
-
if (
|
|
5658
|
-
|
|
5659
|
-
|
|
5660
|
-
|
|
5727
|
+
const emitNativeAfterRouteChange = () => {
|
|
5728
|
+
if (routeChanged && currentEffective > 0) {
|
|
5729
|
+
a.ctx.meta["effectiveMaxContext"] = currentEffective;
|
|
5730
|
+
a.ctx.meta["effectiveMaxContextSource"] = "configured";
|
|
5731
|
+
emitEffectiveLimit(currentEffective, "configured");
|
|
5661
5732
|
}
|
|
5662
5733
|
};
|
|
5663
|
-
|
|
5664
|
-
|
|
5665
|
-
|
|
5666
|
-
|
|
5667
|
-
grant,
|
|
5668
|
-
deny
|
|
5669
|
-
});
|
|
5670
|
-
if (autoExtend) {
|
|
5671
|
-
setImmediate(() => {
|
|
5672
|
-
if (!resolved) {
|
|
5673
|
-
resolved = true;
|
|
5674
|
-
clearTimeout(timer);
|
|
5675
|
-
resolve10(100);
|
|
5676
|
-
}
|
|
5677
|
-
});
|
|
5734
|
+
const refresh = provider.refreshContextLimit;
|
|
5735
|
+
if (opts.probe === false || !refresh) {
|
|
5736
|
+
emitNativeAfterRouteChange();
|
|
5737
|
+
return;
|
|
5678
5738
|
}
|
|
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
|
-
|
|
5707
|
-
|
|
5708
|
-
|
|
5709
|
-
|
|
5710
|
-
}
|
|
5711
|
-
|
|
5712
|
-
|
|
5713
|
-
|
|
5714
|
-
|
|
5715
|
-
const lines = steps.map((step, i) => {
|
|
5716
|
-
const auto = i === 0 && step.auto === true ? ' auto="true"' : "";
|
|
5717
|
-
return `${i + 1}. ${step.text}${auto}`;
|
|
5718
|
-
});
|
|
5719
|
-
return `<nextsteps>
|
|
5720
|
-
${lines.join("\n")}
|
|
5721
|
-
</nextsteps>`;
|
|
5722
|
-
}
|
|
5723
|
-
function endsTurn(stopReason) {
|
|
5724
|
-
return stopReason !== "tool_use" && stopReason !== "tool_call";
|
|
5725
|
-
}
|
|
5726
|
-
function maybeAppendPendingNextSteps(ctx, res) {
|
|
5727
|
-
if (ctx.agentId !== "leader") return res;
|
|
5728
|
-
if (!endsTurn(res.stopReason)) return res;
|
|
5729
|
-
if (ctx.signal.aborted) return res;
|
|
5730
|
-
const steps = readPendingNextSteps(ctx);
|
|
5731
|
-
if (!steps) return res;
|
|
5732
|
-
if (hasOpenTodos(ctx.todos)) {
|
|
5733
|
-
clearPendingNextSteps(ctx);
|
|
5734
|
-
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";
|
|
5770
|
+
}
|
|
5771
|
+
a.ctx.meta["effectiveMaxContext"] = effective;
|
|
5772
|
+
a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
|
|
5773
|
+
a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
|
|
5774
|
+
emitEffectiveLimit(effective, effectiveSource);
|
|
5735
5775
|
}
|
|
5736
|
-
|
|
5737
|
-
|
|
5738
|
-
const
|
|
5739
|
-
if (
|
|
5740
|
-
|
|
5741
|
-
return res;
|
|
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;
|
|
5742
5781
|
}
|
|
5782
|
+
const breakdown = estimateRequestTokens([], sysRef, toolsRef ?? [], calibrationKey());
|
|
5783
|
+
_cachedSysRef = sysRef;
|
|
5784
|
+
_cachedToolsRef = toolsRef;
|
|
5785
|
+
_cachedOverheadTokens = breakdown.systemPrompt + breakdown.tools;
|
|
5786
|
+
return _cachedOverheadTokens;
|
|
5743
5787
|
}
|
|
5744
|
-
|
|
5745
|
-
|
|
5746
|
-
|
|
5747
|
-
|
|
5748
|
-
|
|
5749
|
-
|
|
5750
|
-
|
|
5751
|
-
|
|
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;
|
|
5752
5808
|
}
|
|
5753
|
-
|
|
5754
|
-
|
|
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;
|
|
5819
|
+
}
|
|
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;
|
|
5828
|
+
}
|
|
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;
|
|
5838
|
+
}
|
|
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;
|
|
5859
|
+
}
|
|
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 };
|
|
5882
|
+
}
|
|
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
|
+
});
|
|
5924
|
+
}
|
|
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
|
+
};
|
|
5755
5938
|
}
|
|
5756
5939
|
|
|
5757
|
-
// src/core/
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
5762
|
-
|
|
5763
|
-
|
|
5764
|
-
|
|
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");
|
|
5765
5954
|
}
|
|
5766
|
-
function
|
|
5767
|
-
|
|
5768
|
-
|
|
5769
|
-
|
|
5770
|
-
|
|
5771
|
-
|
|
5772
|
-
|
|
5773
|
-
|
|
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;
|
|
5965
|
+
}
|
|
5966
|
+
return value;
|
|
5967
|
+
}
|
|
5968
|
+
function hashSmall(s) {
|
|
5969
|
+
let h = 2166136261;
|
|
5774
5970
|
for (let i = 0; i < s.length; i++) {
|
|
5775
|
-
|
|
5776
|
-
|
|
5777
|
-
|
|
5778
|
-
|
|
5779
|
-
|
|
5780
|
-
|
|
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;
|
|
5781
5997
|
}
|
|
5782
|
-
|
|
5783
|
-
|
|
5784
|
-
|
|
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 };
|
|
5785
6022
|
}
|
|
5786
|
-
if (
|
|
5787
|
-
|
|
5788
|
-
|
|
5789
|
-
|
|
5790
|
-
|
|
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
|
+
);
|
|
5791
6039
|
}
|
|
5792
|
-
if (ch === "{") stringBraceDepth++;
|
|
5793
|
-
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
5794
|
-
continue;
|
|
5795
|
-
}
|
|
5796
|
-
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
5797
|
-
contentEnd = i + 1;
|
|
5798
|
-
if (ch === '"') {
|
|
5799
|
-
inString = true;
|
|
5800
|
-
sawKey = true;
|
|
5801
|
-
stringBraceDepth = 0;
|
|
5802
|
-
prevSig = '"';
|
|
5803
|
-
} else if (ch === "{" || ch === "[") {
|
|
5804
|
-
stack.push(ch);
|
|
5805
|
-
prevSig = ch;
|
|
5806
|
-
} else if (ch === "}" || ch === "]") {
|
|
5807
|
-
stack.pop();
|
|
5808
|
-
prevSig = ch;
|
|
5809
6040
|
} else {
|
|
5810
|
-
|
|
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
|
+
}
|
|
5811
6075
|
}
|
|
6076
|
+
return { cut: false };
|
|
5812
6077
|
}
|
|
5813
|
-
|
|
5814
|
-
|
|
5815
|
-
|
|
5816
|
-
|
|
5817
|
-
|
|
5818
|
-
|
|
5819
|
-
|
|
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";
|
|
5820
6092
|
}
|
|
5821
|
-
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
5822
|
-
result += '"';
|
|
5823
|
-
} else if (prevSig === ":") {
|
|
5824
|
-
result += "null";
|
|
5825
|
-
}
|
|
5826
|
-
for (let k = stack.length - 1; k >= 0; k--) {
|
|
5827
|
-
result += stack[k] === "{" ? "}" : "]";
|
|
5828
|
-
}
|
|
5829
|
-
if (!tryParse(result).ok) {
|
|
5830
|
-
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
5831
|
-
if (tryParse(patched).ok) result = patched;
|
|
5832
6093
|
}
|
|
5833
|
-
return
|
|
6094
|
+
return lastDirective;
|
|
5834
6095
|
}
|
|
5835
|
-
var
|
|
5836
|
-
|
|
5837
|
-
|
|
5838
|
-
|
|
5839
|
-
|
|
5840
|
-
|
|
5841
|
-
|
|
5842
|
-
|
|
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;
|
|
6102
|
+
}
|
|
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
|
|
6137
|
+
});
|
|
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;
|
|
6165
|
+
}
|
|
6166
|
+
ctx.meta[SLOT_KEY] = cleaned;
|
|
6167
|
+
}
|
|
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;
|
|
6175
|
+
}
|
|
6176
|
+
function clearPendingNextSteps(ctx) {
|
|
6177
|
+
delete ctx.meta[SLOT_KEY];
|
|
6178
|
+
}
|
|
6179
|
+
function hasNextStepsTag(text) {
|
|
6180
|
+
return NEXT_STEPS_TAG_RE.test(text);
|
|
6181
|
+
}
|
|
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>`;
|
|
6190
|
+
}
|
|
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/core/streaming-response-builder.ts
|
|
6226
|
+
import { randomUUID as randomUUID4 } from "node:crypto";
|
|
6227
|
+
|
|
6228
|
+
// src/utils/json-repair.ts
|
|
6229
|
+
function completePartialObject(s) {
|
|
6230
|
+
if (!s.trim().startsWith("{")) return s;
|
|
6231
|
+
if (tryParse(s).ok) return s;
|
|
6232
|
+
return repairTruncated(s);
|
|
6233
|
+
}
|
|
6234
|
+
function repairTruncated(s) {
|
|
6235
|
+
const stack = [];
|
|
6236
|
+
let inString = false;
|
|
6237
|
+
let escaped = false;
|
|
6238
|
+
let sawKey = false;
|
|
6239
|
+
let prevSig = "";
|
|
6240
|
+
let contentEnd = 0;
|
|
6241
|
+
let stringBraceDepth = 0;
|
|
6242
|
+
for (let i = 0; i < s.length; i++) {
|
|
6243
|
+
const ch = expectDefined(s[i]);
|
|
6244
|
+
if (inString) {
|
|
6245
|
+
contentEnd = i + 1;
|
|
6246
|
+
if (escaped) {
|
|
6247
|
+
escaped = false;
|
|
6248
|
+
continue;
|
|
6249
|
+
}
|
|
6250
|
+
if (ch === "\\") {
|
|
6251
|
+
escaped = true;
|
|
6252
|
+
continue;
|
|
6253
|
+
}
|
|
6254
|
+
if (ch === '"') {
|
|
6255
|
+
inString = false;
|
|
6256
|
+
prevSig = '"';
|
|
6257
|
+
stringBraceDepth = 0;
|
|
6258
|
+
continue;
|
|
6259
|
+
}
|
|
6260
|
+
if (ch === "{") stringBraceDepth++;
|
|
6261
|
+
else if (ch === "}" && stringBraceDepth > 0) stringBraceDepth--;
|
|
6262
|
+
continue;
|
|
6263
|
+
}
|
|
6264
|
+
if (ch === " " || ch === " " || ch === "\n" || ch === "\r") continue;
|
|
6265
|
+
contentEnd = i + 1;
|
|
6266
|
+
if (ch === '"') {
|
|
6267
|
+
inString = true;
|
|
6268
|
+
sawKey = true;
|
|
6269
|
+
stringBraceDepth = 0;
|
|
6270
|
+
prevSig = '"';
|
|
6271
|
+
} else if (ch === "{" || ch === "[") {
|
|
6272
|
+
stack.push(ch);
|
|
6273
|
+
prevSig = ch;
|
|
6274
|
+
} else if (ch === "}" || ch === "]") {
|
|
6275
|
+
stack.pop();
|
|
6276
|
+
prevSig = ch;
|
|
6277
|
+
} else {
|
|
6278
|
+
prevSig = ch;
|
|
6279
|
+
}
|
|
6280
|
+
}
|
|
6281
|
+
if (!sawKey && !inString) return s;
|
|
6282
|
+
let result = s.slice(0, contentEnd);
|
|
6283
|
+
if (inString) {
|
|
6284
|
+
if (escaped) {
|
|
6285
|
+
result = result.slice(0, -1);
|
|
6286
|
+
} else if (endsWithInvalidEscape(result)) {
|
|
6287
|
+
result = result.slice(0, -2);
|
|
6288
|
+
}
|
|
6289
|
+
if (stringBraceDepth > 0) result += "}".repeat(stringBraceDepth);
|
|
6290
|
+
result += '"';
|
|
6291
|
+
} else if (prevSig === ":") {
|
|
6292
|
+
result += "null";
|
|
6293
|
+
}
|
|
6294
|
+
for (let k = stack.length - 1; k >= 0; k--) {
|
|
6295
|
+
result += stack[k] === "{" ? "}" : "]";
|
|
6296
|
+
}
|
|
6297
|
+
if (!tryParse(result).ok) {
|
|
6298
|
+
const patched = result.replace(/:(\s*)([}\]])/g, ":null$2");
|
|
6299
|
+
if (tryParse(patched).ok) result = patched;
|
|
6300
|
+
}
|
|
6301
|
+
return result;
|
|
6302
|
+
}
|
|
6303
|
+
var VALID_ESCAPE = /* @__PURE__ */ new Set(['"', "\\", "/", "b", "f", "n", "r", "t", "u"]);
|
|
6304
|
+
function endsWithInvalidEscape(str) {
|
|
6305
|
+
const last = str[str.length - 1];
|
|
6306
|
+
if (str[str.length - 2] !== "\\" || last === void 0) return false;
|
|
6307
|
+
if (VALID_ESCAPE.has(last)) return false;
|
|
6308
|
+
let backslashes = 0;
|
|
6309
|
+
for (let k = str.length - 2; k >= 0 && str[k] === "\\"; k--) backslashes++;
|
|
6310
|
+
return backslashes % 2 === 1;
|
|
5843
6311
|
}
|
|
5844
6312
|
function tryParse(s) {
|
|
5845
6313
|
try {
|
|
@@ -6493,397 +6961,106 @@ async function runProviderWithRetry(opts) {
|
|
|
6493
6961
|
return;
|
|
6494
6962
|
}
|
|
6495
6963
|
signal.addEventListener("abort", onAbort);
|
|
6496
|
-
});
|
|
6497
|
-
attempt++;
|
|
6498
|
-
}
|
|
6499
|
-
}
|
|
6500
|
-
}
|
|
6501
|
-
function elapsedMs(startedNs) {
|
|
6502
|
-
return Number(process.hrtime.bigint() - startedNs) / 1e6;
|
|
6503
|
-
}
|
|
6504
|
-
|
|
6505
|
-
// src/core/queued-messages.ts
|
|
6506
|
-
var META_KEY3 = "_queuedMessagesAwareness";
|
|
6507
|
-
var MAX_ITEMS = 20;
|
|
6508
|
-
var MAX_PREVIEW_CHARS = 500;
|
|
6509
|
-
function read(ctx) {
|
|
6510
|
-
const raw = ctx.meta[META_KEY3];
|
|
6511
|
-
if (!raw || typeof raw !== "object") return void 0;
|
|
6512
|
-
const s = raw;
|
|
6513
|
-
return Array.isArray(s.items) && Array.isArray(s.seen) ? s : void 0;
|
|
6514
|
-
}
|
|
6515
|
-
function sameList(a, b) {
|
|
6516
|
-
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
6517
|
-
}
|
|
6518
|
-
function toPreview(text) {
|
|
6519
|
-
const trimmed = text.trim();
|
|
6520
|
-
if (trimmed.length <= MAX_PREVIEW_CHARS) return trimmed;
|
|
6521
|
-
return `${trimmed.slice(0, MAX_PREVIEW_CHARS - 1)}\u2026`;
|
|
6522
|
-
}
|
|
6523
|
-
function setQueuedMessagesSnapshot(ctx, texts) {
|
|
6524
|
-
const items = texts.map(toPreview).filter((t2) => t2.length > 0).slice(0, MAX_ITEMS);
|
|
6525
|
-
const prev = read(ctx);
|
|
6526
|
-
if (items.length === 0 && (!prev || prev.seen.length === 0)) {
|
|
6527
|
-
if (prev) delete ctx.meta[META_KEY3];
|
|
6528
|
-
return;
|
|
6529
|
-
}
|
|
6530
|
-
ctx.meta[META_KEY3] = { items, seen: prev?.seen ?? [] };
|
|
6531
|
-
}
|
|
6532
|
-
function consumeQueuedMessagesUpdate(ctx) {
|
|
6533
|
-
const s = read(ctx);
|
|
6534
|
-
if (!s) return null;
|
|
6535
|
-
if (sameList(s.items, s.seen)) return null;
|
|
6536
|
-
if (s.items.length === 0) {
|
|
6537
|
-
delete ctx.meta[META_KEY3];
|
|
6538
|
-
return null;
|
|
6539
|
-
}
|
|
6540
|
-
ctx.meta[META_KEY3] = { items: s.items, seen: s.items };
|
|
6541
|
-
return s.items;
|
|
6542
|
-
}
|
|
6543
|
-
function buildQueuedMessagesBlock(items) {
|
|
6544
|
-
const body = items.map((t2, i) => `${i + 1}. ${t2}`).join("\n");
|
|
6545
|
-
return [
|
|
6546
|
-
"[QUEUED MESSAGES \u2014 the user typed these while you were working. They are",
|
|
6547
|
-
"waiting in the input queue and each will be delivered as its own turn",
|
|
6548
|
-
"after you finish, so do NOT answer or act on them as new tasks now. Read",
|
|
6549
|
-
"them as a heads-up: if one changes what you should be doing right now",
|
|
6550
|
-
"(new constraint, invalidated approach), adapt your current work; if one",
|
|
6551
|
-
"is just an FYI, fold it in. The queue may still change before delivery.",
|
|
6552
|
-
"This notice replaces any earlier one \u2014 the queue now holds exactly:",
|
|
6553
|
-
"",
|
|
6554
|
-
body,
|
|
6555
|
-
"]"
|
|
6556
|
-
].join("\n");
|
|
6557
|
-
}
|
|
6558
|
-
|
|
6559
|
-
// src/core/request-provider-binding.ts
|
|
6560
|
-
var requestProviders = /* @__PURE__ */ new WeakMap();
|
|
6561
|
-
function bindRequestProvider(request, provider) {
|
|
6562
|
-
requestProviders.set(request, provider);
|
|
6563
|
-
}
|
|
6564
|
-
function providerBoundToRequest(request) {
|
|
6565
|
-
return requestProviders.get(request);
|
|
6566
|
-
}
|
|
6567
|
-
|
|
6568
|
-
// src/core/agent-loop.ts
|
|
6569
|
-
function toError(err) {
|
|
6570
|
-
return err instanceof Error ? err : new Error(String(err));
|
|
6571
|
-
}
|
|
6572
|
-
function signalAbortReason(signal) {
|
|
6573
|
-
const r = signal.reason;
|
|
6574
|
-
if (r instanceof Error) return r.message || r.name;
|
|
6575
|
-
if (typeof r === "string" && r.length > 0) return r;
|
|
6576
|
-
return "aborted";
|
|
6577
|
-
}
|
|
6578
|
-
function createAgentLoopHandler(a, handlers) {
|
|
6579
|
-
const checkMailbox = attachMailboxChecker(a);
|
|
6580
|
-
const fleetPulseCfg = (() => {
|
|
6581
|
-
try {
|
|
6582
|
-
return typeof a.container?.has === "function" && a.container.has(TOKENS.ConfigStore) ? a.container.resolve(TOKENS.ConfigStore).get().fleet?.pulse : void 0;
|
|
6583
|
-
} catch {
|
|
6584
|
-
return void 0;
|
|
6585
|
-
}
|
|
6586
|
-
})();
|
|
6587
|
-
const getFleetPulse = attachFleetPulse(a, fleetPulseCfg);
|
|
6588
|
-
const pulseEveryN = Math.max(1, fleetPulseCfg?.everyNIterations ?? 5);
|
|
6589
|
-
const backgroundCoordination = () => a.ctx.meta["coordinationContextMode"] === "background";
|
|
6590
|
-
async function refreshProviderContextLimit(provider, model, opts = {}) {
|
|
6591
|
-
a.ctx.meta ??= {};
|
|
6592
|
-
const routeKey = `${provider.id}/${model}`;
|
|
6593
|
-
const previousRouteKey = a.ctx.meta["contextLimitRouteKey"];
|
|
6594
|
-
const routeChanged = previousRouteKey !== void 0 && previousRouteKey !== routeKey;
|
|
6595
|
-
const previousMaxContext = currentMaxContext();
|
|
6596
|
-
if (routeChanged) {
|
|
6597
|
-
delete a.ctx.meta["providerOverflowMaxContext"];
|
|
6598
|
-
delete a.ctx.meta["contextLimitBaseline"];
|
|
6599
|
-
delete a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
6600
|
-
delete a.ctx.meta["effectiveMaxContext"];
|
|
6601
|
-
delete a.ctx.meta["effectiveMaxContextSource"];
|
|
6602
|
-
delete a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
6603
|
-
delete a.ctx.meta["contextLimitLastEmittedSource"];
|
|
6604
|
-
}
|
|
6605
|
-
a.ctx.meta["contextLimitRouteKey"] = routeKey;
|
|
6606
|
-
const routeMeta = a.ctx.meta["effectiveMaxContext"];
|
|
6607
|
-
const routeProviderCap = provider.capabilities.maxContext;
|
|
6608
|
-
const currentEffective = typeof routeMeta === "number" && routeMeta > 0 ? routeMeta : typeof routeProviderCap === "number" && routeProviderCap > 0 ? routeProviderCap : 2e5;
|
|
6609
|
-
const emitEffectiveLimit = (effective2, source) => {
|
|
6610
|
-
const lastEmitted = a.ctx.meta["contextLimitLastEmittedMaxContext"];
|
|
6611
|
-
const lastEmittedSource = a.ctx.meta["contextLimitLastEmittedSource"];
|
|
6612
|
-
if (lastEmitted === effective2 && lastEmittedSource === source) return;
|
|
6613
|
-
const eventPrevious = typeof lastEmitted === "number" && Number.isFinite(lastEmitted) && lastEmitted > 0 ? Math.floor(lastEmitted) : previousMaxContext;
|
|
6614
|
-
a.ctx.meta["contextLimitLastEmittedMaxContext"] = effective2;
|
|
6615
|
-
a.ctx.meta["contextLimitLastEmittedSource"] = source;
|
|
6616
|
-
a.events.emit("ctx.max_context", {
|
|
6617
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
6618
|
-
providerId: provider.id,
|
|
6619
|
-
modelId: model,
|
|
6620
|
-
maxContext: effective2,
|
|
6621
|
-
...eventPrevious > 0 ? { previousMaxContext: eventPrevious } : {},
|
|
6622
|
-
source,
|
|
6623
|
-
decreased: eventPrevious > 0 && effective2 < eventPrevious
|
|
6624
|
-
});
|
|
6625
|
-
};
|
|
6626
|
-
const emitNativeAfterRouteChange = () => {
|
|
6627
|
-
if (routeChanged && currentEffective > 0) {
|
|
6628
|
-
a.ctx.meta["effectiveMaxContext"] = currentEffective;
|
|
6629
|
-
a.ctx.meta["effectiveMaxContextSource"] = "configured";
|
|
6630
|
-
emitEffectiveLimit(currentEffective, "configured");
|
|
6631
|
-
}
|
|
6632
|
-
};
|
|
6633
|
-
const refresh = provider.refreshContextLimit;
|
|
6634
|
-
if (opts.probe === false || !refresh) {
|
|
6635
|
-
emitNativeAfterRouteChange();
|
|
6636
|
-
return;
|
|
6637
|
-
}
|
|
6638
|
-
let discovered;
|
|
6639
|
-
try {
|
|
6640
|
-
discovered = await refresh.call(provider, model, { signal: a.ctx.signal });
|
|
6641
|
-
} catch {
|
|
6642
|
-
emitNativeAfterRouteChange();
|
|
6643
|
-
return;
|
|
6644
|
-
}
|
|
6645
|
-
const providerLimit = discovered?.maxContext;
|
|
6646
|
-
if (typeof providerLimit !== "number" || !Number.isFinite(providerLimit) || providerLimit <= 0) {
|
|
6647
|
-
emitNativeAfterRouteChange();
|
|
6648
|
-
return;
|
|
6649
|
-
}
|
|
6650
|
-
const providerLimitFloored = Math.floor(providerLimit);
|
|
6651
|
-
const existingBaseline = a.ctx.meta["contextLimitBaseline"];
|
|
6652
|
-
const lastApplied = a.ctx.meta["providerLimitEffectiveMaxContext"];
|
|
6653
|
-
const existingOverflow = a.ctx.meta["providerOverflowMaxContext"];
|
|
6654
|
-
const effectiveChangedOutsideProbe = typeof lastApplied === "number" && Number.isFinite(lastApplied) && currentEffective !== Math.floor(lastApplied) && currentEffective !== existingOverflow;
|
|
6655
|
-
const baseline = effectiveChangedOutsideProbe || typeof existingBaseline !== "number" || !Number.isFinite(existingBaseline) || existingBaseline <= 0 ? currentEffective : Math.floor(existingBaseline);
|
|
6656
|
-
a.ctx.meta["contextLimitBaseline"] = baseline;
|
|
6657
|
-
const overflowLimit = a.ctx.meta["providerOverflowMaxContext"];
|
|
6658
|
-
const overflowFloored = typeof overflowLimit === "number" && Number.isFinite(overflowLimit) && overflowLimit > 0 ? Math.floor(overflowLimit) : void 0;
|
|
6659
|
-
const ceilings = [baseline, providerLimitFloored];
|
|
6660
|
-
if (overflowFloored !== void 0) ceilings.push(overflowFloored);
|
|
6661
|
-
const effective = Math.min(...ceilings.filter((limit) => limit > 0));
|
|
6662
|
-
let effectiveSource;
|
|
6663
|
-
if (overflowFloored !== void 0 && effective === overflowFloored) {
|
|
6664
|
-
effectiveSource = "provider_overflow";
|
|
6665
|
-
} else if (effective === providerLimitFloored) {
|
|
6666
|
-
effectiveSource = "provider";
|
|
6667
|
-
} else {
|
|
6668
|
-
effectiveSource = "configured";
|
|
6669
|
-
}
|
|
6670
|
-
a.ctx.meta["effectiveMaxContext"] = effective;
|
|
6671
|
-
a.ctx.meta["providerLimitEffectiveMaxContext"] = effective;
|
|
6672
|
-
a.ctx.meta["effectiveMaxContextSource"] = effectiveSource;
|
|
6673
|
-
emitEffectiveLimit(effective, effectiveSource);
|
|
6674
|
-
}
|
|
6675
|
-
async function compactContextIfNeeded() {
|
|
6676
|
-
const msgCount = a.ctx.messages.length;
|
|
6677
|
-
const maxContext = currentMaxContext();
|
|
6678
|
-
const revision = a.ctx.state.revision;
|
|
6679
|
-
const toolsRef = a.ctx.tools;
|
|
6680
|
-
const systemRef = a.ctx.systemPrompt;
|
|
6681
|
-
const requestTokens = a.ctx.lastRequestTokens;
|
|
6682
|
-
if (_lastCompactionMsgCount === msgCount && _lastCompactionRevision === revision && _lastCompactionToolsRef === toolsRef && _lastCompactionSystemRef === systemRef && _lastCompactionRequestTokens === requestTokens && _lastCompactionWasNoop && _lastCompactionMaxContext === maxContext && maxContext > 0) {
|
|
6683
|
-
return false;
|
|
6684
|
-
}
|
|
6685
|
-
const beforeMessages = a.ctx.messages;
|
|
6686
|
-
const beforeMsgCount = a.ctx.messages.length;
|
|
6687
|
-
const beforeRevision = a.ctx.state.revision;
|
|
6688
|
-
await a.pipelines.contextWindow.run(a.ctx);
|
|
6689
|
-
_lastCompactionMsgCount = a.ctx.messages.length;
|
|
6690
|
-
_lastCompactionRevision = a.ctx.state.revision;
|
|
6691
|
-
_lastCompactionToolsRef = a.ctx.tools;
|
|
6692
|
-
_lastCompactionSystemRef = a.ctx.systemPrompt;
|
|
6693
|
-
_lastCompactionMaxContext = maxContext;
|
|
6694
|
-
const changed = a.ctx.state.revision !== beforeRevision || a.ctx.messages !== beforeMessages || a.ctx.messages.length !== beforeMsgCount;
|
|
6695
|
-
const tokens = refreshContextRequestTokenStash({ force: changed });
|
|
6696
|
-
_lastCompactionRequestTokens = tokens;
|
|
6697
|
-
const load = maxContext > 0 ? tokens / maxContext : 0;
|
|
6698
|
-
_lastCompactionWasNoop = tokens > 0 && load < 0.5;
|
|
6699
|
-
if (changed) {
|
|
6700
|
-
_lastEmittedMsgCount = -1;
|
|
6701
|
-
_lastEmittedToolCount = -1;
|
|
6702
|
-
_lastEmittedMaxContext = -1;
|
|
6703
|
-
}
|
|
6704
|
-
return changed;
|
|
6705
|
-
}
|
|
6706
|
-
const calibrationKey = (model = a.ctx.model) => `${a.ctx.provider?.id ?? "unknown"}/${model}`;
|
|
6707
|
-
let _cachedSysRef = null;
|
|
6708
|
-
let _cachedToolsRef = null;
|
|
6709
|
-
let _cachedOverheadTokens = 0;
|
|
6710
|
-
function systemAndToolsOverhead() {
|
|
6711
|
-
const sysRef = a.ctx.systemPrompt;
|
|
6712
|
-
const toolsRef = a.ctx.tools;
|
|
6713
|
-
if (sysRef === _cachedSysRef && toolsRef === _cachedToolsRef && _cachedOverheadTokens > 0) {
|
|
6714
|
-
return _cachedOverheadTokens;
|
|
6715
|
-
}
|
|
6716
|
-
const breakdown = estimateRequestTokens([], sysRef, toolsRef ?? [], calibrationKey());
|
|
6717
|
-
_cachedSysRef = sysRef;
|
|
6718
|
-
_cachedToolsRef = toolsRef;
|
|
6719
|
-
_cachedOverheadTokens = breakdown.systemPrompt + breakdown.tools;
|
|
6720
|
-
return _cachedOverheadTokens;
|
|
6721
|
-
}
|
|
6722
|
-
function stashRequestTokens(req) {
|
|
6723
|
-
const preFlight = estimateRequestTokens(
|
|
6724
|
-
req.messages,
|
|
6725
|
-
req.system,
|
|
6726
|
-
req.tools ?? [],
|
|
6727
|
-
calibrationKey(req.model)
|
|
6728
|
-
);
|
|
6729
|
-
a.ctx.lastRequestTokens = preFlight.total;
|
|
6730
|
-
_lastPreFlightMsgCount = req.messages.length;
|
|
6731
|
-
_lastPreFlightToolCount = (req.tools ?? []).length;
|
|
6732
|
-
_lastPreFlightRevision = a.ctx.state.revision;
|
|
6733
|
-
_cachedSysRef = req.system;
|
|
6734
|
-
_cachedToolsRef = req.tools ?? [];
|
|
6735
|
-
_cachedOverheadTokens = preFlight.systemPrompt + preFlight.tools;
|
|
6736
|
-
a.ctx.meta["lastRequestTokensAt"] = {
|
|
6737
|
-
msgCount: req.messages.length,
|
|
6738
|
-
toolCount: (req.tools ?? []).length,
|
|
6739
|
-
revision: a.ctx.state.revision
|
|
6740
|
-
};
|
|
6741
|
-
return preFlight;
|
|
6742
|
-
}
|
|
6743
|
-
function refreshContextRequestTokenStash(opts = {}) {
|
|
6744
|
-
const msgCount = a.ctx.messages.length;
|
|
6745
|
-
const toolCount = (a.ctx.tools ?? []).length;
|
|
6746
|
-
const revision = a.ctx.state.revision;
|
|
6747
|
-
const stashed = a.ctx.lastRequestTokens;
|
|
6748
|
-
const stashedAt = a.ctx.meta?.["lastRequestTokensAt"];
|
|
6749
|
-
if (!opts.force && typeof stashed === "number" && stashed > 0 && typeof stashedAt === "object" && stashedAt !== null) {
|
|
6750
|
-
const meta = stashedAt;
|
|
6751
|
-
if (meta.msgCount === msgCount && meta.toolCount === toolCount && meta.revision === revision && _lastPreFlightRevision === revision) {
|
|
6752
|
-
return stashed;
|
|
6753
|
-
}
|
|
6754
|
-
}
|
|
6755
|
-
const refreshed = estimateMessageTokens(a.ctx.messages) + systemAndToolsOverhead();
|
|
6756
|
-
a.ctx.lastRequestTokens = refreshed;
|
|
6757
|
-
_lastPreFlightMsgCount = msgCount;
|
|
6758
|
-
_lastPreFlightToolCount = toolCount;
|
|
6759
|
-
_lastPreFlightRevision = revision;
|
|
6760
|
-
a.ctx.meta["lastRequestTokensAt"] = { msgCount, toolCount, revision };
|
|
6761
|
-
return refreshed;
|
|
6762
|
-
}
|
|
6763
|
-
async function buildRequestWithPreflightCompaction(opts) {
|
|
6764
|
-
let prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
6765
|
-
let req = prepared.request;
|
|
6766
|
-
let preFlight = stashRequestTokens(req);
|
|
6767
|
-
let probedRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
6768
|
-
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
6769
|
-
if (await compactContextIfNeeded()) {
|
|
6770
|
-
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
6771
|
-
req = prepared.request;
|
|
6772
|
-
preFlight = stashRequestTokens(req);
|
|
6773
|
-
const rebuiltRouteKey = `${prepared.provider.id}/${req.model}`;
|
|
6774
|
-
if (rebuiltRouteKey !== probedRouteKey) {
|
|
6775
|
-
probedRouteKey = rebuiltRouteKey;
|
|
6776
|
-
await refreshProviderContextLimit(prepared.provider, req.model);
|
|
6777
|
-
if (await compactContextIfNeeded()) {
|
|
6778
|
-
prepared = await handlers.response.buildAndRunRequestPipeline(opts);
|
|
6779
|
-
req = prepared.request;
|
|
6780
|
-
preFlight = stashRequestTokens(req);
|
|
6781
|
-
}
|
|
6782
|
-
}
|
|
6964
|
+
});
|
|
6965
|
+
attempt++;
|
|
6783
6966
|
}
|
|
6784
|
-
return { req, provider: prepared.provider, preFlight };
|
|
6785
6967
|
}
|
|
6786
|
-
|
|
6787
|
-
|
|
6788
|
-
|
|
6789
|
-
|
|
6790
|
-
|
|
6791
|
-
|
|
6792
|
-
|
|
6793
|
-
|
|
6794
|
-
|
|
6795
|
-
|
|
6796
|
-
|
|
6797
|
-
|
|
6798
|
-
|
|
6799
|
-
|
|
6800
|
-
|
|
6801
|
-
|
|
6802
|
-
|
|
6803
|
-
|
|
6804
|
-
|
|
6805
|
-
|
|
6806
|
-
|
|
6807
|
-
|
|
6808
|
-
|
|
6809
|
-
|
|
6810
|
-
|
|
6811
|
-
|
|
6812
|
-
|
|
6813
|
-
|
|
6814
|
-
|
|
6815
|
-
const cal = getCalibrationState(calibrationKey());
|
|
6816
|
-
total = cal.calibrated ? Math.round(raw * Math.min(1.5, Math.max(0.5, cal.ratio))) : raw;
|
|
6817
|
-
}
|
|
6818
|
-
const rawLoad = maxContext > 0 ? total / maxContext : 0;
|
|
6819
|
-
const load = Math.max(0, Math.min(1, rawLoad));
|
|
6820
|
-
a.events.emit("ctx.pct", {
|
|
6821
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
6822
|
-
load,
|
|
6823
|
-
rawLoad,
|
|
6824
|
-
tokens: total,
|
|
6825
|
-
maxContext
|
|
6826
|
-
});
|
|
6968
|
+
}
|
|
6969
|
+
function elapsedMs(startedNs) {
|
|
6970
|
+
return Number(process.hrtime.bigint() - startedNs) / 1e6;
|
|
6971
|
+
}
|
|
6972
|
+
|
|
6973
|
+
// src/core/queued-messages.ts
|
|
6974
|
+
var META_KEY3 = "_queuedMessagesAwareness";
|
|
6975
|
+
var MAX_ITEMS = 20;
|
|
6976
|
+
var MAX_PREVIEW_CHARS = 500;
|
|
6977
|
+
function read(ctx) {
|
|
6978
|
+
const raw = ctx.meta[META_KEY3];
|
|
6979
|
+
if (!raw || typeof raw !== "object") return void 0;
|
|
6980
|
+
const s = raw;
|
|
6981
|
+
return Array.isArray(s.items) && Array.isArray(s.seen) ? s : void 0;
|
|
6982
|
+
}
|
|
6983
|
+
function sameList(a, b) {
|
|
6984
|
+
return a.length === b.length && a.every((v, i) => v === b[i]);
|
|
6985
|
+
}
|
|
6986
|
+
function toPreview(text) {
|
|
6987
|
+
const trimmed = text.trim();
|
|
6988
|
+
if (trimmed.length <= MAX_PREVIEW_CHARS) return trimmed;
|
|
6989
|
+
return `${trimmed.slice(0, MAX_PREVIEW_CHARS - 1)}\u2026`;
|
|
6990
|
+
}
|
|
6991
|
+
function setQueuedMessagesSnapshot(ctx, texts) {
|
|
6992
|
+
const items = texts.map(toPreview).filter((t2) => t2.length > 0).slice(0, MAX_ITEMS);
|
|
6993
|
+
const prev = read(ctx);
|
|
6994
|
+
if (items.length === 0 && (!prev || prev.seen.length === 0)) {
|
|
6995
|
+
if (prev) delete ctx.meta[META_KEY3];
|
|
6996
|
+
return;
|
|
6827
6997
|
}
|
|
6828
|
-
|
|
6829
|
-
|
|
6830
|
-
|
|
6831
|
-
|
|
6998
|
+
ctx.meta[META_KEY3] = { items, seen: prev?.seen ?? [] };
|
|
6999
|
+
}
|
|
7000
|
+
function consumeQueuedMessagesUpdate(ctx) {
|
|
7001
|
+
const s = read(ctx);
|
|
7002
|
+
if (!s) return null;
|
|
7003
|
+
if (sameList(s.items, s.seen)) return null;
|
|
7004
|
+
if (s.items.length === 0) {
|
|
7005
|
+
delete ctx.meta[META_KEY3];
|
|
7006
|
+
return null;
|
|
6832
7007
|
}
|
|
6833
|
-
|
|
6834
|
-
|
|
6835
|
-
|
|
6836
|
-
|
|
6837
|
-
|
|
6838
|
-
|
|
6839
|
-
|
|
6840
|
-
|
|
6841
|
-
|
|
6842
|
-
|
|
6843
|
-
|
|
6844
|
-
|
|
6845
|
-
|
|
6846
|
-
|
|
7008
|
+
ctx.meta[META_KEY3] = { items: s.items, seen: s.items };
|
|
7009
|
+
return s.items;
|
|
7010
|
+
}
|
|
7011
|
+
function buildQueuedMessagesBlock(items) {
|
|
7012
|
+
const body = items.map((t2, i) => `${i + 1}. ${t2}`).join("\n");
|
|
7013
|
+
return [
|
|
7014
|
+
"[QUEUED MESSAGES \u2014 the user typed these while you were working. They are",
|
|
7015
|
+
"waiting in the input queue and each will be delivered as its own turn",
|
|
7016
|
+
"after you finish, so do NOT answer or act on them as new tasks now. Read",
|
|
7017
|
+
"them as a heads-up: if one changes what you should be doing right now",
|
|
7018
|
+
"(new constraint, invalidated approach), adapt your current work; if one",
|
|
7019
|
+
"is just an FYI, fold it in. The queue may still change before delivery.",
|
|
7020
|
+
"This notice replaces any earlier one \u2014 the queue now holds exactly:",
|
|
7021
|
+
"",
|
|
7022
|
+
body,
|
|
7023
|
+
"]"
|
|
7024
|
+
].join("\n");
|
|
7025
|
+
}
|
|
7026
|
+
|
|
7027
|
+
// src/core/request-provider-binding.ts
|
|
7028
|
+
var requestProviders = /* @__PURE__ */ new WeakMap();
|
|
7029
|
+
function bindRequestProvider(request, provider) {
|
|
7030
|
+
requestProviders.set(request, provider);
|
|
7031
|
+
}
|
|
7032
|
+
function providerBoundToRequest(request) {
|
|
7033
|
+
return requestProviders.get(request);
|
|
7034
|
+
}
|
|
7035
|
+
|
|
7036
|
+
// src/core/agent-loop.ts
|
|
7037
|
+
function toError(err) {
|
|
7038
|
+
return err instanceof Error ? err : new Error(String(err));
|
|
7039
|
+
}
|
|
7040
|
+
function signalAbortReason(signal) {
|
|
7041
|
+
const r = signal.reason;
|
|
7042
|
+
if (r instanceof Error) return r.message || r.name;
|
|
7043
|
+
if (typeof r === "string" && r.length > 0) return r;
|
|
7044
|
+
return "aborted";
|
|
7045
|
+
}
|
|
7046
|
+
function createAgentLoopHandler(a, handlers) {
|
|
7047
|
+
const checkMailbox = attachMailboxChecker(a);
|
|
7048
|
+
const fleetPulseCfg = (() => {
|
|
7049
|
+
try {
|
|
7050
|
+
return typeof a.container?.has === "function" && a.container.has(TOKENS.ConfigStore) ? a.container.resolve(TOKENS.ConfigStore).get().fleet?.pulse : void 0;
|
|
7051
|
+
} catch {
|
|
7052
|
+
return void 0;
|
|
7053
|
+
}
|
|
7054
|
+
})();
|
|
7055
|
+
const getFleetPulse = attachFleetPulse(a, fleetPulseCfg);
|
|
7056
|
+
const pulseEveryN = Math.max(1, fleetPulseCfg?.everyNIterations ?? 5);
|
|
7057
|
+
const backgroundCoordination = () => a.ctx.meta["coordinationContextMode"] === "background";
|
|
7058
|
+
const loopContext = createAgentLoopContextManager(a, handlers);
|
|
6847
7059
|
function foldBlockIntoConversation(block) {
|
|
6848
7060
|
if (!a.ctx.state.appendBlockToLastUserMessage(block)) {
|
|
6849
7061
|
a.ctx.state.appendMessage({ role: "user", content: [block], origin: "runtime" });
|
|
6850
7062
|
}
|
|
6851
7063
|
}
|
|
6852
|
-
function iterationFingerprint(blocks) {
|
|
6853
|
-
const toolUses = blocks.filter(isToolUseBlock);
|
|
6854
|
-
const texts = blocks.filter(isTextBlock);
|
|
6855
|
-
const toolNameSet = Array.from(new Set(toolUses.map((u) => u.name))).sort();
|
|
6856
|
-
const firstInputHash = toolUses[0] ? hashSmall(stableStringify(toolUses[0].input ?? {})) : "";
|
|
6857
|
-
const textBlob = texts.map((t2) => t2.text).join("").slice(0, 512);
|
|
6858
|
-
const hasContent = toolNameSet.length > 0 || textBlob.length > 0;
|
|
6859
|
-
if (!hasContent) return "__empty__";
|
|
6860
|
-
return [
|
|
6861
|
-
`tools=${toolNameSet.join("+") || "-"}`,
|
|
6862
|
-
`in0=${firstInputHash}`,
|
|
6863
|
-
`txt=${textBlob}`
|
|
6864
|
-
].join("\n");
|
|
6865
|
-
}
|
|
6866
|
-
function stableStringify(value) {
|
|
6867
|
-
return JSON.stringify(canonicalize(value));
|
|
6868
|
-
}
|
|
6869
|
-
function canonicalize(value) {
|
|
6870
|
-
if (Array.isArray(value)) return value.map(canonicalize);
|
|
6871
|
-
if (value && typeof value === "object") {
|
|
6872
|
-
const src = value;
|
|
6873
|
-
const out = {};
|
|
6874
|
-
for (const k of Object.keys(src).sort()) out[k] = canonicalize(src[k]);
|
|
6875
|
-
return out;
|
|
6876
|
-
}
|
|
6877
|
-
return value;
|
|
6878
|
-
}
|
|
6879
|
-
function hashSmall(s) {
|
|
6880
|
-
let h = 2166136261;
|
|
6881
|
-
for (let i = 0; i < s.length; i++) {
|
|
6882
|
-
h ^= s.charCodeAt(i);
|
|
6883
|
-
h = h + ((h << 1) + (h << 4) + (h << 7) + (h << 8) + (h << 24)) >>> 0;
|
|
6884
|
-
}
|
|
6885
|
-
return h.toString(36);
|
|
6886
|
-
}
|
|
6887
7064
|
function injectPendingBtwNotes(onMailboxBlock) {
|
|
6888
7065
|
const notes = consumeBtwNotes(a.ctx);
|
|
6889
7066
|
if (notes.length === 0) return;
|
|
@@ -6963,15 +7140,10 @@ function createAgentLoopHandler(a, handlers) {
|
|
|
6963
7140
|
a.ctx.state.replaceMessages(cleaned.messages);
|
|
6964
7141
|
a.ctx.lastRealInputTokens = void 0;
|
|
6965
7142
|
delete a.ctx.meta["realAnchorMsgCount"];
|
|
6966
|
-
refreshContextRequestTokenStash({ force: true });
|
|
7143
|
+
loopContext.refreshContextRequestTokenStash({ force: true });
|
|
6967
7144
|
}
|
|
6968
7145
|
}
|
|
6969
|
-
const
|
|
6970
|
-
let lastToolSignature = "";
|
|
6971
|
-
let toolLoopCount = 0;
|
|
6972
|
-
let iterationSteerDone = false;
|
|
6973
|
-
const recentCallKeys = [];
|
|
6974
|
-
const steeredCallKeys = /* @__PURE__ */ new Set();
|
|
7146
|
+
const loopDetector = new AgentLoopDetector(a);
|
|
6975
7147
|
let pendingLoopSteer = null;
|
|
6976
7148
|
let todoReconcileSteers = 0;
|
|
6977
7149
|
function queueLoopSteer(text) {
|
|
@@ -7078,7 +7250,7 @@ ${text}` : text;
|
|
|
7078
7250
|
req,
|
|
7079
7251
|
provider: requestProvider,
|
|
7080
7252
|
preFlight
|
|
7081
|
-
} = await buildRequestWithPreflightCompaction(opts);
|
|
7253
|
+
} = await loopContext.buildRequestWithPreflightCompaction(opts);
|
|
7082
7254
|
await a.ctx.session.append({
|
|
7083
7255
|
type: "llm_request",
|
|
7084
7256
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -7091,7 +7263,7 @@ ${text}` : text;
|
|
|
7091
7263
|
let res;
|
|
7092
7264
|
try {
|
|
7093
7265
|
res = await customRunner(a.ctx, req);
|
|
7094
|
-
const key = calibrationKey(req.model);
|
|
7266
|
+
const key = loopContext.calibrationKey(req.model);
|
|
7095
7267
|
const cal = getCalibrationState(key);
|
|
7096
7268
|
const calibratedTotal = cal.calibrated ? Math.round(preFlight.total * Math.min(1.5, Math.max(0.5, cal.ratio))) : preFlight.total;
|
|
7097
7269
|
const realInputTokens = effectiveInputTokens(res.usage);
|
|
@@ -7099,10 +7271,10 @@ ${text}` : text;
|
|
|
7099
7271
|
const previousRealInput = a.ctx.lastRealInputTokens;
|
|
7100
7272
|
const previousAnchorMsgCount = typeof a.ctx.meta?.["realAnchorMsgCount"] === "number" ? a.ctx.meta["realAnchorMsgCount"] : void 0;
|
|
7101
7273
|
const anchorIsPlausible = realInputTokens >= calibratedTotal * 0.5;
|
|
7102
|
-
const anchorAdvanced = previousRealInput === void 0 || realInputTokens > previousRealInput || previousAnchorMsgCount !== void 0 &&
|
|
7274
|
+
const anchorAdvanced = previousRealInput === void 0 || realInputTokens > previousRealInput || previousAnchorMsgCount !== void 0 && loopContext.lastPreFlightMsgCount < previousAnchorMsgCount;
|
|
7103
7275
|
if (realInputTokens > 0 && anchorIsPlausible && anchorAdvanced) {
|
|
7104
7276
|
a.ctx.lastRealInputTokens = realInputTokens;
|
|
7105
|
-
a.ctx.meta["realAnchorMsgCount"] =
|
|
7277
|
+
a.ctx.meta["realAnchorMsgCount"] = loopContext.lastPreFlightMsgCount;
|
|
7106
7278
|
}
|
|
7107
7279
|
recoveryRetries = 0;
|
|
7108
7280
|
} catch (err) {
|
|
@@ -7192,7 +7364,9 @@ ${text}` : text;
|
|
|
7192
7364
|
clearEvaluatedMailboxBlocks();
|
|
7193
7365
|
const responseProvider = providerBoundToRequest(req) ?? requestProvider;
|
|
7194
7366
|
const responseResult = await handlers.response.processResponse(res, req, responseProvider);
|
|
7195
|
-
await refreshProviderContextLimit(responseProvider, req.model, {
|
|
7367
|
+
await loopContext.refreshProviderContextLimit(responseProvider, req.model, {
|
|
7368
|
+
probe: false
|
|
7369
|
+
});
|
|
7196
7370
|
if (responseResult.finalText) {
|
|
7197
7371
|
a.ctx.meta["lastAgentOutput"] = responseResult.finalText;
|
|
7198
7372
|
}
|
|
@@ -7215,103 +7389,18 @@ ${text}` : text;
|
|
|
7215
7389
|
}
|
|
7216
7390
|
finalText = responseResult.finalText;
|
|
7217
7391
|
const toolUses = res.content.filter(isToolUseBlock);
|
|
7218
|
-
|
|
7219
|
-
|
|
7220
|
-
|
|
7221
|
-
|
|
7222
|
-
|
|
7223
|
-
|
|
7224
|
-
|
|
7225
|
-
|
|
7226
|
-
iterationSteerDone = false;
|
|
7227
|
-
}
|
|
7228
|
-
const names = toolUses.map((t2) => t2.name).join(", ");
|
|
7229
|
-
const hasText2 = res.content.some(isTextBlock);
|
|
7230
|
-
const kind = toolUses.length > 0 && hasText2 ? "mixed" : toolUses.length > 0 ? "tool" : "message";
|
|
7231
|
-
const observationRepeat = loopCfg.mode === "steer-then-cut" && toolUses.length > 0 && toolUses.every((use) => {
|
|
7232
|
-
const tool = a.tools.get(use.name);
|
|
7233
|
-
return tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0);
|
|
7234
|
-
});
|
|
7235
|
-
const repeatMultiplier = observationRepeat ? 2 : 1;
|
|
7236
|
-
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`;
|
|
7237
|
-
const cutAt = (loopCfg.mode === "cut" ? loopCfg.steerThreshold : loopCfg.cutThreshold) * repeatMultiplier;
|
|
7238
|
-
if (toolLoopCount >= cutAt) {
|
|
7239
|
-
a.logger.warn(`Loop detected: ${detail} \u2014 stopping to prevent infinite loop.`);
|
|
7240
|
-
a.events.emit("tool.loop_detected", {
|
|
7241
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7242
|
-
ctx: a.ctx,
|
|
7243
|
-
tools: names,
|
|
7244
|
-
repeatCount: toolLoopCount,
|
|
7245
|
-
iteration: i,
|
|
7246
|
-
kind,
|
|
7247
|
-
action: "cut",
|
|
7248
|
-
scope: "iteration"
|
|
7249
|
-
});
|
|
7250
|
-
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.]`;
|
|
7251
|
-
return {
|
|
7252
|
-
status: "max_iterations",
|
|
7253
|
-
iterations,
|
|
7254
|
-
finalText: finalText || summary,
|
|
7255
|
-
delegateSummaries
|
|
7256
|
-
};
|
|
7257
|
-
}
|
|
7258
|
-
if (loopCfg.mode === "steer-then-cut" && toolLoopCount >= loopCfg.steerThreshold * repeatMultiplier && !iterationSteerDone) {
|
|
7259
|
-
iterationSteerDone = true;
|
|
7260
|
-
a.logger.warn(`Loop detected: ${detail} \u2014 steering the model to change approach.`);
|
|
7261
|
-
a.events.emit("tool.loop_detected", {
|
|
7262
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7263
|
-
ctx: a.ctx,
|
|
7264
|
-
tools: names,
|
|
7265
|
-
repeatCount: toolLoopCount,
|
|
7266
|
-
iteration: i,
|
|
7267
|
-
kind,
|
|
7268
|
-
action: "steer",
|
|
7269
|
-
scope: "iteration"
|
|
7270
|
-
});
|
|
7271
|
-
queueLoopSteer(
|
|
7272
|
-
`[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.`
|
|
7273
|
-
);
|
|
7274
|
-
}
|
|
7275
|
-
} else {
|
|
7276
|
-
lastToolSignature = "";
|
|
7277
|
-
toolLoopCount = 0;
|
|
7278
|
-
iterationSteerDone = false;
|
|
7279
|
-
}
|
|
7280
|
-
if (loopCfg.mode === "steer-then-cut") {
|
|
7281
|
-
for (const u of toolUses) {
|
|
7282
|
-
const key = `${u.name}:${hashSmall(stableStringify(u.input ?? {}))}`;
|
|
7283
|
-
recentCallKeys.push(key);
|
|
7284
|
-
if (recentCallKeys.length > loopCfg.windowSize) recentCallKeys.shift();
|
|
7285
|
-
if (steeredCallKeys.has(key)) continue;
|
|
7286
|
-
let count = 0;
|
|
7287
|
-
for (const k of recentCallKeys) if (k === key) count++;
|
|
7288
|
-
const tool = a.tools.get(u.name);
|
|
7289
|
-
const observationThreshold = tool?.mutating === false && (tool.riskTier === "safe" || (tool.capabilities?.length ?? 0) > 0) ? loopCfg.callRepeatThreshold * 2 : loopCfg.callRepeatThreshold;
|
|
7290
|
-
if (count < observationThreshold) continue;
|
|
7291
|
-
steeredCallKeys.add(key);
|
|
7292
|
-
const preview2 = JSON.stringify(u.input ?? {}).slice(0, 160);
|
|
7293
|
-
a.logger.warn(
|
|
7294
|
-
`Loop detected: "${u.name}" called with identical arguments ${count}\xD7 within the last ${loopCfg.windowSize} tool calls \u2014 steering the model to change approach.`
|
|
7295
|
-
);
|
|
7296
|
-
a.events.emit("tool.loop_detected", {
|
|
7297
|
-
sessionId: resolveEventSessionId(a.ctx),
|
|
7298
|
-
ctx: a.ctx,
|
|
7299
|
-
tools: u.name,
|
|
7300
|
-
repeatCount: count,
|
|
7301
|
-
iteration: i,
|
|
7302
|
-
kind: "tool",
|
|
7303
|
-
action: "steer",
|
|
7304
|
-
scope: "call"
|
|
7305
|
-
});
|
|
7306
|
-
queueLoopSteer(
|
|
7307
|
-
`[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.`
|
|
7308
|
-
);
|
|
7309
|
-
}
|
|
7310
|
-
}
|
|
7392
|
+
const loopCheck = loopDetector.checkIteration(i, res.content, toolUses, queueLoopSteer);
|
|
7393
|
+
if (loopCheck.cut) {
|
|
7394
|
+
return {
|
|
7395
|
+
status: "max_iterations",
|
|
7396
|
+
iterations,
|
|
7397
|
+
finalText: finalText || loopCheck.cutSummary || "",
|
|
7398
|
+
delegateSummaries
|
|
7399
|
+
};
|
|
7311
7400
|
}
|
|
7312
7401
|
if (toolUses.length === 0) {
|
|
7313
|
-
await compactContextIfNeeded();
|
|
7314
|
-
emitContextPct();
|
|
7402
|
+
await loopContext.compactContextIfNeeded();
|
|
7403
|
+
loopContext.emitContextPct();
|
|
7315
7404
|
a.events.emit("iteration.completed", {
|
|
7316
7405
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7317
7406
|
ctx: a.ctx,
|
|
@@ -7349,8 +7438,8 @@ ${text}` : text;
|
|
|
7349
7438
|
throw toolErr;
|
|
7350
7439
|
}
|
|
7351
7440
|
if (autonomousContinue && consumeAutonomousContinue(a.ctx)) {
|
|
7352
|
-
await compactContextIfNeeded();
|
|
7353
|
-
emitContextPct();
|
|
7441
|
+
await loopContext.compactContextIfNeeded();
|
|
7442
|
+
loopContext.emitContextPct();
|
|
7354
7443
|
a.events.emit("iteration.completed", {
|
|
7355
7444
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7356
7445
|
ctx: a.ctx,
|
|
@@ -7359,8 +7448,8 @@ ${text}` : text;
|
|
|
7359
7448
|
await a.extensions.runAfterIteration(a.ctx, i);
|
|
7360
7449
|
continue;
|
|
7361
7450
|
}
|
|
7362
|
-
await compactContextIfNeeded();
|
|
7363
|
-
emitContextPct();
|
|
7451
|
+
await loopContext.compactContextIfNeeded();
|
|
7452
|
+
loopContext.emitContextPct();
|
|
7364
7453
|
a.events.emit("iteration.completed", {
|
|
7365
7454
|
sessionId: resolveEventSessionId(a.ctx),
|
|
7366
7455
|
ctx: a.ctx,
|
|
@@ -7559,6 +7648,9 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7559
7648
|
"codebase-incoming-calls",
|
|
7560
7649
|
"codebase-outgoing-calls",
|
|
7561
7650
|
"codebase-index",
|
|
7651
|
+
"codebase-skeleton",
|
|
7652
|
+
"codebase-repo-map",
|
|
7653
|
+
"codebase-impact-analysis",
|
|
7562
7654
|
"dead-code-scan",
|
|
7563
7655
|
"diff",
|
|
7564
7656
|
"json",
|
|
@@ -7569,7 +7661,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7569
7661
|
id: "filesystem.write",
|
|
7570
7662
|
pack: "development",
|
|
7571
7663
|
exposure: "direct",
|
|
7572
|
-
tools: ["write", "edit", "replace", "patch"]
|
|
7664
|
+
tools: ["write", "edit", "replace", "patch", "codebase-ast-replace"]
|
|
7573
7665
|
},
|
|
7574
7666
|
{
|
|
7575
7667
|
id: "execution.shell",
|
|
@@ -7581,7 +7673,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7581
7673
|
id: "verification.run",
|
|
7582
7674
|
pack: "development",
|
|
7583
7675
|
exposure: "direct",
|
|
7584
|
-
tools: ["lint", "format", "typecheck", "test", "e2e_plan"]
|
|
7676
|
+
tools: ["lint", "format", "typecheck", "test", "e2e_plan", "codebase-targeted-test"]
|
|
7585
7677
|
},
|
|
7586
7678
|
{
|
|
7587
7679
|
id: "version-control.manage",
|
|
@@ -7718,7 +7810,13 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7718
7810
|
id: "quality.analyze",
|
|
7719
7811
|
pack: "development",
|
|
7720
7812
|
exposure: "on-demand",
|
|
7721
|
-
tools: [
|
|
7813
|
+
tools: [
|
|
7814
|
+
"dead_code_scan",
|
|
7815
|
+
"detect_duplicate_code",
|
|
7816
|
+
"secret_scanner_test",
|
|
7817
|
+
"error_lens_history",
|
|
7818
|
+
"security-ast-scan"
|
|
7819
|
+
]
|
|
7722
7820
|
},
|
|
7723
7821
|
{
|
|
7724
7822
|
id: "release.manage",
|
|
@@ -7736,7 +7834,7 @@ var RUNTIME_CAPABILITY_MANIFEST = [
|
|
|
7736
7834
|
id: "tools.discover",
|
|
7737
7835
|
pack: "admin",
|
|
7738
7836
|
exposure: "direct",
|
|
7739
|
-
tools: ["tool_search", "tool_use", "tool_help", "batch_tool_use"]
|
|
7837
|
+
tools: ["tool_search", "tool_use", "tool_help", "batch_tool_use", "clarify"]
|
|
7740
7838
|
},
|
|
7741
7839
|
{
|
|
7742
7840
|
id: "runtime.admin",
|
|
@@ -8119,26 +8217,41 @@ function stripNextStepsFromMessage(msg) {
|
|
|
8119
8217
|
strippedNextStepsCache.set(msg, clone);
|
|
8120
8218
|
return clone;
|
|
8121
8219
|
}
|
|
8122
|
-
function
|
|
8220
|
+
function markCacheBoundary(msg) {
|
|
8221
|
+
if (typeof msg.content === "string") return void 0;
|
|
8222
|
+
const blocks = msg.content.slice();
|
|
8223
|
+
const boundary = blocks[blocks.length - 1];
|
|
8224
|
+
if (!boundary || boundary.type !== "text" && boundary.type !== "tool_result") return void 0;
|
|
8225
|
+
blocks[blocks.length - 1] = { ...boundary, cache_control: { type: "ephemeral" } };
|
|
8226
|
+
return { ...msg, content: blocks };
|
|
8227
|
+
}
|
|
8228
|
+
function composeRequestMessages(history, tail, previous) {
|
|
8123
8229
|
if (history.length === 0) return null;
|
|
8124
8230
|
const out = history.slice();
|
|
8125
8231
|
const lastIdx = out.length - 1;
|
|
8126
8232
|
const last = out[lastIdx];
|
|
8233
|
+
if (previous && previous.index < lastIdx && history[previous.index] === previous.message) {
|
|
8234
|
+
const marked2 = markCacheBoundary(previous.message);
|
|
8235
|
+
if (marked2) out[previous.index] = marked2;
|
|
8236
|
+
}
|
|
8127
8237
|
const blocks = typeof last.content === "string" ? [{ type: "text", text: last.content }] : last.content.slice();
|
|
8128
|
-
const
|
|
8129
|
-
|
|
8130
|
-
|
|
8238
|
+
const tailBlock = blocks[blocks.length - 1];
|
|
8239
|
+
const marked = tailBlock && (tailBlock.type === "text" || tailBlock.type === "tool_result");
|
|
8240
|
+
if (marked) {
|
|
8241
|
+
blocks[blocks.length - 1] = { ...tailBlock, cache_control: { type: "ephemeral" } };
|
|
8131
8242
|
}
|
|
8243
|
+
const boundary = marked ? { message: last, index: lastIdx } : void 0;
|
|
8132
8244
|
if (tail.length === 0 || last.role !== "user") {
|
|
8133
8245
|
out[lastIdx] = { ...last, content: blocks };
|
|
8134
8246
|
if (tail.length > 0) out.push({ role: "user", content: [LIVE_CONTEXT_HEADER, ...tail] });
|
|
8135
|
-
return out;
|
|
8247
|
+
return { messages: out, boundary };
|
|
8136
8248
|
}
|
|
8137
8249
|
out[lastIdx] = { ...last, content: [...blocks, LIVE_CONTEXT_HEADER, ...tail] };
|
|
8138
|
-
return out;
|
|
8250
|
+
return { messages: out, boundary };
|
|
8139
8251
|
}
|
|
8140
8252
|
function createAgentResponseHandler(a) {
|
|
8141
8253
|
const stabilizedPromptEpochs = /* @__PURE__ */ new WeakSet();
|
|
8254
|
+
let previousBoundary;
|
|
8142
8255
|
function stabilizePromptEpoch() {
|
|
8143
8256
|
const prompt = a.ctx.systemPrompt;
|
|
8144
8257
|
if (stabilizedPromptEpochs.has(prompt)) return;
|
|
@@ -8161,7 +8274,7 @@ function createAgentResponseHandler(a) {
|
|
|
8161
8274
|
...repaired.report
|
|
8162
8275
|
});
|
|
8163
8276
|
a.logger.warn(
|
|
8164
|
-
`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)`
|
|
8277
|
+
`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)
|
|
8165
8278
|
);
|
|
8166
8279
|
}
|
|
8167
8280
|
}
|
|
@@ -8179,7 +8292,9 @@ function createAgentResponseHandler(a) {
|
|
|
8179
8292
|
...memoryEvidence
|
|
8180
8293
|
].filter((block) => block !== void 0);
|
|
8181
8294
|
const requestHistory = stripDeliveredNextSteps(a.ctx.messages);
|
|
8182
|
-
const
|
|
8295
|
+
const composed = composeRequestMessages(requestHistory, liveContextTail, previousBoundary);
|
|
8296
|
+
if (composed) previousBoundary = composed.boundary;
|
|
8297
|
+
const composedMessages = composed?.messages ?? null;
|
|
8183
8298
|
const system = composedMessages ? stableSystem : liveContextTail.length > 0 ? [...stableSystem, ...liveContextTail] : stableSystem;
|
|
8184
8299
|
await a.ctx.waitForModelTransition();
|
|
8185
8300
|
const provider = a.ctx.provider;
|
|
@@ -8279,6 +8394,10 @@ function createAgentResponseHandler(a) {
|
|
|
8279
8394
|
}
|
|
8280
8395
|
return { buildAndRunRequestPipeline, processResponse };
|
|
8281
8396
|
}
|
|
8397
|
+
function formatAdjacencyRepairIds(toolUses, toolResults) {
|
|
8398
|
+
const ids = [.../* @__PURE__ */ new Set([...toolUses, ...toolResults])].slice(0, 8);
|
|
8399
|
+
return ids.length > 0 ? `; affected tool ids: ${ids.join(", ")}${toolUses.length + toolResults.length > ids.length ? ", \u2026" : ""}` : "";
|
|
8400
|
+
}
|
|
8282
8401
|
|
|
8283
8402
|
// src/utils/sage-output-block.ts
|
|
8284
8403
|
var SAGE_INJECTOR_HEADINGS = /* @__PURE__ */ new Set([
|
|
@@ -8850,7 +8969,7 @@ var Agent = class {
|
|
|
8850
8969
|
this.maxIterations = init.maxIterations ?? DEFAULT_MAX_ITERATIONS;
|
|
8851
8970
|
this.executionStrategy = init.executionStrategy ?? "smart";
|
|
8852
8971
|
this.perIterationOutputCapBytes = init.perIterationOutputCapBytes ?? 1e5;
|
|
8853
|
-
this.autoExtendLimit = init.autoExtendLimit ??
|
|
8972
|
+
this.autoExtendLimit = init.autoExtendLimit ?? false;
|
|
8854
8973
|
this.loopDetection = resolveLoopDetection(init.loopDetection);
|
|
8855
8974
|
this.autonomousContinue = init.autonomousContinue ?? false;
|
|
8856
8975
|
this.refreshSystemPrompt = init.refreshSystemPrompt ?? false;
|
|
@@ -9217,9 +9336,6 @@ function resolveContinuation(input) {
|
|
|
9217
9336
|
return { source: "open", text, label: "\u25B6 Continue \u2192 (no pending task \u2014 choosing next step)" };
|
|
9218
9337
|
}
|
|
9219
9338
|
|
|
9220
|
-
// src/core/fallback-model.ts
|
|
9221
|
-
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9222
|
-
|
|
9223
9339
|
// src/core/model-availability-calendar.ts
|
|
9224
9340
|
function logicalCalendarTarget(providerId, model) {
|
|
9225
9341
|
if (providerId !== "omniroute") return { providerId, model };
|
|
@@ -9326,6 +9442,12 @@ function normalizeModelRef(ref, defaultProvider) {
|
|
|
9326
9442
|
function hasText(value) {
|
|
9327
9443
|
return typeof value === "string" && value.trim().length > 0;
|
|
9328
9444
|
}
|
|
9445
|
+
function asRefList(value) {
|
|
9446
|
+
return Array.isArray(value) ? value : void 0;
|
|
9447
|
+
}
|
|
9448
|
+
function asProfileName(value) {
|
|
9449
|
+
return hasText(value) ? value : void 0;
|
|
9450
|
+
}
|
|
9329
9451
|
function providerHasKey(entry) {
|
|
9330
9452
|
if (!entry) return false;
|
|
9331
9453
|
if (hasText(entry.apiKey)) return true;
|
|
@@ -9336,7 +9458,7 @@ function providerHasKey(entry) {
|
|
|
9336
9458
|
}
|
|
9337
9459
|
function visibleProviderModels(config, providerId, providerModels) {
|
|
9338
9460
|
const entry = config.providers?.[providerId];
|
|
9339
|
-
return entry?.models
|
|
9461
|
+
return Array.isArray(entry?.models) ? [...entry.models] : providerModels;
|
|
9340
9462
|
}
|
|
9341
9463
|
function buildProfiles(config) {
|
|
9342
9464
|
const entries = /* @__PURE__ */ new Map();
|
|
@@ -9373,13 +9495,34 @@ var FallbackProfileManager = class {
|
|
|
9373
9495
|
listProfiles() {
|
|
9374
9496
|
return Object.freeze([...this.profiles.keys()]);
|
|
9375
9497
|
}
|
|
9498
|
+
/**
|
|
9499
|
+
* The profile the session has selected (`config.fallbackProfile`, set by
|
|
9500
|
+
* `/fallback profile use <name>`), or undefined when none is selected or the
|
|
9501
|
+
* name no longer resolves to a defined profile.
|
|
9502
|
+
*
|
|
9503
|
+
* Consulted by every resolution entry point when the caller does not name a
|
|
9504
|
+
* profile itself. Without this the leader — which passes no profile — could
|
|
9505
|
+
* never use a named profile at all: `config.fallbackProfiles` was reachable
|
|
9506
|
+
* only by copying a chain into `fallbackModels`.
|
|
9507
|
+
*/
|
|
9508
|
+
activeProfileName() {
|
|
9509
|
+
const name = asProfileName(this.config.fallbackProfile);
|
|
9510
|
+
return name && this.profiles.has(name) ? name : void 0;
|
|
9511
|
+
}
|
|
9376
9512
|
// ── Resolution ─────────────────────────────────────────────────────────
|
|
9377
9513
|
/**
|
|
9378
9514
|
* Resolve a named fallback profile to a validated, provider-filtered chain.
|
|
9379
9515
|
*
|
|
9380
|
-
* Returns an empty chain when
|
|
9381
|
-
*
|
|
9382
|
-
*
|
|
9516
|
+
* Returns an empty chain when the profile doesn't exist, or when every entry
|
|
9517
|
+
* is excluded, quarantined, or blacked out.
|
|
9518
|
+
*
|
|
9519
|
+
* Filtering is intentionally identical to {@link resolveRefs} (the explicit
|
|
9520
|
+
* `fallbackModels` path): the self-exclusion, the runtime status tracker,
|
|
9521
|
+
* and the availability calendar — nothing else. Anything a named profile
|
|
9522
|
+
* drops, an explicit chain drops too, and vice versa. Profiles used to apply
|
|
9523
|
+
* two extra filters (provider "usability" and the `providers[].models`
|
|
9524
|
+
* snapshot) that the explicit path did not, which silently rerouted roles
|
|
9525
|
+
* pinned to a profile onto a different model than the one configured.
|
|
9383
9526
|
*
|
|
9384
9527
|
* @param name - Profile name from config.fallbackProfiles.
|
|
9385
9528
|
* @param defaultProvider - Used when an entry has no explicit provider.
|
|
@@ -9400,13 +9543,9 @@ var FallbackProfileManager = class {
|
|
|
9400
9543
|
if (seen.has(key)) continue;
|
|
9401
9544
|
seen.add(key);
|
|
9402
9545
|
if (excludeKey && key === excludeKey) continue;
|
|
9403
|
-
const health = this.checkProvider(providerId);
|
|
9404
|
-
if (!health.usable) continue;
|
|
9405
9546
|
if (this.statusTracker && !this.statusTracker.isAvailable(providerId, parsed.model)) continue;
|
|
9406
9547
|
if (!evaluateModelCalendar(this.config.modelAvailabilitySchedule, providerId, parsed.model).allowed)
|
|
9407
9548
|
continue;
|
|
9408
|
-
const allowedModels = this.config.providers?.[providerId]?.models;
|
|
9409
|
-
if (allowedModels && !allowedModels.includes(parsed.model)) continue;
|
|
9410
9549
|
resolved.push({
|
|
9411
9550
|
providerId,
|
|
9412
9551
|
model: parsed.model,
|
|
@@ -9424,12 +9563,14 @@ var FallbackProfileManager = class {
|
|
|
9424
9563
|
resolveEffective(opts = {}) {
|
|
9425
9564
|
const bridge = this.resolveBridge(opts.exclude);
|
|
9426
9565
|
let selected = FREEZER_EMPTY;
|
|
9427
|
-
|
|
9428
|
-
|
|
9566
|
+
const explicitRefs = asRefList(opts.fallbackModels);
|
|
9567
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
9568
|
+
if (explicitRefs && explicitRefs.length > 0) {
|
|
9569
|
+
const resolved = this.resolveRefs(explicitRefs, opts.exclude);
|
|
9429
9570
|
if (resolved.length > 0) selected = resolved;
|
|
9430
9571
|
}
|
|
9431
|
-
if (selected.length === 0 &&
|
|
9432
|
-
const resolved = this.resolve(
|
|
9572
|
+
if (selected.length === 0 && profileName) {
|
|
9573
|
+
const resolved = this.resolve(profileName, { exclude: opts.exclude });
|
|
9433
9574
|
if (resolved.length > 0) selected = resolved;
|
|
9434
9575
|
}
|
|
9435
9576
|
if (selected.length === 0 && opts.fallbackAuto !== false) {
|
|
@@ -9505,13 +9646,14 @@ var FallbackProfileManager = class {
|
|
|
9505
9646
|
};
|
|
9506
9647
|
const configFallbackAuto = this.config.fallbackAuto;
|
|
9507
9648
|
const effectiveFallbackAuto = configFallbackAuto !== void 0 && configFallbackAuto !== null ? configFallbackAuto : !opts.closedWorld;
|
|
9508
|
-
const explicitRefs = opts.fallbackModels ?? this.config.fallbackModels;
|
|
9649
|
+
const explicitRefs = asRefList(opts.fallbackModels) ?? asRefList(this.config.fallbackModels);
|
|
9650
|
+
const profileName = asProfileName(opts.fallbackProfile) ?? this.activeProfileName();
|
|
9509
9651
|
const explicitUsable = explicitRefs !== void 0 && explicitRefs.length > 0 && this.resolveRefs(explicitRefs, current).length > 0;
|
|
9510
|
-
const profileUsable =
|
|
9652
|
+
const profileUsable = profileName !== void 0 && this.hasProfile(profileName) && this.resolve(profileName, { exclude: current }).length > 0;
|
|
9511
9653
|
const fromExplicitSource = explicitUsable || profileUsable;
|
|
9512
|
-
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) :
|
|
9654
|
+
const selectedChain = opts.closedWorld ? explicitRefs && explicitRefs.length > 0 ? this.resolveRefs(explicitRefs, current) : profileName ? this.resolve(profileName, { exclude: current }) : FREEZER_EMPTY : this.resolveEffective({
|
|
9513
9655
|
fallbackModels: explicitRefs,
|
|
9514
|
-
fallbackProfile:
|
|
9656
|
+
fallbackProfile: profileName,
|
|
9515
9657
|
fallbackAuto: effectiveFallbackAuto,
|
|
9516
9658
|
exclude: current
|
|
9517
9659
|
});
|
|
@@ -9528,7 +9670,7 @@ var FallbackProfileManager = class {
|
|
|
9528
9670
|
});
|
|
9529
9671
|
}
|
|
9530
9672
|
candidates.push(...selectedChain);
|
|
9531
|
-
if (!fromExplicitSource && effectiveFallbackAuto &&
|
|
9673
|
+
if (!fromExplicitSource && effectiveFallbackAuto && profileName !== "default") {
|
|
9532
9674
|
candidates.push(...this.resolve("default", { exclude: current }));
|
|
9533
9675
|
}
|
|
9534
9676
|
if (!fromExplicitSource && effectiveFallbackAuto) {
|
|
@@ -9606,7 +9748,7 @@ var FallbackProfileManager = class {
|
|
|
9606
9748
|
const leaderModel = this.config.model;
|
|
9607
9749
|
const providers = this.config.providers ?? {};
|
|
9608
9750
|
const favoriteSet = new Set(
|
|
9609
|
-
(this.config.favoriteModels ?? []).map((ref) => {
|
|
9751
|
+
(asRefList(this.config.favoriteModels) ?? []).map((ref) => {
|
|
9610
9752
|
const p = parseModelRef(ref);
|
|
9611
9753
|
return `${p.provider ?? leaderProvider}/${p.model}`;
|
|
9612
9754
|
})
|
|
@@ -9681,7 +9823,206 @@ function dedupeChain(entries, current) {
|
|
|
9681
9823
|
);
|
|
9682
9824
|
}
|
|
9683
9825
|
|
|
9826
|
+
// src/core/fallback-doctor.ts
|
|
9827
|
+
function diagnoseFallbackConfig(config, tracker, opts) {
|
|
9828
|
+
const mgr = new FallbackProfileManager(config, { statusTracker: tracker });
|
|
9829
|
+
const primary = { providerId: config.provider, model: config.model };
|
|
9830
|
+
const activeProfile = mgr.activeProfileName();
|
|
9831
|
+
const explicitChain = Object.freeze([...config.fallbackModels ?? []]);
|
|
9832
|
+
const effectiveCandidates = mgr.resolveCandidates(primary, {
|
|
9833
|
+
fallbackProfile: activeProfile
|
|
9834
|
+
});
|
|
9835
|
+
const effectiveOrder = Object.freeze(
|
|
9836
|
+
effectiveCandidates.map((c) => `${c.providerId}/${c.model}`)
|
|
9837
|
+
);
|
|
9838
|
+
const autoEnabled = config.fallbackAuto !== false;
|
|
9839
|
+
const warnings = [];
|
|
9840
|
+
const providersInChain = /* @__PURE__ */ new Set();
|
|
9841
|
+
for (const entry of effectiveCandidates) {
|
|
9842
|
+
providersInChain.add(entry.providerId);
|
|
9843
|
+
}
|
|
9844
|
+
if (effectiveCandidates.length === 0) {
|
|
9845
|
+
warnings.push({
|
|
9846
|
+
code: "EMPTY_CHAIN",
|
|
9847
|
+
severity: "critical",
|
|
9848
|
+
message: "The effective fallback chain is empty. Any rate limit or outage on the primary model will immediately fail without recovery.",
|
|
9849
|
+
recommendation: "Add fallback models using /fallback add <provider/model> or enable smart defaults with /fallback auto on."
|
|
9850
|
+
});
|
|
9851
|
+
}
|
|
9852
|
+
if (effectiveCandidates.length > 0) {
|
|
9853
|
+
const onlyPrimaryProvider = providersInChain.size === 1 && providersInChain.has(primary.providerId);
|
|
9854
|
+
if (onlyPrimaryProvider) {
|
|
9855
|
+
warnings.push({
|
|
9856
|
+
code: "SIBLING_QUARANTINE_RISK",
|
|
9857
|
+
severity: "warning",
|
|
9858
|
+
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.`,
|
|
9859
|
+
recommendation: "Add at least one cross-provider model (e.g. OpenAI, Anthropic, or Google) to your fallback profile."
|
|
9860
|
+
});
|
|
9861
|
+
}
|
|
9862
|
+
}
|
|
9863
|
+
for (const entry of effectiveCandidates) {
|
|
9864
|
+
const health = mgr.checkProvider(entry.providerId);
|
|
9865
|
+
if (!health.usable) {
|
|
9866
|
+
warnings.push({
|
|
9867
|
+
code: "PROVIDER_UNUSABLE",
|
|
9868
|
+
severity: "critical",
|
|
9869
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
9870
|
+
message: `Provider "${entry.providerId}" is missing an API key or baseUrl in configuration.`,
|
|
9871
|
+
recommendation: `Configure API credentials for "${entry.providerId}" or remove it from the fallback chain.`
|
|
9872
|
+
});
|
|
9873
|
+
}
|
|
9874
|
+
if (tracker && !tracker.isAvailable(entry.providerId, entry.model)) {
|
|
9875
|
+
const status2 = tracker.getStatus(entry.providerId, entry.model);
|
|
9876
|
+
const remainingMs = status2?.stateExpiresAt ? Math.max(0, status2.stateExpiresAt - Date.now()) : 0;
|
|
9877
|
+
const remainingSec = Math.round(remainingMs / 1e3);
|
|
9878
|
+
warnings.push({
|
|
9879
|
+
code: "MODEL_QUARANTINED",
|
|
9880
|
+
severity: "warning",
|
|
9881
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
9882
|
+
message: `Model is currently in quarantine (waiting room) due to ${status2?.lastErrorKind ?? "failures"}.${remainingSec > 0 ? ` Resets in ${remainingSec}s.` : ""}`,
|
|
9883
|
+
recommendation: "Model will be automatically re-admitted once cooldown/reset expires."
|
|
9884
|
+
});
|
|
9885
|
+
}
|
|
9886
|
+
const cal = evaluateModelCalendar(
|
|
9887
|
+
config.modelAvailabilitySchedule,
|
|
9888
|
+
entry.providerId,
|
|
9889
|
+
entry.model
|
|
9890
|
+
);
|
|
9891
|
+
if (!cal.allowed) {
|
|
9892
|
+
warnings.push({
|
|
9893
|
+
code: "CALENDAR_BLOCKED",
|
|
9894
|
+
severity: "warning",
|
|
9895
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
9896
|
+
message: `Model is blocked by the model availability calendar (${cal.rule?.label ?? "schedule restriction"}).`
|
|
9897
|
+
});
|
|
9898
|
+
}
|
|
9899
|
+
if (opts?.modelContextLimitMap) {
|
|
9900
|
+
const primaryLimit = opts.modelContextLimitMap[`${primary.providerId}/${primary.model}`] ?? opts.modelContextLimitMap[primary.model];
|
|
9901
|
+
const entryLimit = opts.modelContextLimitMap[`${entry.providerId}/${entry.model}`] ?? opts.modelContextLimitMap[entry.model];
|
|
9902
|
+
if (typeof primaryLimit === "number" && typeof entryLimit === "number" && entryLimit > 0 && primaryLimit > 0 && entryLimit < primaryLimit) {
|
|
9903
|
+
warnings.push({
|
|
9904
|
+
code: "CONTEXT_WINDOW_WARNING",
|
|
9905
|
+
severity: "warning",
|
|
9906
|
+
target: `${entry.providerId}/${entry.model}`,
|
|
9907
|
+
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.`,
|
|
9908
|
+
recommendation: `Ensure fallback models have equal or greater context capacity (>= ${primaryLimit.toLocaleString()} tokens) or keep sessions below ${entryLimit.toLocaleString()} tokens.`
|
|
9909
|
+
});
|
|
9910
|
+
}
|
|
9911
|
+
}
|
|
9912
|
+
}
|
|
9913
|
+
const uniqueProviders = Object.freeze([...providersInChain]);
|
|
9914
|
+
const crossProviderCount = uniqueProviders.filter((p) => p !== primary.providerId).length;
|
|
9915
|
+
const hasCritical = warnings.some((w) => w.severity === "critical");
|
|
9916
|
+
const hasWarning = warnings.some((w) => w.severity === "warning");
|
|
9917
|
+
const status = hasCritical ? "critical" : hasWarning ? "warning" : "healthy";
|
|
9918
|
+
return Object.freeze({
|
|
9919
|
+
primary: Object.freeze(primary),
|
|
9920
|
+
activeProfile,
|
|
9921
|
+
explicitChain,
|
|
9922
|
+
effectiveOrder,
|
|
9923
|
+
autoEnabled,
|
|
9924
|
+
status,
|
|
9925
|
+
warnings: Object.freeze(warnings),
|
|
9926
|
+
crossProviderCount,
|
|
9927
|
+
uniqueProviders
|
|
9928
|
+
});
|
|
9929
|
+
}
|
|
9930
|
+
function simulateFallbackFailover(config, opts = {}) {
|
|
9931
|
+
const errorKind = opts.errorKind ?? "rate_limit";
|
|
9932
|
+
const isEligible = isFallbackWorthy(errorKind);
|
|
9933
|
+
const primary = { providerId: config.provider, model: config.model };
|
|
9934
|
+
if (!isEligible) {
|
|
9935
|
+
return Object.freeze({
|
|
9936
|
+
triggeringError: errorKind,
|
|
9937
|
+
isFallbackEligible: false,
|
|
9938
|
+
primary: Object.freeze(primary),
|
|
9939
|
+
steps: Object.freeze([]),
|
|
9940
|
+
finalTarget: null,
|
|
9941
|
+
summary: `Error kind "${errorKind}" is not fallback-worthy (request-shaped error, e.g. context_overflow/auth). The agent loop will not attempt fallback.`
|
|
9942
|
+
});
|
|
9943
|
+
}
|
|
9944
|
+
const mgr = new FallbackProfileManager(config, { statusTracker: opts.statusTracker });
|
|
9945
|
+
const candidates = mgr.resolveCandidates(primary);
|
|
9946
|
+
const steps = [];
|
|
9947
|
+
let finalTarget = null;
|
|
9948
|
+
for (let i = 0; i < candidates.length; i++) {
|
|
9949
|
+
const entry = candidates[i];
|
|
9950
|
+
const key = `${entry.providerId}/${entry.model}`;
|
|
9951
|
+
const providerSwitched = entry.providerId !== primary.providerId;
|
|
9952
|
+
const cal = evaluateModelCalendar(
|
|
9953
|
+
config.modelAvailabilitySchedule,
|
|
9954
|
+
entry.providerId,
|
|
9955
|
+
entry.model
|
|
9956
|
+
);
|
|
9957
|
+
if (!cal.allowed) {
|
|
9958
|
+
steps.push({
|
|
9959
|
+
index: i + 1,
|
|
9960
|
+
providerId: entry.providerId,
|
|
9961
|
+
model: entry.model,
|
|
9962
|
+
skipped: true,
|
|
9963
|
+
reason: `Blocked by model availability calendar (${cal.rule?.label ?? "schedule"})`,
|
|
9964
|
+
providerSwitched
|
|
9965
|
+
});
|
|
9966
|
+
continue;
|
|
9967
|
+
}
|
|
9968
|
+
if (opts.statusTracker && !opts.statusTracker.isAvailable(entry.providerId, entry.model)) {
|
|
9969
|
+
steps.push({
|
|
9970
|
+
index: i + 1,
|
|
9971
|
+
providerId: entry.providerId,
|
|
9972
|
+
model: entry.model,
|
|
9973
|
+
skipped: true,
|
|
9974
|
+
reason: "Quarantined in status tracker waiting room",
|
|
9975
|
+
providerSwitched
|
|
9976
|
+
});
|
|
9977
|
+
continue;
|
|
9978
|
+
}
|
|
9979
|
+
const contextLimit = opts.modelContextLimitMap?.[key];
|
|
9980
|
+
if (typeof opts.currentTokens === "number" && opts.currentTokens > 0 && typeof contextLimit === "number" && contextLimit > 0 && opts.currentTokens > contextLimit) {
|
|
9981
|
+
steps.push({
|
|
9982
|
+
index: i + 1,
|
|
9983
|
+
providerId: entry.providerId,
|
|
9984
|
+
model: entry.model,
|
|
9985
|
+
skipped: true,
|
|
9986
|
+
reason: `Context pre-filter: request tokens (${opts.currentTokens}) exceeds model window (${contextLimit})`,
|
|
9987
|
+
providerSwitched
|
|
9988
|
+
});
|
|
9989
|
+
continue;
|
|
9990
|
+
}
|
|
9991
|
+
const health = mgr.checkProvider(entry.providerId);
|
|
9992
|
+
if (!health.usable) {
|
|
9993
|
+
steps.push({
|
|
9994
|
+
index: i + 1,
|
|
9995
|
+
providerId: entry.providerId,
|
|
9996
|
+
model: entry.model,
|
|
9997
|
+
skipped: true,
|
|
9998
|
+
reason: `Cannot construct provider "${entry.providerId}" (missing API key/endpoint)`,
|
|
9999
|
+
providerSwitched
|
|
10000
|
+
});
|
|
10001
|
+
continue;
|
|
10002
|
+
}
|
|
10003
|
+
steps.push({
|
|
10004
|
+
index: i + 1,
|
|
10005
|
+
providerId: entry.providerId,
|
|
10006
|
+
model: entry.model,
|
|
10007
|
+
skipped: false,
|
|
10008
|
+
providerSwitched
|
|
10009
|
+
});
|
|
10010
|
+
finalTarget = { providerId: entry.providerId, model: entry.model };
|
|
10011
|
+
break;
|
|
10012
|
+
}
|
|
10013
|
+
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.";
|
|
10014
|
+
return Object.freeze({
|
|
10015
|
+
triggeringError: errorKind,
|
|
10016
|
+
isFallbackEligible: true,
|
|
10017
|
+
primary: Object.freeze(primary),
|
|
10018
|
+
steps: Object.freeze(steps),
|
|
10019
|
+
finalTarget: finalTarget ? Object.freeze(finalTarget) : null,
|
|
10020
|
+
summary
|
|
10021
|
+
});
|
|
10022
|
+
}
|
|
10023
|
+
|
|
9684
10024
|
// src/core/fallback-model.ts
|
|
10025
|
+
import { randomUUID as randomUUID6 } from "node:crypto";
|
|
9685
10026
|
function fallbackProfileChain(config, profileName) {
|
|
9686
10027
|
if (!profileName) return [];
|
|
9687
10028
|
const mgr = new FallbackProfileManager(config);
|
|
@@ -9717,9 +10058,15 @@ function effectiveFallbackChain(config) {
|
|
|
9717
10058
|
const mgr = new FallbackProfileManager(config);
|
|
9718
10059
|
return mgr.resolveEffective({
|
|
9719
10060
|
fallbackModels: config.fallbackModels,
|
|
10061
|
+
fallbackProfile: config.fallbackProfile,
|
|
9720
10062
|
fallbackAuto: config.fallbackAuto
|
|
9721
10063
|
}).map((e) => `${e.providerId}/${e.model}`);
|
|
9722
10064
|
}
|
|
10065
|
+
function runtimeFallbackChain(config) {
|
|
10066
|
+
const mgr = new FallbackProfileManager(config);
|
|
10067
|
+
const current = primaryTarget(config);
|
|
10068
|
+
return mgr.resolveCandidates(current, {}).map((e) => `${e.providerId}/${e.model}`);
|
|
10069
|
+
}
|
|
9723
10070
|
var DEFAULT_PRIMARY_COOLDOWN_MS = 6e4;
|
|
9724
10071
|
var DEFAULT_PRIMARY_COOLDOWN_MAX_MS = 10 * 6e4;
|
|
9725
10072
|
var DEFAULT_PRIMARY_RECOVERY_SUCCESSES = 2;
|
|
@@ -9759,7 +10106,11 @@ function createFallbackModelExtension(deps) {
|
|
|
9759
10106
|
let blockedPrimary;
|
|
9760
10107
|
let primaryBlockedUntil = 0;
|
|
9761
10108
|
const now = () => deps.now?.() ?? Date.now();
|
|
9762
|
-
const
|
|
10109
|
+
const liveStickiness = () => deps.getConfig().fallbackStickiness;
|
|
10110
|
+
const cooldownBase = () => Math.max(
|
|
10111
|
+
0,
|
|
10112
|
+
deps.primaryCooldownMs ?? liveStickiness()?.primaryProbeInterval ?? DEFAULT_PRIMARY_COOLDOWN_MS
|
|
10113
|
+
);
|
|
9763
10114
|
const cooldownMax = () => Math.max(cooldownBase(), deps.primaryCooldownMaxMs ?? DEFAULT_PRIMARY_COOLDOWN_MAX_MS);
|
|
9764
10115
|
const selectedPrimary = (cfg) => deps.getPrimaryTarget?.() ?? primaryTarget(cfg);
|
|
9765
10116
|
const primaryInCooldown = (cfg) => sameTarget(blockedPrimary, selectedPrimary(cfg)) && now() < primaryBlockedUntil;
|
|
@@ -9778,7 +10129,7 @@ function createFallbackModelExtension(deps) {
|
|
|
9778
10129
|
primaryBlockedUntil = now() + Math.min(cooldownMax(), base * multiplier);
|
|
9779
10130
|
};
|
|
9780
10131
|
const recoveryTarget = () => Math.max(1, deps.primaryRecoverySuccesses ?? DEFAULT_PRIMARY_RECOVERY_SUCCESSES);
|
|
9781
|
-
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? 0);
|
|
10132
|
+
const stickyTarget = () => Math.max(0, deps.stickyFallbackTurns ?? liveStickiness()?.stickyFallbackTurns ?? 0);
|
|
9782
10133
|
const inStickyWindow = () => stickyTurnsElapsed < stickyTarget();
|
|
9783
10134
|
const onPrimarySuccess = (cfg) => {
|
|
9784
10135
|
if (!sameTarget(blockedPrimary, selectedPrimary(cfg))) return;
|
|
@@ -9806,7 +10157,25 @@ function createFallbackModelExtension(deps) {
|
|
|
9806
10157
|
if (!evaluateModelCalendar(cfg.modelAvailabilitySchedule, primary.providerId, primary.model).allowed || deps.statusTracker && !deps.statusTracker.isAvailable(primary.providerId, primary.model))
|
|
9807
10158
|
return;
|
|
9808
10159
|
try {
|
|
9809
|
-
|
|
10160
|
+
const primaryProvider = await deps.buildProvider(primary.providerId, primary.model);
|
|
10161
|
+
const currentTokens = ctx.lastRequestTokens;
|
|
10162
|
+
const maxContext = maxContextOf(primaryProvider);
|
|
10163
|
+
if (maxContext > 0 && typeof currentTokens === "number" && currentTokens > maxContext) {
|
|
10164
|
+
deps.events.emit("provider.primary_probe_context_blocked", {
|
|
10165
|
+
sessionId: resolveEventSessionId(ctx),
|
|
10166
|
+
providerId: primary.providerId,
|
|
10167
|
+
model: primary.model,
|
|
10168
|
+
currentTokens,
|
|
10169
|
+
maxContext,
|
|
10170
|
+
timestamp: now()
|
|
10171
|
+
});
|
|
10172
|
+
deps.logger?.warn(
|
|
10173
|
+
`fallback-model: deferring primary probe "${primary.providerId}/${primary.model}" \u2014 context (${currentTokens}) exceeds target window (${maxContext})`
|
|
10174
|
+
);
|
|
10175
|
+
markPrimaryFailure(cfg);
|
|
10176
|
+
return;
|
|
10177
|
+
}
|
|
10178
|
+
ctx.provider = primaryProvider;
|
|
9810
10179
|
ctx.model = primary.model;
|
|
9811
10180
|
await deps.onModelSwitch?.(primary.providerId, primary.model);
|
|
9812
10181
|
primaryBlockedUntil = 0;
|
|
@@ -9920,9 +10289,10 @@ function createFallbackModelExtension(deps) {
|
|
|
9920
10289
|
const status = shouldFallback(firstErr_);
|
|
9921
10290
|
if (status === null) throw firstErr_;
|
|
9922
10291
|
let gateRequestId;
|
|
9923
|
-
|
|
10292
|
+
const configuredGateSeconds = cfg.fallbackGateSeconds ?? deps.fallbackGateSeconds;
|
|
10293
|
+
if (configuredGateSeconds !== 0 && deps.fallbackGate && usableChain.length > 0) {
|
|
9924
10294
|
gateRequestId = randomUUID6();
|
|
9925
|
-
const autoSwitchSeconds = Math.max(1,
|
|
10295
|
+
const autoSwitchSeconds = Math.max(1, configuredGateSeconds ?? 7);
|
|
9926
10296
|
const gateCandidates = usableChain.map((e) => ({
|
|
9927
10297
|
providerId: e.providerId,
|
|
9928
10298
|
model: e.model
|
|
@@ -10749,6 +11119,7 @@ function shellGuidanceBlock(shell, detail) {
|
|
|
10749
11119
|
var MAX_ENVIRONMENT_CACHE_ENTRIES = 16;
|
|
10750
11120
|
async function buildEnvironment(ctx, env) {
|
|
10751
11121
|
const modelCapabilities = env.modelCapabilities;
|
|
11122
|
+
const today = env.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10752
11123
|
const cacheKey = [
|
|
10753
11124
|
ctx.projectRoot,
|
|
10754
11125
|
ctx.provider ?? "",
|
|
@@ -10757,7 +11128,9 @@ async function buildEnvironment(ctx, env) {
|
|
|
10757
11128
|
modelCapabilities?.supportsTools ? 1 : 0,
|
|
10758
11129
|
modelCapabilities?.supportsVision ? 1 : 0,
|
|
10759
11130
|
modelCapabilities?.supportsReasoning ? 1 : 0,
|
|
10760
|
-
env.skillCache ?? ""
|
|
11131
|
+
env.skillCache ?? "",
|
|
11132
|
+
today,
|
|
11133
|
+
env.modeId ?? ""
|
|
10761
11134
|
].join("\0");
|
|
10762
11135
|
const cached = env.envCacheByRoot.get(cacheKey);
|
|
10763
11136
|
if (cached) {
|
|
@@ -10765,7 +11138,6 @@ async function buildEnvironment(ctx, env) {
|
|
|
10765
11138
|
env.envCacheByRoot.set(cacheKey, cached);
|
|
10766
11139
|
return cached;
|
|
10767
11140
|
}
|
|
10768
|
-
const today = env.todayIso ?? (/* @__PURE__ */ new Date()).toISOString().slice(0, 10);
|
|
10769
11141
|
const platform2 = `${os3.platform()} ${os3.release()}`;
|
|
10770
11142
|
const effShell = effectiveShell(os3.platform(), process.env["WRONGSTACK_SHELL"]);
|
|
10771
11143
|
const shell = effShell === "posix" ? process.env.SHELL ?? process.env.ComSpec ?? "unknown" : SHELL_DISPLAY[effShell];
|
|
@@ -11611,7 +11983,9 @@ ${peers}
|
|
|
11611
11983
|
const tpl = tplCtx ?? this.templateContext(ctx);
|
|
11612
11984
|
const section = (key, vars = {}) => instructionSection(instructions, key, vars, tpl);
|
|
11613
11985
|
const tier = this.tier;
|
|
11614
|
-
|
|
11986
|
+
const subagent = ctx.subagent === true;
|
|
11987
|
+
const maxContextTokens = this.modelCapabilities()?.maxContextTokens ?? 0;
|
|
11988
|
+
if (this._toolsUsageCache?.toolsRef === tools && this._toolsUsageCache?.tier === tier && this._toolsUsageCache?.subagent === subagent && this._toolsUsageCache?.maxContextTokens === maxContextTokens) {
|
|
11615
11989
|
return this._toolsUsageCache.text;
|
|
11616
11990
|
}
|
|
11617
11991
|
const byCat = /* @__PURE__ */ new Map();
|
|
@@ -11738,7 +12112,7 @@ ${hint.trim()}`);
|
|
|
11738
12112
|
}
|
|
11739
12113
|
}
|
|
11740
12114
|
const text = lines.join("\n");
|
|
11741
|
-
this._toolsUsageCache = { toolsRef: tools, tier, text };
|
|
12115
|
+
this._toolsUsageCache = { toolsRef: tools, tier, subagent, maxContextTokens, text };
|
|
11742
12116
|
return text;
|
|
11743
12117
|
}
|
|
11744
12118
|
renderOnlineAgents(agents) {
|
|
@@ -11801,6 +12175,7 @@ export {
|
|
|
11801
12175
|
createDefaultPipelines,
|
|
11802
12176
|
createFallbackModelExtension,
|
|
11803
12177
|
detectContinueIntent,
|
|
12178
|
+
diagnoseFallbackConfig,
|
|
11804
12179
|
effectiveFallbackChain,
|
|
11805
12180
|
fallbackProfileChain,
|
|
11806
12181
|
formatModelRef,
|
|
@@ -11816,8 +12191,10 @@ export {
|
|
|
11816
12191
|
renderNextStepsBlock,
|
|
11817
12192
|
resolveContinuation,
|
|
11818
12193
|
runProviderWithRetry,
|
|
12194
|
+
runtimeFallbackChain,
|
|
11819
12195
|
setBtwNote,
|
|
11820
12196
|
setQueuedMessagesSnapshot,
|
|
12197
|
+
simulateFallbackFailover,
|
|
11821
12198
|
smartDefaultFallbackChain,
|
|
11822
12199
|
wrapAsState,
|
|
11823
12200
|
writePendingNextSteps
|