evil-omo 3.12.0 → 3.12.3
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/cli/index.js +174 -91
- package/dist/config/schema/background-task.d.ts +2 -2
- package/dist/config/schema/evil-omo-config.d.ts +2 -2
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/create-hooks.d.ts +1 -0
- package/dist/evil-omo.schema.json +4 -6
- package/dist/features/background-agent/constants.d.ts +2 -2
- package/dist/features/background-agent/loop-detector.d.ts +4 -5
- package/dist/features/background-agent/manager.d.ts +1 -0
- package/dist/features/background-agent/session-status-classifier.d.ts +2 -0
- package/dist/features/background-agent/types.d.ts +4 -4
- package/dist/features/builtin-commands/templates/start-work.d.ts +1 -1
- package/dist/hooks/index.d.ts +1 -0
- package/dist/hooks/todo-description-override/description.d.ts +1 -0
- package/dist/hooks/todo-description-override/hook.d.ts +8 -0
- package/dist/hooks/todo-description-override/index.d.ts +1 -0
- package/dist/index.js +382 -229
- package/dist/plugin/hooks/create-core-hooks.d.ts +1 -0
- package/dist/plugin/hooks/create-tool-guard-hooks.d.ts +2 -1
- package/dist/shared/connected-providers-cache.d.ts +26 -29
- package/package.json +12 -12
package/dist/index.js
CHANGED
|
@@ -16190,7 +16190,7 @@ async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0,
|
|
|
16190
16190
|
}
|
|
16191
16191
|
let resolved = text;
|
|
16192
16192
|
for (const [pattern, replacement] of replacements.entries()) {
|
|
16193
|
-
resolved = resolved.
|
|
16193
|
+
resolved = resolved.replaceAll(pattern, replacement);
|
|
16194
16194
|
}
|
|
16195
16195
|
if (findFileReferences(resolved).length > 0 && depth + 1 < maxDepth) {
|
|
16196
16196
|
return resolveFileReferencesInText(resolved, cwd, depth + 1, maxDepth);
|
|
@@ -16289,6 +16289,7 @@ function transformToolName(toolName) {
|
|
|
16289
16289
|
function escapeRegexExceptAsterisk(str2) {
|
|
16290
16290
|
return str2.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
16291
16291
|
}
|
|
16292
|
+
var regexCache = new Map;
|
|
16292
16293
|
function matchesToolMatcher(toolName, matcher) {
|
|
16293
16294
|
if (!matcher) {
|
|
16294
16295
|
return true;
|
|
@@ -16296,8 +16297,12 @@ function matchesToolMatcher(toolName, matcher) {
|
|
|
16296
16297
|
const patterns = matcher.split("|").map((p) => p.trim());
|
|
16297
16298
|
return patterns.some((p) => {
|
|
16298
16299
|
if (p.includes("*")) {
|
|
16299
|
-
|
|
16300
|
-
|
|
16300
|
+
let regex = regexCache.get(p);
|
|
16301
|
+
if (!regex) {
|
|
16302
|
+
const escaped = escapeRegexExceptAsterisk(p);
|
|
16303
|
+
regex = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`, "i");
|
|
16304
|
+
regexCache.set(p, regex);
|
|
16305
|
+
}
|
|
16301
16306
|
return regex.test(toolName);
|
|
16302
16307
|
}
|
|
16303
16308
|
return p.toLowerCase() === toolName.toLowerCase();
|
|
@@ -17653,120 +17658,156 @@ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync
|
|
|
17653
17658
|
import { join as join10 } from "path";
|
|
17654
17659
|
var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json";
|
|
17655
17660
|
var PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
|
|
17656
|
-
function
|
|
17657
|
-
|
|
17658
|
-
|
|
17659
|
-
|
|
17660
|
-
|
|
17661
|
-
|
|
17662
|
-
|
|
17663
|
-
|
|
17664
|
-
|
|
17665
|
-
|
|
17666
|
-
|
|
17667
|
-
|
|
17668
|
-
|
|
17669
|
-
|
|
17661
|
+
function createConnectedProvidersCacheStore(getCacheDir2 = getOmoOpenCodeCacheDir) {
|
|
17662
|
+
function getCacheFilePath(filename) {
|
|
17663
|
+
return join10(getCacheDir2(), filename);
|
|
17664
|
+
}
|
|
17665
|
+
let memConnected;
|
|
17666
|
+
let memProviderModels;
|
|
17667
|
+
function ensureCacheDir2() {
|
|
17668
|
+
const cacheDir = getCacheDir2();
|
|
17669
|
+
if (!existsSync8(cacheDir)) {
|
|
17670
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
17671
|
+
}
|
|
17672
|
+
}
|
|
17673
|
+
function readConnectedProvidersCache() {
|
|
17674
|
+
if (memConnected !== undefined)
|
|
17675
|
+
return memConnected;
|
|
17676
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17677
|
+
if (!existsSync8(cacheFile)) {
|
|
17678
|
+
log("[connected-providers-cache] Cache file not found", { cacheFile });
|
|
17679
|
+
memConnected = null;
|
|
17680
|
+
return null;
|
|
17681
|
+
}
|
|
17682
|
+
try {
|
|
17683
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17684
|
+
const data = JSON.parse(content);
|
|
17685
|
+
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
|
|
17686
|
+
memConnected = data.connected;
|
|
17687
|
+
return data.connected;
|
|
17688
|
+
} catch (err) {
|
|
17689
|
+
log("[connected-providers-cache] Error reading cache", { error: String(err) });
|
|
17690
|
+
memConnected = null;
|
|
17691
|
+
return null;
|
|
17692
|
+
}
|
|
17670
17693
|
}
|
|
17671
|
-
|
|
17672
|
-
const
|
|
17673
|
-
|
|
17674
|
-
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
|
|
17675
|
-
return data.connected;
|
|
17676
|
-
} catch (err) {
|
|
17677
|
-
log("[connected-providers-cache] Error reading cache", { error: String(err) });
|
|
17678
|
-
return null;
|
|
17694
|
+
function hasConnectedProvidersCache() {
|
|
17695
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17696
|
+
return existsSync8(cacheFile);
|
|
17679
17697
|
}
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17686
|
-
|
|
17687
|
-
|
|
17688
|
-
|
|
17689
|
-
|
|
17690
|
-
|
|
17691
|
-
|
|
17692
|
-
|
|
17693
|
-
|
|
17694
|
-
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17695
|
-
} catch (err) {
|
|
17696
|
-
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17697
|
-
}
|
|
17698
|
-
}
|
|
17699
|
-
function readProviderModelsCache() {
|
|
17700
|
-
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17701
|
-
if (!existsSync8(cacheFile)) {
|
|
17702
|
-
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
|
|
17703
|
-
return null;
|
|
17698
|
+
function writeConnectedProvidersCache(connected) {
|
|
17699
|
+
ensureCacheDir2();
|
|
17700
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17701
|
+
const data = {
|
|
17702
|
+
connected,
|
|
17703
|
+
updatedAt: new Date().toISOString()
|
|
17704
|
+
};
|
|
17705
|
+
try {
|
|
17706
|
+
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
17707
|
+
memConnected = connected;
|
|
17708
|
+
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17709
|
+
} catch (err) {
|
|
17710
|
+
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17711
|
+
}
|
|
17704
17712
|
}
|
|
17705
|
-
|
|
17706
|
-
|
|
17707
|
-
|
|
17708
|
-
|
|
17709
|
-
|
|
17710
|
-
|
|
17711
|
-
|
|
17712
|
-
|
|
17713
|
-
|
|
17714
|
-
|
|
17715
|
-
|
|
17713
|
+
function readProviderModelsCache() {
|
|
17714
|
+
if (memProviderModels !== undefined)
|
|
17715
|
+
return memProviderModels;
|
|
17716
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17717
|
+
if (!existsSync8(cacheFile)) {
|
|
17718
|
+
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
|
|
17719
|
+
memProviderModels = null;
|
|
17720
|
+
return null;
|
|
17721
|
+
}
|
|
17722
|
+
try {
|
|
17723
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17724
|
+
const data = JSON.parse(content);
|
|
17725
|
+
log("[connected-providers-cache] Read provider-models cache", {
|
|
17726
|
+
providerCount: Object.keys(data.models).length,
|
|
17727
|
+
updatedAt: data.updatedAt
|
|
17728
|
+
});
|
|
17729
|
+
memProviderModels = data;
|
|
17730
|
+
return data;
|
|
17731
|
+
} catch (err) {
|
|
17732
|
+
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) });
|
|
17733
|
+
memProviderModels = null;
|
|
17734
|
+
return null;
|
|
17735
|
+
}
|
|
17716
17736
|
}
|
|
17717
|
-
|
|
17718
|
-
|
|
17719
|
-
|
|
17720
|
-
return existsSync8(cacheFile);
|
|
17721
|
-
}
|
|
17722
|
-
function writeProviderModelsCache(data) {
|
|
17723
|
-
ensureCacheDir2();
|
|
17724
|
-
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17725
|
-
const cacheData = {
|
|
17726
|
-
...data,
|
|
17727
|
-
updatedAt: new Date().toISOString()
|
|
17728
|
-
};
|
|
17729
|
-
try {
|
|
17730
|
-
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17731
|
-
log("[connected-providers-cache] Provider-models cache written", {
|
|
17732
|
-
providerCount: Object.keys(data.models).length
|
|
17733
|
-
});
|
|
17734
|
-
} catch (err) {
|
|
17735
|
-
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17737
|
+
function hasProviderModelsCache() {
|
|
17738
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17739
|
+
return existsSync8(cacheFile);
|
|
17736
17740
|
}
|
|
17737
|
-
|
|
17738
|
-
|
|
17739
|
-
|
|
17740
|
-
|
|
17741
|
-
|
|
17741
|
+
function writeProviderModelsCache(data) {
|
|
17742
|
+
ensureCacheDir2();
|
|
17743
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17744
|
+
const cacheData = {
|
|
17745
|
+
...data,
|
|
17746
|
+
updatedAt: new Date().toISOString()
|
|
17747
|
+
};
|
|
17748
|
+
try {
|
|
17749
|
+
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17750
|
+
memProviderModels = cacheData;
|
|
17751
|
+
log("[connected-providers-cache] Provider-models cache written", {
|
|
17752
|
+
providerCount: Object.keys(data.models).length
|
|
17753
|
+
});
|
|
17754
|
+
} catch (err) {
|
|
17755
|
+
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17756
|
+
}
|
|
17742
17757
|
}
|
|
17743
|
-
|
|
17744
|
-
|
|
17745
|
-
|
|
17746
|
-
|
|
17747
|
-
|
|
17748
|
-
|
|
17749
|
-
|
|
17750
|
-
|
|
17751
|
-
|
|
17752
|
-
|
|
17753
|
-
|
|
17754
|
-
|
|
17758
|
+
async function updateConnectedProvidersCache(client) {
|
|
17759
|
+
if (!client?.provider?.list) {
|
|
17760
|
+
log("[connected-providers-cache] client.provider.list not available");
|
|
17761
|
+
return;
|
|
17762
|
+
}
|
|
17763
|
+
try {
|
|
17764
|
+
const result = await client.provider.list();
|
|
17765
|
+
const connected = result.data?.connected ?? [];
|
|
17766
|
+
log("[connected-providers-cache] Fetched connected providers", {
|
|
17767
|
+
count: connected.length,
|
|
17768
|
+
providers: connected
|
|
17769
|
+
});
|
|
17770
|
+
writeConnectedProvidersCache(connected);
|
|
17771
|
+
const modelsByProvider = {};
|
|
17772
|
+
const allProviders = result.data?.all ?? [];
|
|
17773
|
+
for (const provider of allProviders) {
|
|
17774
|
+
if (provider.models) {
|
|
17775
|
+
const modelIds = Object.keys(provider.models);
|
|
17776
|
+
if (modelIds.length > 0) {
|
|
17777
|
+
modelsByProvider[provider.id] = modelIds;
|
|
17778
|
+
}
|
|
17755
17779
|
}
|
|
17756
17780
|
}
|
|
17781
|
+
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17782
|
+
providerCount: Object.keys(modelsByProvider).length,
|
|
17783
|
+
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17784
|
+
});
|
|
17785
|
+
writeProviderModelsCache({
|
|
17786
|
+
models: modelsByProvider,
|
|
17787
|
+
connected
|
|
17788
|
+
});
|
|
17789
|
+
} catch (err) {
|
|
17790
|
+
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17757
17791
|
}
|
|
17758
|
-
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17759
|
-
providerCount: Object.keys(modelsByProvider).length,
|
|
17760
|
-
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17761
|
-
});
|
|
17762
|
-
writeProviderModelsCache({
|
|
17763
|
-
models: modelsByProvider,
|
|
17764
|
-
connected
|
|
17765
|
-
});
|
|
17766
|
-
} catch (err) {
|
|
17767
|
-
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17768
17792
|
}
|
|
17793
|
+
return {
|
|
17794
|
+
readConnectedProvidersCache,
|
|
17795
|
+
hasConnectedProvidersCache,
|
|
17796
|
+
readProviderModelsCache,
|
|
17797
|
+
hasProviderModelsCache,
|
|
17798
|
+
writeProviderModelsCache,
|
|
17799
|
+
updateConnectedProvidersCache
|
|
17800
|
+
};
|
|
17769
17801
|
}
|
|
17802
|
+
var defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(() => getOmoOpenCodeCacheDir());
|
|
17803
|
+
var {
|
|
17804
|
+
readConnectedProvidersCache,
|
|
17805
|
+
hasConnectedProvidersCache,
|
|
17806
|
+
readProviderModelsCache,
|
|
17807
|
+
hasProviderModelsCache,
|
|
17808
|
+
writeProviderModelsCache,
|
|
17809
|
+
updateConnectedProvidersCache
|
|
17810
|
+
} = defaultConnectedProvidersCacheStore;
|
|
17770
17811
|
|
|
17771
17812
|
// src/shared/model-availability.ts
|
|
17772
17813
|
init_logger();
|
|
@@ -20491,12 +20532,14 @@ async function handleSessionIdle(args) {
|
|
|
20491
20532
|
return;
|
|
20492
20533
|
}
|
|
20493
20534
|
if (!todos || todos.length === 0) {
|
|
20535
|
+
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20494
20536
|
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20495
20537
|
log(`[${HOOK_NAME}] No todos`, { sessionID });
|
|
20496
20538
|
return;
|
|
20497
20539
|
}
|
|
20498
20540
|
const incompleteCount = getIncompleteCount(todos);
|
|
20499
20541
|
if (incompleteCount === 0) {
|
|
20542
|
+
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20500
20543
|
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20501
20544
|
log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
|
|
20502
20545
|
return;
|
|
@@ -20510,20 +20553,12 @@ async function handleSessionIdle(args) {
|
|
|
20510
20553
|
log(`[${HOOK_NAME}] Reset consecutive failures after recovery window`, { sessionID, failureResetWindowMs: FAILURE_RESET_WINDOW_MS });
|
|
20511
20554
|
}
|
|
20512
20555
|
if (state2.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
20513
|
-
log(`[${HOOK_NAME}] Skipped: max consecutive failures reached`, {
|
|
20514
|
-
sessionID,
|
|
20515
|
-
consecutiveFailures: state2.consecutiveFailures,
|
|
20516
|
-
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
|
|
20517
|
-
});
|
|
20556
|
+
log(`[${HOOK_NAME}] Skipped: max consecutive failures reached`, { sessionID, consecutiveFailures: state2.consecutiveFailures });
|
|
20518
20557
|
return;
|
|
20519
20558
|
}
|
|
20520
20559
|
const effectiveCooldown = CONTINUATION_COOLDOWN_MS * Math.pow(2, Math.min(state2.consecutiveFailures, 5));
|
|
20521
20560
|
if (state2.lastInjectedAt && Date.now() - state2.lastInjectedAt < effectiveCooldown) {
|
|
20522
|
-
log(`[${HOOK_NAME}] Skipped: cooldown active`, {
|
|
20523
|
-
sessionID,
|
|
20524
|
-
effectiveCooldown,
|
|
20525
|
-
consecutiveFailures: state2.consecutiveFailures
|
|
20526
|
-
});
|
|
20561
|
+
log(`[${HOOK_NAME}] Skipped: cooldown active`, { sessionID, effectiveCooldown, consecutiveFailures: state2.consecutiveFailures });
|
|
20527
20562
|
return;
|
|
20528
20563
|
}
|
|
20529
20564
|
let resolvedInfo;
|
|
@@ -36377,6 +36412,15 @@ function takePendingCall(callID) {
|
|
|
36377
36412
|
import * as fs6 from "fs";
|
|
36378
36413
|
import { tmpdir as tmpdir4 } from "os";
|
|
36379
36414
|
import { join as join31 } from "path";
|
|
36415
|
+
var ApplyPatchMetadataSchema = zod_default.object({
|
|
36416
|
+
files: zod_default.array(zod_default.object({
|
|
36417
|
+
filePath: zod_default.string(),
|
|
36418
|
+
movePath: zod_default.string().optional(),
|
|
36419
|
+
before: zod_default.string(),
|
|
36420
|
+
after: zod_default.string(),
|
|
36421
|
+
type: zod_default.string().optional()
|
|
36422
|
+
}))
|
|
36423
|
+
});
|
|
36380
36424
|
var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
36381
36425
|
var DEBUG_FILE3 = join31(tmpdir4(), "comment-checker-debug.log");
|
|
36382
36426
|
function debugLog3(...args) {
|
|
@@ -36437,15 +36481,6 @@ function createCommentCheckerHooks(config2) {
|
|
|
36437
36481
|
debugLog3("skipping due to tool failure in output");
|
|
36438
36482
|
return;
|
|
36439
36483
|
}
|
|
36440
|
-
const ApplyPatchMetadataSchema = zod_default.object({
|
|
36441
|
-
files: zod_default.array(zod_default.object({
|
|
36442
|
-
filePath: zod_default.string(),
|
|
36443
|
-
movePath: zod_default.string().optional(),
|
|
36444
|
-
before: zod_default.string(),
|
|
36445
|
-
after: zod_default.string(),
|
|
36446
|
-
type: zod_default.string().optional()
|
|
36447
|
-
}))
|
|
36448
|
-
});
|
|
36449
36484
|
if (toolLower === "apply_patch") {
|
|
36450
36485
|
const parsed = ApplyPatchMetadataSchema.safeParse(output.metadata);
|
|
36451
36486
|
if (!parsed.success) {
|
|
@@ -36916,7 +36951,7 @@ function isTokenLimitError(text) {
|
|
|
36916
36951
|
return false;
|
|
36917
36952
|
}
|
|
36918
36953
|
const lower = text.toLowerCase();
|
|
36919
|
-
return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw
|
|
36954
|
+
return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
36920
36955
|
}
|
|
36921
36956
|
function parseAnthropicTokenLimitError(err) {
|
|
36922
36957
|
try {
|
|
@@ -38334,7 +38369,6 @@ function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
|
|
|
38334
38369
|
};
|
|
38335
38370
|
}
|
|
38336
38371
|
// src/hooks/think-mode/detector.ts
|
|
38337
|
-
var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
|
|
38338
38372
|
var MULTILINGUAL_KEYWORDS = [
|
|
38339
38373
|
"\uC0DD\uAC01",
|
|
38340
38374
|
"\uAC80\uD1A0",
|
|
@@ -38420,8 +38454,7 @@ var MULTILINGUAL_KEYWORDS = [
|
|
|
38420
38454
|
"fikir",
|
|
38421
38455
|
"berfikir"
|
|
38422
38456
|
];
|
|
38423
|
-
var
|
|
38424
|
-
var THINK_PATTERNS = [...ENGLISH_PATTERNS, ...MULTILINGUAL_PATTERNS];
|
|
38457
|
+
var COMBINED_THINK_PATTERN = new RegExp(`\\b(?:ultrathink|think)\\b|${MULTILINGUAL_KEYWORDS.join("|")}`, "i");
|
|
38425
38458
|
var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
38426
38459
|
var INLINE_CODE_PATTERN = /`[^`]+`/g;
|
|
38427
38460
|
function removeCodeBlocks(text) {
|
|
@@ -38429,7 +38462,7 @@ function removeCodeBlocks(text) {
|
|
|
38429
38462
|
}
|
|
38430
38463
|
function detectThinkKeyword(text) {
|
|
38431
38464
|
const textWithoutCode = removeCodeBlocks(text);
|
|
38432
|
-
return
|
|
38465
|
+
return COMBINED_THINK_PATTERN.test(textWithoutCode);
|
|
38433
38466
|
}
|
|
38434
38467
|
function extractPromptText(parts) {
|
|
38435
38468
|
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
|
|
@@ -39086,16 +39119,16 @@ async function loadPluginExtendedConfig() {
|
|
|
39086
39119
|
}
|
|
39087
39120
|
return merged;
|
|
39088
39121
|
}
|
|
39089
|
-
var
|
|
39122
|
+
var regexCache2 = new Map;
|
|
39090
39123
|
function getRegex(pattern) {
|
|
39091
|
-
let regex =
|
|
39124
|
+
let regex = regexCache2.get(pattern);
|
|
39092
39125
|
if (!regex) {
|
|
39093
39126
|
try {
|
|
39094
39127
|
regex = new RegExp(pattern);
|
|
39095
|
-
|
|
39128
|
+
regexCache2.set(pattern, regex);
|
|
39096
39129
|
} catch {
|
|
39097
39130
|
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
39098
|
-
|
|
39131
|
+
regexCache2.set(pattern, regex);
|
|
39099
39132
|
}
|
|
39100
39133
|
}
|
|
39101
39134
|
return regex;
|
|
@@ -39961,8 +39994,6 @@ function createToolExecuteAfterHandler(ctx, config2) {
|
|
|
39961
39994
|
if (!output) {
|
|
39962
39995
|
return;
|
|
39963
39996
|
}
|
|
39964
|
-
const claudeConfig = await loadClaudeHooksConfig();
|
|
39965
|
-
const extendedConfig = await loadPluginExtendedConfig();
|
|
39966
39997
|
const cachedInput = getToolInput(input.sessionID, input.tool, input.callID) || {};
|
|
39967
39998
|
appendTranscriptEntry(input.sessionID, {
|
|
39968
39999
|
type: "tool_result",
|
|
@@ -39974,6 +40005,8 @@ function createToolExecuteAfterHandler(ctx, config2) {
|
|
|
39974
40005
|
if (isHookDisabled(config2, "PostToolUse")) {
|
|
39975
40006
|
return;
|
|
39976
40007
|
}
|
|
40008
|
+
const claudeConfig = await loadClaudeHooksConfig();
|
|
40009
|
+
const extendedConfig = await loadPluginExtendedConfig();
|
|
39977
40010
|
const postClient = {
|
|
39978
40011
|
session: {
|
|
39979
40012
|
messages: (opts) => ctx.client.session.messages(opts)
|
|
@@ -40154,8 +40187,6 @@ function createToolExecuteBeforeHandler(ctx, config2) {
|
|
|
40154
40187
|
output.args.todos = parsed;
|
|
40155
40188
|
log("todowrite: parsed todos string to array", { sessionID: input.sessionID });
|
|
40156
40189
|
}
|
|
40157
|
-
const claudeConfig = await loadClaudeHooksConfig();
|
|
40158
|
-
const extendedConfig = await loadPluginExtendedConfig();
|
|
40159
40190
|
appendTranscriptEntry(input.sessionID, {
|
|
40160
40191
|
type: "tool_use",
|
|
40161
40192
|
timestamp: new Date().toISOString(),
|
|
@@ -40166,6 +40197,8 @@ function createToolExecuteBeforeHandler(ctx, config2) {
|
|
|
40166
40197
|
if (isHookDisabled(config2, "PreToolUse")) {
|
|
40167
40198
|
return;
|
|
40168
40199
|
}
|
|
40200
|
+
const claudeConfig = await loadClaudeHooksConfig();
|
|
40201
|
+
const extendedConfig = await loadPluginExtendedConfig();
|
|
40169
40202
|
const preCtx = {
|
|
40170
40203
|
sessionId: input.sessionID,
|
|
40171
40204
|
toolName: input.tool,
|
|
@@ -43759,6 +43792,9 @@ async function injectContinuationPrompt(ctx, options) {
|
|
|
43759
43792
|
async function handleDetectedCompletion(ctx, input) {
|
|
43760
43793
|
const { sessionID, state: state3, loopState, directory, apiTimeoutMs } = input;
|
|
43761
43794
|
if (state3.ultrawork && !state3.verification_pending) {
|
|
43795
|
+
if (state3.verification_session_id) {
|
|
43796
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
43797
|
+
}
|
|
43762
43798
|
const verificationState = loopState.markVerificationPending(sessionID);
|
|
43763
43799
|
if (!verificationState) {
|
|
43764
43800
|
log(`[${HOOK_NAME3}] Failed to transition ultrawork loop to verification`, {
|
|
@@ -43995,6 +44031,9 @@ async function handleFailedVerification(ctx, input) {
|
|
|
43995
44031
|
});
|
|
43996
44032
|
return false;
|
|
43997
44033
|
}
|
|
44034
|
+
if (state3.verification_session_id) {
|
|
44035
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
44036
|
+
}
|
|
43998
44037
|
const resumedState = loopState.restartAfterFailedVerification(parentSessionID, messageCountAtStart);
|
|
43999
44038
|
if (!resumedState) {
|
|
44000
44039
|
log(`[${HOOK_NAME3}] Failed to restart loop after verification failure`, {
|
|
@@ -47600,9 +47639,9 @@ var BabysittingConfigSchema = exports_external.object({
|
|
|
47600
47639
|
});
|
|
47601
47640
|
// src/config/schema/background-task.ts
|
|
47602
47641
|
var CircuitBreakerConfigSchema = exports_external.object({
|
|
47642
|
+
enabled: exports_external.boolean().optional(),
|
|
47603
47643
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
47604
|
-
|
|
47605
|
-
repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
|
|
47644
|
+
consecutiveThreshold: exports_external.number().int().min(5).optional()
|
|
47606
47645
|
});
|
|
47607
47646
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
47608
47647
|
defaultConcurrency: exports_external.number().min(1).optional(),
|
|
@@ -47798,7 +47837,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
47798
47837
|
"write-existing-file-guard",
|
|
47799
47838
|
"anthropic-effort",
|
|
47800
47839
|
"hashline-read-enhancer",
|
|
47801
|
-
"read-image-resizer"
|
|
47840
|
+
"read-image-resizer",
|
|
47841
|
+
"todo-description-override"
|
|
47802
47842
|
]);
|
|
47803
47843
|
// src/config/schema/notification.ts
|
|
47804
47844
|
var NotificationConfigSchema = exports_external.object({
|
|
@@ -49201,7 +49241,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49201
49241
|
- \`--worktree <path>\` (optional): absolute path to an existing git worktree to work in
|
|
49202
49242
|
- If specified and valid: hook pre-sets worktree_path in boulder.json
|
|
49203
49243
|
- If specified but invalid: you must run \`git worktree add <path> <branch>\` first
|
|
49204
|
-
- If omitted:
|
|
49244
|
+
- If omitted: work directly in the current project directory (no worktree)
|
|
49205
49245
|
|
|
49206
49246
|
## WHAT TO DO
|
|
49207
49247
|
|
|
@@ -49218,7 +49258,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49218
49258
|
- If ONE plan: auto-select it
|
|
49219
49259
|
- If MULTIPLE plans: show list with timestamps, ask user to select
|
|
49220
49260
|
|
|
49221
|
-
4. **Worktree Setup** (when \`worktree_path\` not already set in boulder.json):
|
|
49261
|
+
4. **Worktree Setup** (ONLY when \`--worktree\` was explicitly specified and \`worktree_path\` not already set in boulder.json):
|
|
49222
49262
|
1. \`git worktree list --porcelain\` \u2014 see available worktrees
|
|
49223
49263
|
2. Create: \`git worktree add <absolute-path> <branch-or-HEAD>\`
|
|
49224
49264
|
3. Update boulder.json to add \`"worktree_path": "<absolute-path>"\`
|
|
@@ -49280,9 +49320,41 @@ Reading plan and beginning execution...
|
|
|
49280
49320
|
|
|
49281
49321
|
- The session_id is injected by the hook - use it directly
|
|
49282
49322
|
- Always update boulder.json BEFORE starting work
|
|
49283
|
-
-
|
|
49323
|
+
- If worktree_path is set in boulder.json, all work happens inside that worktree directory
|
|
49284
49324
|
- Read the FULL plan file before delegating any tasks
|
|
49285
|
-
- Follow atlas delegation protocols (7-section format)
|
|
49325
|
+
- Follow atlas delegation protocols (7-section format)
|
|
49326
|
+
|
|
49327
|
+
## TASK BREAKDOWN (MANDATORY)
|
|
49328
|
+
|
|
49329
|
+
After reading the plan file, you MUST decompose every plan task into granular, implementation-level sub-steps and register ALL of them as task/todo items BEFORE starting any work.
|
|
49330
|
+
|
|
49331
|
+
**How to break down**:
|
|
49332
|
+
- Each plan checkbox item (e.g., \`- [ ] Add user authentication\`) must be split into concrete, actionable sub-tasks
|
|
49333
|
+
- Sub-tasks should be specific enough that each one touches a clear set of files/functions
|
|
49334
|
+
- Include: file to modify, what to change, expected behavior, and how to verify
|
|
49335
|
+
- Do NOT leave any task vague \u2014 "implement feature X" is NOT acceptable; "add validateToken() to src/auth/middleware.ts that checks JWT expiry and returns 401" IS acceptable
|
|
49336
|
+
|
|
49337
|
+
**Example breakdown**:
|
|
49338
|
+
Plan task: \`- [ ] Add rate limiting to API\`
|
|
49339
|
+
\u2192 Todo items:
|
|
49340
|
+
1. Create \`src/middleware/rate-limiter.ts\` with sliding window algorithm (max 100 req/min per IP)
|
|
49341
|
+
2. Add RateLimiter middleware to \`src/app.ts\` router chain, before auth middleware
|
|
49342
|
+
3. Add rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) to response in \`rate-limiter.ts\`
|
|
49343
|
+
4. Add test: verify 429 response after exceeding limit in \`src/middleware/rate-limiter.test.ts\`
|
|
49344
|
+
5. Add test: verify headers are present on normal responses
|
|
49345
|
+
|
|
49346
|
+
Register these as task/todo items so progress is tracked and visible throughout the session.
|
|
49347
|
+
|
|
49348
|
+
## WORKTREE COMPLETION
|
|
49349
|
+
|
|
49350
|
+
When working in a worktree (\`worktree_path\` is set in boulder.json) and ALL plan tasks are complete:
|
|
49351
|
+
1. Commit all remaining changes in the worktree
|
|
49352
|
+
2. Switch to the main working directory (the original repo, NOT the worktree)
|
|
49353
|
+
3. Merge the worktree branch into the current branch: \`git merge <worktree-branch>\`
|
|
49354
|
+
4. If merge succeeds, clean up: \`git worktree remove <worktree-path>\`
|
|
49355
|
+
5. Remove the boulder.json state
|
|
49356
|
+
|
|
49357
|
+
This is the DEFAULT behavior when \`--worktree\` was used. Skip merge only if the user explicitly instructs otherwise (e.g., asks to create a PR instead).`;
|
|
49286
49358
|
|
|
49287
49359
|
// src/features/builtin-commands/templates/handoff.ts
|
|
49288
49360
|
var HANDOFF_TEMPLATE = `# Handoff Command
|
|
@@ -49664,9 +49736,6 @@ function skillToCommandInfo(skill) {
|
|
|
49664
49736
|
lazyContentLoader: skill.lazyContent
|
|
49665
49737
|
};
|
|
49666
49738
|
}
|
|
49667
|
-
function filterDiscoveredCommandsByScope(commands3, scope) {
|
|
49668
|
-
return commands3.filter((command) => command.scope === scope);
|
|
49669
|
-
}
|
|
49670
49739
|
async function discoverAllCommands(options) {
|
|
49671
49740
|
const discoveredCommands = discoverCommandsSync(process.cwd(), {
|
|
49672
49741
|
pluginsEnabled: options?.pluginsEnabled,
|
|
@@ -49674,14 +49743,17 @@ async function discoverAllCommands(options) {
|
|
|
49674
49743
|
});
|
|
49675
49744
|
const skills2 = options?.skills ?? await discoverAllSkills();
|
|
49676
49745
|
const skillCommands = skills2.map(skillToCommandInfo);
|
|
49746
|
+
const scopeOrder = ["project", "user", "opencode-project", "opencode", "builtin", "plugin"];
|
|
49747
|
+
const grouped = new Map;
|
|
49748
|
+
for (const cmd of discoveredCommands) {
|
|
49749
|
+
const list = grouped.get(cmd.scope) ?? [];
|
|
49750
|
+
list.push(cmd);
|
|
49751
|
+
grouped.set(cmd.scope, list);
|
|
49752
|
+
}
|
|
49753
|
+
const orderedCommands = scopeOrder.flatMap((scope) => grouped.get(scope) ?? []);
|
|
49677
49754
|
return [
|
|
49678
49755
|
...skillCommands,
|
|
49679
|
-
...
|
|
49680
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "user"),
|
|
49681
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "opencode-project"),
|
|
49682
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "opencode"),
|
|
49683
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "builtin"),
|
|
49684
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "plugin")
|
|
49756
|
+
...orderedCommands
|
|
49685
49757
|
];
|
|
49686
49758
|
}
|
|
49687
49759
|
async function findCommand2(commandName, options) {
|
|
@@ -53440,6 +53512,7 @@ function getErrorMessage2(error48) {
|
|
|
53440
53512
|
return "";
|
|
53441
53513
|
}
|
|
53442
53514
|
}
|
|
53515
|
+
var DEFAULT_RETRY_PATTERN = new RegExp(`\\b(${DEFAULT_CONFIG2.retry_on_errors.join("|")})\\b`);
|
|
53443
53516
|
function extractStatusCode(error48, retryOnErrors) {
|
|
53444
53517
|
if (!error48)
|
|
53445
53518
|
return;
|
|
@@ -53454,8 +53527,7 @@ function extractStatusCode(error48, retryOnErrors) {
|
|
|
53454
53527
|
if (statusCode !== undefined) {
|
|
53455
53528
|
return statusCode;
|
|
53456
53529
|
}
|
|
53457
|
-
const
|
|
53458
|
-
const pattern = new RegExp(`\\b(${codes.join("|")})\\b`);
|
|
53530
|
+
const pattern = retryOnErrors ? new RegExp(`\\b(${retryOnErrors.join("|")})\\b`) : DEFAULT_RETRY_PATTERN;
|
|
53459
53531
|
const message = getErrorMessage2(error48);
|
|
53460
53532
|
const statusMatch = message.match(pattern);
|
|
53461
53533
|
if (statusMatch) {
|
|
@@ -55146,6 +55218,46 @@ function createReadImageResizerHook(_ctx) {
|
|
|
55146
55218
|
}
|
|
55147
55219
|
};
|
|
55148
55220
|
}
|
|
55221
|
+
// src/hooks/todo-description-override/description.ts
|
|
55222
|
+
var TODOWRITE_DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.
|
|
55223
|
+
|
|
55224
|
+
## Todo Format (MANDATORY)
|
|
55225
|
+
|
|
55226
|
+
Each todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.
|
|
55227
|
+
|
|
55228
|
+
Format: "[WHERE] [HOW] to [WHY] \u2014 expect [RESULT]"
|
|
55229
|
+
|
|
55230
|
+
GOOD:
|
|
55231
|
+
- "src/utils/validation.ts: Add validateEmail() for input sanitization \u2014 returns boolean"
|
|
55232
|
+
- "UserService.create(): Call validateEmail() before DB insert \u2014 rejects invalid emails with 400"
|
|
55233
|
+
- "validation.test.ts: Add test for missing @ sign \u2014 expect validateEmail('foo') to return false"
|
|
55234
|
+
|
|
55235
|
+
BAD:
|
|
55236
|
+
- "Implement email validation" (where? how? what result?)
|
|
55237
|
+
- "Add dark mode" (this is a feature, not a todo)
|
|
55238
|
+
- "Fix auth" (what file? what changes? what's expected?)
|
|
55239
|
+
|
|
55240
|
+
## Granularity Rules
|
|
55241
|
+
|
|
55242
|
+
Each todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.
|
|
55243
|
+
|
|
55244
|
+
**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.
|
|
55245
|
+
|
|
55246
|
+
## Task Management
|
|
55247
|
+
- One in_progress at a time. Complete it before starting the next.
|
|
55248
|
+
- Mark completed immediately after finishing each item.
|
|
55249
|
+
- Skip this tool for single trivial tasks (one-step, obvious action).`;
|
|
55250
|
+
|
|
55251
|
+
// src/hooks/todo-description-override/hook.ts
|
|
55252
|
+
function createTodoDescriptionOverrideHook() {
|
|
55253
|
+
return {
|
|
55254
|
+
"tool.definition": async (input, output) => {
|
|
55255
|
+
if (input.toolID === "todowrite") {
|
|
55256
|
+
output.description = TODOWRITE_DESCRIPTION;
|
|
55257
|
+
}
|
|
55258
|
+
}
|
|
55259
|
+
};
|
|
55260
|
+
}
|
|
55149
55261
|
// src/hooks/anthropic-effort/hook.ts
|
|
55150
55262
|
var OPUS_4_6_PATTERN = /claude-opus-4[-.]6/i;
|
|
55151
55263
|
function isClaudeProvider(providerID, modelID) {
|
|
@@ -75723,10 +75835,11 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
|
|
|
75723
75835
|
allTasks.push(task);
|
|
75724
75836
|
}
|
|
75725
75837
|
}
|
|
75838
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
75726
75839
|
const activeTasks = allTasks.filter((task) => task.status !== "completed" && task.status !== "deleted");
|
|
75727
75840
|
const summaries = activeTasks.map((task) => {
|
|
75728
75841
|
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
|
75729
|
-
const blockerTask =
|
|
75842
|
+
const blockerTask = taskMap.get(blockerId);
|
|
75730
75843
|
return !blockerTask || blockerTask.status !== "completed";
|
|
75731
75844
|
});
|
|
75732
75845
|
return {
|
|
@@ -76415,6 +76528,15 @@ function applyPrepend(lines, text) {
|
|
|
76415
76528
|
}
|
|
76416
76529
|
|
|
76417
76530
|
// src/tools/hashline-edit/edit-operations.ts
|
|
76531
|
+
function arraysEqual(a, b) {
|
|
76532
|
+
if (a.length !== b.length)
|
|
76533
|
+
return false;
|
|
76534
|
+
for (let i2 = 0;i2 < a.length; i2++) {
|
|
76535
|
+
if (a[i2] !== b[i2])
|
|
76536
|
+
return false;
|
|
76537
|
+
}
|
|
76538
|
+
return true;
|
|
76539
|
+
}
|
|
76418
76540
|
function applyHashlineEditsWithReport(content, edits) {
|
|
76419
76541
|
if (edits.length === 0) {
|
|
76420
76542
|
return {
|
|
@@ -76444,9 +76566,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76444
76566
|
switch (edit.op) {
|
|
76445
76567
|
case "replace": {
|
|
76446
76568
|
const next = edit.end ? applyReplaceLines(lines, edit.pos, edit.end, edit.lines, { skipValidation: true }) : applySetLine(lines, edit.pos, edit.lines, { skipValidation: true });
|
|
76447
|
-
if (next
|
|
76448
|
-
`) === lines.join(`
|
|
76449
|
-
`)) {
|
|
76569
|
+
if (arraysEqual(next, lines)) {
|
|
76450
76570
|
noopEdits += 1;
|
|
76451
76571
|
break;
|
|
76452
76572
|
}
|
|
@@ -76455,9 +76575,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76455
76575
|
}
|
|
76456
76576
|
case "append": {
|
|
76457
76577
|
const next = edit.pos ? applyInsertAfter(lines, edit.pos, edit.lines, { skipValidation: true }) : applyAppend(lines, edit.lines);
|
|
76458
|
-
if (next
|
|
76459
|
-
`) === lines.join(`
|
|
76460
|
-
`)) {
|
|
76578
|
+
if (arraysEqual(next, lines)) {
|
|
76461
76579
|
noopEdits += 1;
|
|
76462
76580
|
break;
|
|
76463
76581
|
}
|
|
@@ -76466,9 +76584,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76466
76584
|
}
|
|
76467
76585
|
case "prepend": {
|
|
76468
76586
|
const next = edit.pos ? applyInsertBefore(lines, edit.pos, edit.lines, { skipValidation: true }) : applyPrepend(lines, edit.lines);
|
|
76469
|
-
if (next
|
|
76470
|
-
`) === lines.join(`
|
|
76471
|
-
`)) {
|
|
76587
|
+
if (arraysEqual(next, lines)) {
|
|
76472
76588
|
noopEdits += 1;
|
|
76473
76589
|
break;
|
|
76474
76590
|
}
|
|
@@ -77493,6 +77609,7 @@ function createToolGuardHooks(args) {
|
|
|
77493
77609
|
const hashlineReadEnhancer = isHookEnabled("hashline-read-enhancer") ? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.hashline_edit ?? false } })) : null;
|
|
77494
77610
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
77495
77611
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
77612
|
+
const todoDescriptionOverride = isHookEnabled("todo-description-override") ? safeHook("todo-description-override", () => createTodoDescriptionOverrideHook()) : null;
|
|
77496
77613
|
return {
|
|
77497
77614
|
commentChecker,
|
|
77498
77615
|
toolOutputTruncator,
|
|
@@ -77504,7 +77621,8 @@ function createToolGuardHooks(args) {
|
|
|
77504
77621
|
writeExistingFileGuard,
|
|
77505
77622
|
hashlineReadEnhancer,
|
|
77506
77623
|
jsonErrorRecovery,
|
|
77507
|
-
readImageResizer
|
|
77624
|
+
readImageResizer,
|
|
77625
|
+
todoDescriptionOverride
|
|
77508
77626
|
};
|
|
77509
77627
|
}
|
|
77510
77628
|
|
|
@@ -78031,8 +78149,8 @@ var MIN_STABILITY_TIME_MS2 = 10 * 1000;
|
|
|
78031
78149
|
var DEFAULT_STALE_TIMEOUT_MS = 1200000;
|
|
78032
78150
|
var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
|
|
78033
78151
|
var DEFAULT_MAX_TOOL_CALLS = 200;
|
|
78034
|
-
var
|
|
78035
|
-
var
|
|
78152
|
+
var DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
|
|
78153
|
+
var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
|
|
78036
78154
|
var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
|
|
78037
78155
|
var MIN_IDLE_TIME_MS = 5000;
|
|
78038
78156
|
var POLLING_INTERVAL_MS = 3000;
|
|
@@ -78453,6 +78571,22 @@ function removeTaskToastTracking(taskId) {
|
|
|
78453
78571
|
}
|
|
78454
78572
|
}
|
|
78455
78573
|
|
|
78574
|
+
// src/features/background-agent/session-status-classifier.ts
|
|
78575
|
+
var ACTIVE_SESSION_STATUSES = new Set(["busy", "retry", "running"]);
|
|
78576
|
+
var KNOWN_TERMINAL_STATUSES = new Set(["idle", "interrupted"]);
|
|
78577
|
+
function isActiveSessionStatus(type2) {
|
|
78578
|
+
if (ACTIVE_SESSION_STATUSES.has(type2)) {
|
|
78579
|
+
return true;
|
|
78580
|
+
}
|
|
78581
|
+
if (!KNOWN_TERMINAL_STATUSES.has(type2)) {
|
|
78582
|
+
log("[background-agent] Unknown session status type encountered:", type2);
|
|
78583
|
+
}
|
|
78584
|
+
return false;
|
|
78585
|
+
}
|
|
78586
|
+
function isTerminalSessionStatus(type2) {
|
|
78587
|
+
return KNOWN_TERMINAL_STATUSES.has(type2) && type2 !== "idle";
|
|
78588
|
+
}
|
|
78589
|
+
|
|
78456
78590
|
// src/features/background-agent/task-poller.ts
|
|
78457
78591
|
var TERMINAL_TASK_STATUSES = new Set([
|
|
78458
78592
|
"completed",
|
|
@@ -78531,7 +78665,7 @@ async function checkAndInterruptStaleTasks(args) {
|
|
|
78531
78665
|
if (!startedAt || !sessionID)
|
|
78532
78666
|
continue;
|
|
78533
78667
|
const sessionStatus = sessionStatuses?.[sessionID]?.type;
|
|
78534
|
-
const sessionIsRunning = sessionStatus !== undefined && sessionStatus
|
|
78668
|
+
const sessionIsRunning = sessionStatus !== undefined && isActiveSessionStatus(sessionStatus);
|
|
78535
78669
|
const runtime = now - startedAt.getTime();
|
|
78536
78670
|
if (!task.progress?.lastUpdate) {
|
|
78537
78671
|
if (sessionIsRunning)
|
|
@@ -78587,51 +78721,57 @@ async function checkAndInterruptStaleTasks(args) {
|
|
|
78587
78721
|
// src/features/background-agent/loop-detector.ts
|
|
78588
78722
|
function resolveCircuitBreakerSettings(config4) {
|
|
78589
78723
|
return {
|
|
78724
|
+
enabled: config4?.circuitBreaker?.enabled ?? DEFAULT_CIRCUIT_BREAKER_ENABLED,
|
|
78590
78725
|
maxToolCalls: config4?.circuitBreaker?.maxToolCalls ?? config4?.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,
|
|
78591
|
-
|
|
78592
|
-
repetitionThresholdPercent: config4?.circuitBreaker?.repetitionThresholdPercent ?? DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT
|
|
78726
|
+
consecutiveThreshold: config4?.circuitBreaker?.consecutiveThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD
|
|
78593
78727
|
};
|
|
78594
78728
|
}
|
|
78595
|
-
function recordToolCall(window, toolName, settings) {
|
|
78596
|
-
const
|
|
78597
|
-
|
|
78729
|
+
function recordToolCall(window, toolName, settings, toolInput) {
|
|
78730
|
+
const signature = createToolCallSignature(toolName, toolInput);
|
|
78731
|
+
if (window && window.lastSignature === signature) {
|
|
78732
|
+
return {
|
|
78733
|
+
lastSignature: signature,
|
|
78734
|
+
consecutiveCount: window.consecutiveCount + 1,
|
|
78735
|
+
threshold: settings.consecutiveThreshold
|
|
78736
|
+
};
|
|
78737
|
+
}
|
|
78598
78738
|
return {
|
|
78599
|
-
|
|
78600
|
-
|
|
78601
|
-
|
|
78739
|
+
lastSignature: signature,
|
|
78740
|
+
consecutiveCount: 1,
|
|
78741
|
+
threshold: settings.consecutiveThreshold
|
|
78602
78742
|
};
|
|
78603
78743
|
}
|
|
78604
|
-
function
|
|
78605
|
-
if (
|
|
78606
|
-
return
|
|
78607
|
-
|
|
78608
|
-
|
|
78609
|
-
|
|
78610
|
-
|
|
78744
|
+
function sortObject2(obj) {
|
|
78745
|
+
if (obj === null || obj === undefined)
|
|
78746
|
+
return obj;
|
|
78747
|
+
if (typeof obj !== "object")
|
|
78748
|
+
return obj;
|
|
78749
|
+
if (Array.isArray(obj))
|
|
78750
|
+
return obj.map(sortObject2);
|
|
78751
|
+
const sorted = {};
|
|
78752
|
+
const keys = Object.keys(obj).sort();
|
|
78753
|
+
for (const key of keys) {
|
|
78754
|
+
sorted[key] = sortObject2(obj[key]);
|
|
78611
78755
|
}
|
|
78612
|
-
|
|
78613
|
-
|
|
78614
|
-
|
|
78615
|
-
|
|
78616
|
-
|
|
78617
|
-
repeatedCount = count;
|
|
78618
|
-
}
|
|
78756
|
+
return sorted;
|
|
78757
|
+
}
|
|
78758
|
+
function createToolCallSignature(toolName, toolInput) {
|
|
78759
|
+
if (toolInput === undefined || toolInput === null) {
|
|
78760
|
+
return toolName;
|
|
78619
78761
|
}
|
|
78620
|
-
|
|
78621
|
-
|
|
78622
|
-
if (sampleSize < minimumSampleSize) {
|
|
78623
|
-
return { triggered: false };
|
|
78762
|
+
if (Object.keys(toolInput).length === 0) {
|
|
78763
|
+
return toolName;
|
|
78624
78764
|
}
|
|
78625
|
-
|
|
78626
|
-
|
|
78765
|
+
return `${toolName}::${JSON.stringify(sortObject2(toolInput))}`;
|
|
78766
|
+
}
|
|
78767
|
+
function detectRepetitiveToolUse(window) {
|
|
78768
|
+
if (!window || window.consecutiveCount < window.threshold) {
|
|
78627
78769
|
return { triggered: false };
|
|
78628
78770
|
}
|
|
78629
78771
|
return {
|
|
78630
78772
|
triggered: true,
|
|
78631
|
-
toolName:
|
|
78632
|
-
repeatedCount
|
|
78633
|
-
sampleSize,
|
|
78634
|
-
thresholdPercent: window.thresholdPercent
|
|
78773
|
+
toolName: window.lastSignature.split("::")[0],
|
|
78774
|
+
repeatedCount: window.consecutiveCount
|
|
78635
78775
|
};
|
|
78636
78776
|
}
|
|
78637
78777
|
|
|
@@ -78730,6 +78870,7 @@ class BackgroundManager {
|
|
|
78730
78870
|
preStartDescendantReservations;
|
|
78731
78871
|
enableParentSessionNotifications;
|
|
78732
78872
|
taskHistory = new TaskHistory;
|
|
78873
|
+
cachedCircuitBreakerSettings;
|
|
78733
78874
|
constructor(ctx, config4, options) {
|
|
78734
78875
|
this.tasks = new Map;
|
|
78735
78876
|
this.notifications = new Map;
|
|
@@ -79322,35 +79463,36 @@ class BackgroundManager {
|
|
|
79322
79463
|
}
|
|
79323
79464
|
task.progress.lastUpdate = new Date;
|
|
79324
79465
|
if (partInfo?.type === "tool" || partInfo?.tool) {
|
|
79325
|
-
const countedToolPartIDs = task.progress.countedToolPartIDs ??
|
|
79326
|
-
const shouldCountToolCall = !partInfo.id || partInfo.state?.status !== "running" || !countedToolPartIDs.
|
|
79466
|
+
const countedToolPartIDs = task.progress.countedToolPartIDs ?? new Set;
|
|
79467
|
+
const shouldCountToolCall = !partInfo.id || partInfo.state?.status !== "running" || !countedToolPartIDs.has(partInfo.id);
|
|
79327
79468
|
if (!shouldCountToolCall) {
|
|
79328
79469
|
return;
|
|
79329
79470
|
}
|
|
79330
79471
|
if (partInfo.id && partInfo.state?.status === "running") {
|
|
79331
|
-
|
|
79472
|
+
countedToolPartIDs.add(partInfo.id);
|
|
79473
|
+
task.progress.countedToolPartIDs = countedToolPartIDs;
|
|
79332
79474
|
}
|
|
79333
79475
|
task.progress.toolCalls += 1;
|
|
79334
79476
|
task.progress.lastTool = partInfo.tool;
|
|
79335
|
-
const circuitBreaker = resolveCircuitBreakerSettings(this.config);
|
|
79477
|
+
const circuitBreaker = this.cachedCircuitBreakerSettings ?? (this.cachedCircuitBreakerSettings = resolveCircuitBreakerSettings(this.config));
|
|
79336
79478
|
if (partInfo.tool) {
|
|
79337
|
-
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker);
|
|
79338
|
-
|
|
79339
|
-
|
|
79340
|
-
|
|
79341
|
-
|
|
79342
|
-
|
|
79343
|
-
|
|
79344
|
-
|
|
79345
|
-
|
|
79346
|
-
|
|
79347
|
-
|
|
79348
|
-
|
|
79349
|
-
|
|
79350
|
-
|
|
79351
|
-
|
|
79352
|
-
|
|
79353
|
-
|
|
79479
|
+
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker, partInfo.state?.input);
|
|
79480
|
+
if (circuitBreaker.enabled) {
|
|
79481
|
+
const loopDetection = detectRepetitiveToolUse(task.progress.toolCallWindow);
|
|
79482
|
+
if (loopDetection.triggered) {
|
|
79483
|
+
log("[background-agent] Circuit breaker: consecutive tool usage detected", {
|
|
79484
|
+
taskId: task.id,
|
|
79485
|
+
agent: task.agent,
|
|
79486
|
+
sessionID,
|
|
79487
|
+
toolName: loopDetection.toolName,
|
|
79488
|
+
repeatedCount: loopDetection.repeatedCount
|
|
79489
|
+
});
|
|
79490
|
+
this.cancelTask(task.id, {
|
|
79491
|
+
source: "circuit-breaker",
|
|
79492
|
+
reason: `Subagent called ${loopDetection.toolName} ${loopDetection.repeatedCount} consecutive times (threshold: ${circuitBreaker.consecutiveThreshold}). This usually indicates an infinite loop. The task was automatically cancelled to prevent excessive token usage.`
|
|
79493
|
+
});
|
|
79494
|
+
return;
|
|
79495
|
+
}
|
|
79354
79496
|
}
|
|
79355
79497
|
}
|
|
79356
79498
|
const maxToolCalls = circuitBreaker.maxToolCalls;
|
|
@@ -79997,7 +80139,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
79997
80139
|
continue;
|
|
79998
80140
|
}
|
|
79999
80141
|
}
|
|
80000
|
-
if (sessionStatus && sessionStatus.type
|
|
80142
|
+
if (sessionStatus && isActiveSessionStatus(sessionStatus.type)) {
|
|
80001
80143
|
log("[background-agent] Session still running, relying on event-based progress:", {
|
|
80002
80144
|
taskId: task.id,
|
|
80003
80145
|
sessionID,
|
|
@@ -80006,6 +80148,17 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
80006
80148
|
});
|
|
80007
80149
|
continue;
|
|
80008
80150
|
}
|
|
80151
|
+
if (sessionStatus && isTerminalSessionStatus(sessionStatus.type)) {
|
|
80152
|
+
await this.tryCompleteTask(task, `polling (terminal session status: ${sessionStatus.type})`);
|
|
80153
|
+
continue;
|
|
80154
|
+
}
|
|
80155
|
+
if (sessionStatus && sessionStatus.type !== "idle") {
|
|
80156
|
+
log("[background-agent] Unknown session status, treating as potentially idle:", {
|
|
80157
|
+
taskId: task.id,
|
|
80158
|
+
sessionID,
|
|
80159
|
+
sessionStatus: sessionStatus.type
|
|
80160
|
+
});
|
|
80161
|
+
}
|
|
80009
80162
|
const completionSource = sessionStatus?.type === "idle" ? "polling (idle status)" : "polling (session gone from status)";
|
|
80010
80163
|
const hasValidOutput = await this.validateSessionHasOutput(sessionID);
|
|
80011
80164
|
if (!hasValidOutput) {
|
|
@@ -97783,10 +97936,7 @@ function createPluginInterface(args) {
|
|
|
97783
97936
|
const { ctx, pluginConfig, firstMessageVariantGate, managers, hooks: hooks2, tools } = args;
|
|
97784
97937
|
return {
|
|
97785
97938
|
tool: tools,
|
|
97786
|
-
"chat.params":
|
|
97787
|
-
const handler = createChatParamsHandler({ anthropicEffort: hooks2.anthropicEffort });
|
|
97788
|
-
await handler(input, output);
|
|
97789
|
-
},
|
|
97939
|
+
"chat.params": createChatParamsHandler({ anthropicEffort: hooks2.anthropicEffort }),
|
|
97790
97940
|
"chat.headers": createChatHeadersHandler({ ctx }),
|
|
97791
97941
|
"chat.message": createChatMessageHandler3({
|
|
97792
97942
|
ctx,
|
|
@@ -97813,7 +97963,10 @@ function createPluginInterface(args) {
|
|
|
97813
97963
|
"tool.execute.after": createToolExecuteAfterHandler3({
|
|
97814
97964
|
ctx,
|
|
97815
97965
|
hooks: hooks2
|
|
97816
|
-
})
|
|
97966
|
+
}),
|
|
97967
|
+
"tool.definition": async (input, output) => {
|
|
97968
|
+
await hooks2.todoDescriptionOverride?.["tool.definition"]?.(input, output);
|
|
97969
|
+
}
|
|
97817
97970
|
};
|
|
97818
97971
|
}
|
|
97819
97972
|
|