oh-my-opencode 3.12.0 → 3.12.2
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 +203 -97
- package/dist/config/schema/background-task.d.ts +2 -2
- package/dist/config/schema/hooks.d.ts +1 -0
- package/dist/config/schema/oh-my-opencode-config.d.ts +2 -2
- package/dist/create-hooks.d.ts +1 -0
- 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 +520 -323
- package/dist/oh-my-opencode.schema.json +4 -6
- 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
|
@@ -55,20 +55,43 @@ __export(exports_logger, {
|
|
|
55
55
|
import * as fs from "fs";
|
|
56
56
|
import * as os from "os";
|
|
57
57
|
import * as path from "path";
|
|
58
|
+
function flush() {
|
|
59
|
+
if (buffer.length === 0)
|
|
60
|
+
return;
|
|
61
|
+
const data = buffer.join("");
|
|
62
|
+
buffer = [];
|
|
63
|
+
try {
|
|
64
|
+
fs.appendFileSync(logFile, data);
|
|
65
|
+
} catch {}
|
|
66
|
+
}
|
|
67
|
+
function scheduleFlush() {
|
|
68
|
+
if (flushTimer)
|
|
69
|
+
return;
|
|
70
|
+
flushTimer = setTimeout(() => {
|
|
71
|
+
flushTimer = null;
|
|
72
|
+
flush();
|
|
73
|
+
}, FLUSH_INTERVAL_MS);
|
|
74
|
+
}
|
|
58
75
|
function log(message, data) {
|
|
59
76
|
try {
|
|
60
77
|
const timestamp2 = new Date().toISOString();
|
|
61
78
|
const logEntry = `[${timestamp2}] ${message} ${data ? JSON.stringify(data) : ""}
|
|
62
79
|
`;
|
|
63
|
-
|
|
80
|
+
buffer.push(logEntry);
|
|
81
|
+
if (buffer.length >= BUFFER_SIZE_LIMIT) {
|
|
82
|
+
flush();
|
|
83
|
+
} else {
|
|
84
|
+
scheduleFlush();
|
|
85
|
+
}
|
|
64
86
|
} catch {}
|
|
65
87
|
}
|
|
66
88
|
function getLogFilePath() {
|
|
67
89
|
return logFile;
|
|
68
90
|
}
|
|
69
|
-
var logFile;
|
|
91
|
+
var logFile, buffer, flushTimer = null, FLUSH_INTERVAL_MS = 500, BUFFER_SIZE_LIMIT = 50;
|
|
70
92
|
var init_logger = __esm(() => {
|
|
71
93
|
logFile = path.join(os.tmpdir(), "oh-my-opencode.log");
|
|
94
|
+
buffer = [];
|
|
72
95
|
});
|
|
73
96
|
|
|
74
97
|
// src/shared/truncate-description.ts
|
|
@@ -2454,18 +2477,18 @@ var require_sharedArrayCancellation = __commonJS((exports) => {
|
|
|
2454
2477
|
if (request.id === null) {
|
|
2455
2478
|
return;
|
|
2456
2479
|
}
|
|
2457
|
-
const
|
|
2458
|
-
const data = new Int32Array(
|
|
2480
|
+
const buffer2 = new SharedArrayBuffer(4);
|
|
2481
|
+
const data = new Int32Array(buffer2, 0, 1);
|
|
2459
2482
|
data[0] = CancellationState.Continue;
|
|
2460
|
-
this.buffers.set(request.id,
|
|
2461
|
-
request.$cancellationData =
|
|
2483
|
+
this.buffers.set(request.id, buffer2);
|
|
2484
|
+
request.$cancellationData = buffer2;
|
|
2462
2485
|
}
|
|
2463
2486
|
async sendCancellation(_conn, id) {
|
|
2464
|
-
const
|
|
2465
|
-
if (
|
|
2487
|
+
const buffer2 = this.buffers.get(id);
|
|
2488
|
+
if (buffer2 === undefined) {
|
|
2466
2489
|
return;
|
|
2467
2490
|
}
|
|
2468
|
-
const data = new Int32Array(
|
|
2491
|
+
const data = new Int32Array(buffer2, 0, 1);
|
|
2469
2492
|
Atomics.store(data, 0, CancellationState.Cancelled);
|
|
2470
2493
|
}
|
|
2471
2494
|
cleanup(id) {
|
|
@@ -2478,8 +2501,8 @@ var require_sharedArrayCancellation = __commonJS((exports) => {
|
|
|
2478
2501
|
exports.SharedArraySenderStrategy = SharedArraySenderStrategy;
|
|
2479
2502
|
|
|
2480
2503
|
class SharedArrayBufferCancellationToken {
|
|
2481
|
-
constructor(
|
|
2482
|
-
this.data = new Int32Array(
|
|
2504
|
+
constructor(buffer2) {
|
|
2505
|
+
this.data = new Int32Array(buffer2, 0, 1);
|
|
2483
2506
|
}
|
|
2484
2507
|
get isCancellationRequested() {
|
|
2485
2508
|
return Atomics.load(this.data, 0) === CancellationState.Cancelled;
|
|
@@ -2490,8 +2513,8 @@ var require_sharedArrayCancellation = __commonJS((exports) => {
|
|
|
2490
2513
|
}
|
|
2491
2514
|
|
|
2492
2515
|
class SharedArrayBufferCancellationTokenSource {
|
|
2493
|
-
constructor(
|
|
2494
|
-
this.token = new SharedArrayBufferCancellationToken(
|
|
2516
|
+
constructor(buffer2) {
|
|
2517
|
+
this.token = new SharedArrayBufferCancellationToken(buffer2);
|
|
2495
2518
|
}
|
|
2496
2519
|
cancel() {}
|
|
2497
2520
|
dispose() {}
|
|
@@ -2502,11 +2525,11 @@ var require_sharedArrayCancellation = __commonJS((exports) => {
|
|
|
2502
2525
|
this.kind = "request";
|
|
2503
2526
|
}
|
|
2504
2527
|
createCancellationTokenSource(request) {
|
|
2505
|
-
const
|
|
2506
|
-
if (
|
|
2528
|
+
const buffer2 = request.$cancellationData;
|
|
2529
|
+
if (buffer2 === undefined) {
|
|
2507
2530
|
return new cancellation_1.CancellationTokenSource;
|
|
2508
2531
|
}
|
|
2509
|
-
return new SharedArrayBufferCancellationTokenSource(
|
|
2532
|
+
return new SharedArrayBufferCancellationTokenSource(buffer2);
|
|
2510
2533
|
}
|
|
2511
2534
|
}
|
|
2512
2535
|
exports.SharedArrayReceiverStrategy = SharedArrayReceiverStrategy;
|
|
@@ -2840,18 +2863,18 @@ var require_messageWriter = __commonJS((exports) => {
|
|
|
2840
2863
|
}
|
|
2841
2864
|
async write(msg) {
|
|
2842
2865
|
return this.writeSemaphore.lock(async () => {
|
|
2843
|
-
const payload = this.options.contentTypeEncoder.encode(msg, this.options).then((
|
|
2866
|
+
const payload = this.options.contentTypeEncoder.encode(msg, this.options).then((buffer2) => {
|
|
2844
2867
|
if (this.options.contentEncoder !== undefined) {
|
|
2845
|
-
return this.options.contentEncoder.encode(
|
|
2868
|
+
return this.options.contentEncoder.encode(buffer2);
|
|
2846
2869
|
} else {
|
|
2847
|
-
return
|
|
2870
|
+
return buffer2;
|
|
2848
2871
|
}
|
|
2849
2872
|
});
|
|
2850
|
-
return payload.then((
|
|
2873
|
+
return payload.then((buffer2) => {
|
|
2851
2874
|
const headers = [];
|
|
2852
|
-
headers.push(ContentLength,
|
|
2875
|
+
headers.push(ContentLength, buffer2.byteLength.toString(), CRLF);
|
|
2853
2876
|
headers.push(CRLF);
|
|
2854
|
-
return this.doWrite(msg, headers,
|
|
2877
|
+
return this.doWrite(msg, headers, buffer2);
|
|
2855
2878
|
}, (error48) => {
|
|
2856
2879
|
this.fireError(error48);
|
|
2857
2880
|
throw error48;
|
|
@@ -2953,9 +2976,9 @@ var require_messageBuffer = __commonJS((exports) => {
|
|
|
2953
2976
|
if (state3 !== 4) {
|
|
2954
2977
|
return;
|
|
2955
2978
|
}
|
|
2956
|
-
const
|
|
2979
|
+
const buffer2 = this._read(chunkBytesRead + offset);
|
|
2957
2980
|
const result = new Map;
|
|
2958
|
-
const headers = this.toString(
|
|
2981
|
+
const headers = this.toString(buffer2, "ascii").split(CRLF);
|
|
2959
2982
|
if (headers.length < 2) {
|
|
2960
2983
|
return result;
|
|
2961
2984
|
}
|
|
@@ -4379,11 +4402,11 @@ var require_ril = __commonJS((exports) => {
|
|
|
4379
4402
|
return new util_1.TextDecoder(encoding).decode(value);
|
|
4380
4403
|
}
|
|
4381
4404
|
}
|
|
4382
|
-
asNative(
|
|
4405
|
+
asNative(buffer2, length) {
|
|
4383
4406
|
if (length === undefined) {
|
|
4384
|
-
return
|
|
4407
|
+
return buffer2 instanceof Buffer ? buffer2 : Buffer.from(buffer2);
|
|
4385
4408
|
} else {
|
|
4386
|
-
return
|
|
4409
|
+
return buffer2 instanceof Buffer ? buffer2.slice(0, length) : Buffer.from(buffer2, 0, length);
|
|
4387
4410
|
}
|
|
4388
4411
|
}
|
|
4389
4412
|
allocNative(length) {
|
|
@@ -4467,12 +4490,12 @@ var require_ril = __commonJS((exports) => {
|
|
|
4467
4490
|
}),
|
|
4468
4491
|
decoder: Object.freeze({
|
|
4469
4492
|
name: "application/json",
|
|
4470
|
-
decode: (
|
|
4493
|
+
decode: (buffer2, options) => {
|
|
4471
4494
|
try {
|
|
4472
|
-
if (
|
|
4473
|
-
return Promise.resolve(JSON.parse(
|
|
4495
|
+
if (buffer2 instanceof Buffer) {
|
|
4496
|
+
return Promise.resolve(JSON.parse(buffer2.toString(options.charset)));
|
|
4474
4497
|
} else {
|
|
4475
|
-
return Promise.resolve(JSON.parse(new util_1.TextDecoder(options.charset).decode(
|
|
4498
|
+
return Promise.resolve(JSON.parse(new util_1.TextDecoder(options.charset).decode(buffer2)));
|
|
4476
4499
|
}
|
|
4477
4500
|
} catch (err) {
|
|
4478
4501
|
return Promise.reject(err);
|
|
@@ -8405,20 +8428,20 @@ var require_utils2 = __commonJS((exports, module) => {
|
|
|
8405
8428
|
return acc;
|
|
8406
8429
|
}
|
|
8407
8430
|
var nonSimpleDomain = RegExp.prototype.test.bind(/[^!"$&'()*+,\-.;=_`a-z{}~]/u);
|
|
8408
|
-
function consumeIsZone(
|
|
8409
|
-
|
|
8431
|
+
function consumeIsZone(buffer2) {
|
|
8432
|
+
buffer2.length = 0;
|
|
8410
8433
|
return true;
|
|
8411
8434
|
}
|
|
8412
|
-
function consumeHextets(
|
|
8413
|
-
if (
|
|
8414
|
-
const hex5 = stringArrayToHexStripped(
|
|
8435
|
+
function consumeHextets(buffer2, address, output) {
|
|
8436
|
+
if (buffer2.length) {
|
|
8437
|
+
const hex5 = stringArrayToHexStripped(buffer2);
|
|
8415
8438
|
if (hex5 !== "") {
|
|
8416
8439
|
address.push(hex5);
|
|
8417
8440
|
} else {
|
|
8418
8441
|
output.error = true;
|
|
8419
8442
|
return false;
|
|
8420
8443
|
}
|
|
8421
|
-
|
|
8444
|
+
buffer2.length = 0;
|
|
8422
8445
|
}
|
|
8423
8446
|
return true;
|
|
8424
8447
|
}
|
|
@@ -8426,7 +8449,7 @@ var require_utils2 = __commonJS((exports, module) => {
|
|
|
8426
8449
|
let tokenCount = 0;
|
|
8427
8450
|
const output = { error: false, address: "", zone: "" };
|
|
8428
8451
|
const address = [];
|
|
8429
|
-
const
|
|
8452
|
+
const buffer2 = [];
|
|
8430
8453
|
let endipv6Encountered = false;
|
|
8431
8454
|
let endIpv6 = false;
|
|
8432
8455
|
let consume = consumeHextets;
|
|
@@ -8439,7 +8462,7 @@ var require_utils2 = __commonJS((exports, module) => {
|
|
|
8439
8462
|
if (endipv6Encountered === true) {
|
|
8440
8463
|
endIpv6 = true;
|
|
8441
8464
|
}
|
|
8442
|
-
if (!consume(
|
|
8465
|
+
if (!consume(buffer2, address, output)) {
|
|
8443
8466
|
break;
|
|
8444
8467
|
}
|
|
8445
8468
|
if (++tokenCount > 7) {
|
|
@@ -8452,22 +8475,22 @@ var require_utils2 = __commonJS((exports, module) => {
|
|
|
8452
8475
|
address.push(":");
|
|
8453
8476
|
continue;
|
|
8454
8477
|
} else if (cursor === "%") {
|
|
8455
|
-
if (!consume(
|
|
8478
|
+
if (!consume(buffer2, address, output)) {
|
|
8456
8479
|
break;
|
|
8457
8480
|
}
|
|
8458
8481
|
consume = consumeIsZone;
|
|
8459
8482
|
} else {
|
|
8460
|
-
|
|
8483
|
+
buffer2.push(cursor);
|
|
8461
8484
|
continue;
|
|
8462
8485
|
}
|
|
8463
8486
|
}
|
|
8464
|
-
if (
|
|
8487
|
+
if (buffer2.length) {
|
|
8465
8488
|
if (consume === consumeIsZone) {
|
|
8466
|
-
output.zone =
|
|
8489
|
+
output.zone = buffer2.join("");
|
|
8467
8490
|
} else if (endIpv6) {
|
|
8468
|
-
address.push(
|
|
8491
|
+
address.push(buffer2.join(""));
|
|
8469
8492
|
} else {
|
|
8470
|
-
address.push(stringArrayToHexStripped(
|
|
8493
|
+
address.push(stringArrayToHexStripped(buffer2));
|
|
8471
8494
|
}
|
|
8472
8495
|
}
|
|
8473
8496
|
output.address = address.join("");
|
|
@@ -12160,14 +12183,14 @@ var require_readShebang = __commonJS((exports, module) => {
|
|
|
12160
12183
|
var shebangCommand = require_shebang_command();
|
|
12161
12184
|
function readShebang(command) {
|
|
12162
12185
|
const size = 150;
|
|
12163
|
-
const
|
|
12186
|
+
const buffer2 = Buffer.alloc(size);
|
|
12164
12187
|
let fd;
|
|
12165
12188
|
try {
|
|
12166
12189
|
fd = fs19.openSync(command, "r");
|
|
12167
|
-
fs19.readSync(fd,
|
|
12190
|
+
fs19.readSync(fd, buffer2, 0, size, 0);
|
|
12168
12191
|
fs19.closeSync(fd);
|
|
12169
12192
|
} catch (e) {}
|
|
12170
|
-
return shebangCommand(
|
|
12193
|
+
return shebangCommand(buffer2.toString());
|
|
12171
12194
|
}
|
|
12172
12195
|
module.exports = readShebang;
|
|
12173
12196
|
});
|
|
@@ -15246,7 +15269,7 @@ async function resolveFileReferencesInText(text, cwd = process.cwd(), depth = 0,
|
|
|
15246
15269
|
}
|
|
15247
15270
|
let resolved = text;
|
|
15248
15271
|
for (const [pattern, replacement] of replacements.entries()) {
|
|
15249
|
-
resolved = resolved.
|
|
15272
|
+
resolved = resolved.replaceAll(pattern, replacement);
|
|
15250
15273
|
}
|
|
15251
15274
|
if (findFileReferences(resolved).length > 0 && depth + 1 < maxDepth) {
|
|
15252
15275
|
return resolveFileReferencesInText(resolved, cwd, depth + 1, maxDepth);
|
|
@@ -15345,6 +15368,7 @@ function transformToolName(toolName) {
|
|
|
15345
15368
|
function escapeRegexExceptAsterisk(str2) {
|
|
15346
15369
|
return str2.replace(/[.+?^${}()|[\]\\]/g, "\\$&");
|
|
15347
15370
|
}
|
|
15371
|
+
var regexCache = new Map;
|
|
15348
15372
|
function matchesToolMatcher(toolName, matcher) {
|
|
15349
15373
|
if (!matcher) {
|
|
15350
15374
|
return true;
|
|
@@ -15352,8 +15376,12 @@ function matchesToolMatcher(toolName, matcher) {
|
|
|
15352
15376
|
const patterns = matcher.split("|").map((p) => p.trim());
|
|
15353
15377
|
return patterns.some((p) => {
|
|
15354
15378
|
if (p.includes("*")) {
|
|
15355
|
-
|
|
15356
|
-
|
|
15379
|
+
let regex = regexCache.get(p);
|
|
15380
|
+
if (!regex) {
|
|
15381
|
+
const escaped = escapeRegexExceptAsterisk(p);
|
|
15382
|
+
regex = new RegExp(`^${escaped.replace(/\*/g, ".*")}$`, "i");
|
|
15383
|
+
regexCache.set(p, regex);
|
|
15384
|
+
}
|
|
15357
15385
|
return regex.test(toolName);
|
|
15358
15386
|
}
|
|
15359
15387
|
return p.toLowerCase() === toolName.toLowerCase();
|
|
@@ -17584,120 +17612,156 @@ import { existsSync as existsSync8, readFileSync as readFileSync4, writeFileSync
|
|
|
17584
17612
|
import { join as join9 } from "path";
|
|
17585
17613
|
var CONNECTED_PROVIDERS_CACHE_FILE = "connected-providers.json";
|
|
17586
17614
|
var PROVIDER_MODELS_CACHE_FILE = "provider-models.json";
|
|
17587
|
-
function
|
|
17588
|
-
|
|
17589
|
-
|
|
17590
|
-
|
|
17591
|
-
|
|
17592
|
-
|
|
17593
|
-
|
|
17594
|
-
|
|
17595
|
-
|
|
17596
|
-
|
|
17597
|
-
|
|
17598
|
-
|
|
17599
|
-
|
|
17600
|
-
|
|
17601
|
-
|
|
17602
|
-
|
|
17603
|
-
|
|
17604
|
-
|
|
17605
|
-
|
|
17606
|
-
|
|
17607
|
-
|
|
17608
|
-
|
|
17609
|
-
|
|
17615
|
+
function createConnectedProvidersCacheStore(getCacheDir2 = getOmoOpenCodeCacheDir) {
|
|
17616
|
+
function getCacheFilePath(filename) {
|
|
17617
|
+
return join9(getCacheDir2(), filename);
|
|
17618
|
+
}
|
|
17619
|
+
let memConnected;
|
|
17620
|
+
let memProviderModels;
|
|
17621
|
+
function ensureCacheDir2() {
|
|
17622
|
+
const cacheDir = getCacheDir2();
|
|
17623
|
+
if (!existsSync8(cacheDir)) {
|
|
17624
|
+
mkdirSync2(cacheDir, { recursive: true });
|
|
17625
|
+
}
|
|
17626
|
+
}
|
|
17627
|
+
function readConnectedProvidersCache() {
|
|
17628
|
+
if (memConnected !== undefined)
|
|
17629
|
+
return memConnected;
|
|
17630
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17631
|
+
if (!existsSync8(cacheFile)) {
|
|
17632
|
+
log("[connected-providers-cache] Cache file not found", { cacheFile });
|
|
17633
|
+
memConnected = null;
|
|
17634
|
+
return null;
|
|
17635
|
+
}
|
|
17636
|
+
try {
|
|
17637
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17638
|
+
const data = JSON.parse(content);
|
|
17639
|
+
log("[connected-providers-cache] Read cache", { count: data.connected.length, updatedAt: data.updatedAt });
|
|
17640
|
+
memConnected = data.connected;
|
|
17641
|
+
return data.connected;
|
|
17642
|
+
} catch (err) {
|
|
17643
|
+
log("[connected-providers-cache] Error reading cache", { error: String(err) });
|
|
17644
|
+
memConnected = null;
|
|
17645
|
+
return null;
|
|
17646
|
+
}
|
|
17610
17647
|
}
|
|
17611
|
-
|
|
17612
|
-
|
|
17613
|
-
|
|
17614
|
-
return existsSync8(cacheFile);
|
|
17615
|
-
}
|
|
17616
|
-
function writeConnectedProvidersCache(connected) {
|
|
17617
|
-
ensureCacheDir2();
|
|
17618
|
-
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17619
|
-
const data = {
|
|
17620
|
-
connected,
|
|
17621
|
-
updatedAt: new Date().toISOString()
|
|
17622
|
-
};
|
|
17623
|
-
try {
|
|
17624
|
-
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
17625
|
-
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17626
|
-
} catch (err) {
|
|
17627
|
-
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17648
|
+
function hasConnectedProvidersCache() {
|
|
17649
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17650
|
+
return existsSync8(cacheFile);
|
|
17628
17651
|
}
|
|
17629
|
-
|
|
17630
|
-
|
|
17631
|
-
|
|
17632
|
-
|
|
17633
|
-
|
|
17634
|
-
|
|
17652
|
+
function writeConnectedProvidersCache(connected) {
|
|
17653
|
+
ensureCacheDir2();
|
|
17654
|
+
const cacheFile = getCacheFilePath(CONNECTED_PROVIDERS_CACHE_FILE);
|
|
17655
|
+
const data = {
|
|
17656
|
+
connected,
|
|
17657
|
+
updatedAt: new Date().toISOString()
|
|
17658
|
+
};
|
|
17659
|
+
try {
|
|
17660
|
+
writeFileSync2(cacheFile, JSON.stringify(data, null, 2));
|
|
17661
|
+
memConnected = connected;
|
|
17662
|
+
log("[connected-providers-cache] Cache written", { count: connected.length });
|
|
17663
|
+
} catch (err) {
|
|
17664
|
+
log("[connected-providers-cache] Error writing cache", { error: String(err) });
|
|
17665
|
+
}
|
|
17635
17666
|
}
|
|
17636
|
-
|
|
17637
|
-
|
|
17638
|
-
|
|
17639
|
-
|
|
17640
|
-
|
|
17641
|
-
|
|
17642
|
-
|
|
17643
|
-
|
|
17644
|
-
|
|
17645
|
-
|
|
17646
|
-
|
|
17667
|
+
function readProviderModelsCache() {
|
|
17668
|
+
if (memProviderModels !== undefined)
|
|
17669
|
+
return memProviderModels;
|
|
17670
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17671
|
+
if (!existsSync8(cacheFile)) {
|
|
17672
|
+
log("[connected-providers-cache] Provider-models cache file not found", { cacheFile });
|
|
17673
|
+
memProviderModels = null;
|
|
17674
|
+
return null;
|
|
17675
|
+
}
|
|
17676
|
+
try {
|
|
17677
|
+
const content = readFileSync4(cacheFile, "utf-8");
|
|
17678
|
+
const data = JSON.parse(content);
|
|
17679
|
+
log("[connected-providers-cache] Read provider-models cache", {
|
|
17680
|
+
providerCount: Object.keys(data.models).length,
|
|
17681
|
+
updatedAt: data.updatedAt
|
|
17682
|
+
});
|
|
17683
|
+
memProviderModels = data;
|
|
17684
|
+
return data;
|
|
17685
|
+
} catch (err) {
|
|
17686
|
+
log("[connected-providers-cache] Error reading provider-models cache", { error: String(err) });
|
|
17687
|
+
memProviderModels = null;
|
|
17688
|
+
return null;
|
|
17689
|
+
}
|
|
17647
17690
|
}
|
|
17648
|
-
|
|
17649
|
-
|
|
17650
|
-
|
|
17651
|
-
return existsSync8(cacheFile);
|
|
17652
|
-
}
|
|
17653
|
-
function writeProviderModelsCache(data) {
|
|
17654
|
-
ensureCacheDir2();
|
|
17655
|
-
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17656
|
-
const cacheData = {
|
|
17657
|
-
...data,
|
|
17658
|
-
updatedAt: new Date().toISOString()
|
|
17659
|
-
};
|
|
17660
|
-
try {
|
|
17661
|
-
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17662
|
-
log("[connected-providers-cache] Provider-models cache written", {
|
|
17663
|
-
providerCount: Object.keys(data.models).length
|
|
17664
|
-
});
|
|
17665
|
-
} catch (err) {
|
|
17666
|
-
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17691
|
+
function hasProviderModelsCache() {
|
|
17692
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17693
|
+
return existsSync8(cacheFile);
|
|
17667
17694
|
}
|
|
17668
|
-
|
|
17669
|
-
|
|
17670
|
-
|
|
17671
|
-
|
|
17672
|
-
|
|
17695
|
+
function writeProviderModelsCache(data) {
|
|
17696
|
+
ensureCacheDir2();
|
|
17697
|
+
const cacheFile = getCacheFilePath(PROVIDER_MODELS_CACHE_FILE);
|
|
17698
|
+
const cacheData = {
|
|
17699
|
+
...data,
|
|
17700
|
+
updatedAt: new Date().toISOString()
|
|
17701
|
+
};
|
|
17702
|
+
try {
|
|
17703
|
+
writeFileSync2(cacheFile, JSON.stringify(cacheData, null, 2));
|
|
17704
|
+
memProviderModels = cacheData;
|
|
17705
|
+
log("[connected-providers-cache] Provider-models cache written", {
|
|
17706
|
+
providerCount: Object.keys(data.models).length
|
|
17707
|
+
});
|
|
17708
|
+
} catch (err) {
|
|
17709
|
+
log("[connected-providers-cache] Error writing provider-models cache", { error: String(err) });
|
|
17710
|
+
}
|
|
17673
17711
|
}
|
|
17674
|
-
|
|
17675
|
-
|
|
17676
|
-
|
|
17677
|
-
|
|
17678
|
-
|
|
17679
|
-
|
|
17680
|
-
|
|
17681
|
-
|
|
17682
|
-
|
|
17683
|
-
|
|
17684
|
-
|
|
17685
|
-
|
|
17712
|
+
async function updateConnectedProvidersCache(client) {
|
|
17713
|
+
if (!client?.provider?.list) {
|
|
17714
|
+
log("[connected-providers-cache] client.provider.list not available");
|
|
17715
|
+
return;
|
|
17716
|
+
}
|
|
17717
|
+
try {
|
|
17718
|
+
const result = await client.provider.list();
|
|
17719
|
+
const connected = result.data?.connected ?? [];
|
|
17720
|
+
log("[connected-providers-cache] Fetched connected providers", {
|
|
17721
|
+
count: connected.length,
|
|
17722
|
+
providers: connected
|
|
17723
|
+
});
|
|
17724
|
+
writeConnectedProvidersCache(connected);
|
|
17725
|
+
const modelsByProvider = {};
|
|
17726
|
+
const allProviders = result.data?.all ?? [];
|
|
17727
|
+
for (const provider of allProviders) {
|
|
17728
|
+
if (provider.models) {
|
|
17729
|
+
const modelIds = Object.keys(provider.models);
|
|
17730
|
+
if (modelIds.length > 0) {
|
|
17731
|
+
modelsByProvider[provider.id] = modelIds;
|
|
17732
|
+
}
|
|
17686
17733
|
}
|
|
17687
17734
|
}
|
|
17735
|
+
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17736
|
+
providerCount: Object.keys(modelsByProvider).length,
|
|
17737
|
+
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17738
|
+
});
|
|
17739
|
+
writeProviderModelsCache({
|
|
17740
|
+
models: modelsByProvider,
|
|
17741
|
+
connected
|
|
17742
|
+
});
|
|
17743
|
+
} catch (err) {
|
|
17744
|
+
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17688
17745
|
}
|
|
17689
|
-
log("[connected-providers-cache] Extracted models from provider list", {
|
|
17690
|
-
providerCount: Object.keys(modelsByProvider).length,
|
|
17691
|
-
totalModels: Object.values(modelsByProvider).reduce((sum, ids) => sum + ids.length, 0)
|
|
17692
|
-
});
|
|
17693
|
-
writeProviderModelsCache({
|
|
17694
|
-
models: modelsByProvider,
|
|
17695
|
-
connected
|
|
17696
|
-
});
|
|
17697
|
-
} catch (err) {
|
|
17698
|
-
log("[connected-providers-cache] Error updating cache", { error: String(err) });
|
|
17699
17746
|
}
|
|
17747
|
+
return {
|
|
17748
|
+
readConnectedProvidersCache,
|
|
17749
|
+
hasConnectedProvidersCache,
|
|
17750
|
+
readProviderModelsCache,
|
|
17751
|
+
hasProviderModelsCache,
|
|
17752
|
+
writeProviderModelsCache,
|
|
17753
|
+
updateConnectedProvidersCache
|
|
17754
|
+
};
|
|
17700
17755
|
}
|
|
17756
|
+
var defaultConnectedProvidersCacheStore = createConnectedProvidersCacheStore(() => getOmoOpenCodeCacheDir());
|
|
17757
|
+
var {
|
|
17758
|
+
readConnectedProvidersCache,
|
|
17759
|
+
hasConnectedProvidersCache,
|
|
17760
|
+
readProviderModelsCache,
|
|
17761
|
+
hasProviderModelsCache,
|
|
17762
|
+
writeProviderModelsCache,
|
|
17763
|
+
updateConnectedProvidersCache
|
|
17764
|
+
} = defaultConnectedProvidersCacheStore;
|
|
17701
17765
|
|
|
17702
17766
|
// src/shared/model-availability.ts
|
|
17703
17767
|
init_logger();
|
|
@@ -20372,15 +20436,18 @@ async function handleSessionIdle(args) {
|
|
|
20372
20436
|
shouldSkipContinuation
|
|
20373
20437
|
} = args;
|
|
20374
20438
|
log(`[${HOOK_NAME}] session.idle`, { sessionID });
|
|
20439
|
+
console.error(`[TODO-DIAG] session.idle fired for ${sessionID}`);
|
|
20375
20440
|
const state2 = sessionStateStore.getState(sessionID);
|
|
20376
20441
|
if (state2.isRecovering) {
|
|
20377
20442
|
log(`[${HOOK_NAME}] Skipped: in recovery`, { sessionID });
|
|
20443
|
+
console.error(`[TODO-DIAG] BLOCKED: isRecovering=true`);
|
|
20378
20444
|
return;
|
|
20379
20445
|
}
|
|
20380
20446
|
if (state2.abortDetectedAt) {
|
|
20381
20447
|
const timeSinceAbort = Date.now() - state2.abortDetectedAt;
|
|
20382
20448
|
if (timeSinceAbort < ABORT_WINDOW_MS) {
|
|
20383
20449
|
log(`[${HOOK_NAME}] Skipped: abort detected via event ${timeSinceAbort}ms ago`, { sessionID });
|
|
20450
|
+
console.error(`[TODO-DIAG] BLOCKED: abort detected ${timeSinceAbort}ms ago`);
|
|
20384
20451
|
state2.abortDetectedAt = undefined;
|
|
20385
20452
|
return;
|
|
20386
20453
|
}
|
|
@@ -20389,6 +20456,7 @@ async function handleSessionIdle(args) {
|
|
|
20389
20456
|
const hasRunningBgTasks = backgroundManager ? backgroundManager.getTasksByParentSession(sessionID).some((task) => task.status === "running") : false;
|
|
20390
20457
|
if (hasRunningBgTasks) {
|
|
20391
20458
|
log(`[${HOOK_NAME}] Skipped: background tasks running`, { sessionID });
|
|
20459
|
+
console.error(`[TODO-DIAG] BLOCKED: background tasks running`, backgroundManager?.getTasksByParentSession(sessionID).filter((t) => t.status === "running").map((t) => t.id));
|
|
20392
20460
|
return;
|
|
20393
20461
|
}
|
|
20394
20462
|
try {
|
|
@@ -20399,10 +20467,12 @@ async function handleSessionIdle(args) {
|
|
|
20399
20467
|
const messages = normalizeSDKResponse(messagesResp, []);
|
|
20400
20468
|
if (isLastAssistantMessageAborted(messages)) {
|
|
20401
20469
|
log(`[${HOOK_NAME}] Skipped: last assistant message was aborted (API fallback)`, { sessionID });
|
|
20470
|
+
console.error(`[TODO-DIAG] BLOCKED: last assistant message aborted`);
|
|
20402
20471
|
return;
|
|
20403
20472
|
}
|
|
20404
20473
|
if (hasUnansweredQuestion(messages)) {
|
|
20405
20474
|
log(`[${HOOK_NAME}] Skipped: pending question awaiting user response`, { sessionID });
|
|
20475
|
+
console.error(`[TODO-DIAG] BLOCKED: hasUnansweredQuestion=true`);
|
|
20406
20476
|
return;
|
|
20407
20477
|
}
|
|
20408
20478
|
} catch (error) {
|
|
@@ -20414,21 +20484,27 @@ async function handleSessionIdle(args) {
|
|
|
20414
20484
|
todos = normalizeSDKResponse(response, [], { preferResponseOnMissingData: true });
|
|
20415
20485
|
} catch (error) {
|
|
20416
20486
|
log(`[${HOOK_NAME}] Todo fetch failed`, { sessionID, error: String(error) });
|
|
20487
|
+
console.error(`[TODO-DIAG] BLOCKED: todo fetch failed`, String(error));
|
|
20417
20488
|
return;
|
|
20418
20489
|
}
|
|
20419
20490
|
if (!todos || todos.length === 0) {
|
|
20491
|
+
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20420
20492
|
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20421
20493
|
log(`[${HOOK_NAME}] No todos`, { sessionID });
|
|
20494
|
+
console.error(`[TODO-DIAG] BLOCKED: no todos`);
|
|
20422
20495
|
return;
|
|
20423
20496
|
}
|
|
20424
20497
|
const incompleteCount = getIncompleteCount(todos);
|
|
20425
20498
|
if (incompleteCount === 0) {
|
|
20499
|
+
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20426
20500
|
sessionStateStore.resetContinuationProgress(sessionID);
|
|
20427
20501
|
log(`[${HOOK_NAME}] All todos complete`, { sessionID, total: todos.length });
|
|
20502
|
+
console.error(`[TODO-DIAG] BLOCKED: all todos complete (${todos.length})`);
|
|
20428
20503
|
return;
|
|
20429
20504
|
}
|
|
20430
20505
|
if (state2.inFlight) {
|
|
20431
20506
|
log(`[${HOOK_NAME}] Skipped: injection in flight`, { sessionID });
|
|
20507
|
+
console.error(`[TODO-DIAG] BLOCKED: inFlight=true`);
|
|
20432
20508
|
return;
|
|
20433
20509
|
}
|
|
20434
20510
|
if (state2.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES && state2.lastInjectedAt && Date.now() - state2.lastInjectedAt >= FAILURE_RESET_WINDOW_MS) {
|
|
@@ -20436,20 +20512,14 @@ async function handleSessionIdle(args) {
|
|
|
20436
20512
|
log(`[${HOOK_NAME}] Reset consecutive failures after recovery window`, { sessionID, failureResetWindowMs: FAILURE_RESET_WINDOW_MS });
|
|
20437
20513
|
}
|
|
20438
20514
|
if (state2.consecutiveFailures >= MAX_CONSECUTIVE_FAILURES) {
|
|
20439
|
-
log(`[${HOOK_NAME}] Skipped: max consecutive failures reached`, {
|
|
20440
|
-
|
|
20441
|
-
consecutiveFailures: state2.consecutiveFailures,
|
|
20442
|
-
maxConsecutiveFailures: MAX_CONSECUTIVE_FAILURES
|
|
20443
|
-
});
|
|
20515
|
+
log(`[${HOOK_NAME}] Skipped: max consecutive failures reached`, { sessionID, consecutiveFailures: state2.consecutiveFailures });
|
|
20516
|
+
console.error(`[TODO-DIAG] BLOCKED: consecutiveFailures=${state2.consecutiveFailures} >= ${MAX_CONSECUTIVE_FAILURES}`);
|
|
20444
20517
|
return;
|
|
20445
20518
|
}
|
|
20446
20519
|
const effectiveCooldown = CONTINUATION_COOLDOWN_MS * Math.pow(2, Math.min(state2.consecutiveFailures, 5));
|
|
20447
20520
|
if (state2.lastInjectedAt && Date.now() - state2.lastInjectedAt < effectiveCooldown) {
|
|
20448
|
-
log(`[${HOOK_NAME}] Skipped: cooldown active`, {
|
|
20449
|
-
|
|
20450
|
-
effectiveCooldown,
|
|
20451
|
-
consecutiveFailures: state2.consecutiveFailures
|
|
20452
|
-
});
|
|
20521
|
+
log(`[${HOOK_NAME}] Skipped: cooldown active`, { sessionID, effectiveCooldown, consecutiveFailures: state2.consecutiveFailures });
|
|
20522
|
+
console.error(`[TODO-DIAG] BLOCKED: cooldown active (${effectiveCooldown}ms, failures=${state2.consecutiveFailures})`);
|
|
20453
20523
|
return;
|
|
20454
20524
|
}
|
|
20455
20525
|
let resolvedInfo;
|
|
@@ -20470,10 +20540,12 @@ async function handleSessionIdle(args) {
|
|
|
20470
20540
|
const resolvedAgentName = resolvedInfo?.agent;
|
|
20471
20541
|
if (resolvedAgentName && skipAgents.some((s) => getAgentConfigKey(s) === getAgentConfigKey(resolvedAgentName))) {
|
|
20472
20542
|
log(`[${HOOK_NAME}] Skipped: agent in skipAgents list`, { sessionID, agent: resolvedAgentName });
|
|
20543
|
+
console.error(`[TODO-DIAG] BLOCKED: agent '${resolvedAgentName}' in skipAgents`);
|
|
20473
20544
|
return;
|
|
20474
20545
|
}
|
|
20475
20546
|
if ((compactionGuardActive || encounteredCompaction) && !resolvedInfo?.agent) {
|
|
20476
20547
|
log(`[${HOOK_NAME}] Skipped: compaction occurred but no agent info resolved`, { sessionID });
|
|
20548
|
+
console.error(`[TODO-DIAG] BLOCKED: compaction guard + no agent`);
|
|
20477
20549
|
return;
|
|
20478
20550
|
}
|
|
20479
20551
|
if (state2.recentCompactionAt && resolvedInfo?.agent) {
|
|
@@ -20481,16 +20553,20 @@ async function handleSessionIdle(args) {
|
|
|
20481
20553
|
}
|
|
20482
20554
|
if (isContinuationStopped?.(sessionID)) {
|
|
20483
20555
|
log(`[${HOOK_NAME}] Skipped: continuation stopped for session`, { sessionID });
|
|
20556
|
+
console.error(`[TODO-DIAG] BLOCKED: isContinuationStopped=true`);
|
|
20484
20557
|
return;
|
|
20485
20558
|
}
|
|
20486
20559
|
if (shouldSkipContinuation?.(sessionID)) {
|
|
20487
20560
|
log(`[${HOOK_NAME}] Skipped: another continuation hook already injected`, { sessionID });
|
|
20561
|
+
console.error(`[TODO-DIAG] BLOCKED: shouldSkipContinuation=true (gptPermissionContinuation recently injected)`);
|
|
20488
20562
|
return;
|
|
20489
20563
|
}
|
|
20490
20564
|
const progressUpdate = sessionStateStore.trackContinuationProgress(sessionID, incompleteCount, todos);
|
|
20491
20565
|
if (shouldStopForStagnation({ sessionID, incompleteCount, progressUpdate })) {
|
|
20566
|
+
console.error(`[TODO-DIAG] BLOCKED: stagnation detected (count=${progressUpdate.stagnationCount})`);
|
|
20492
20567
|
return;
|
|
20493
20568
|
}
|
|
20569
|
+
console.error(`[TODO-DIAG] PASSED all gates! Starting countdown (${incompleteCount}/${todos.length} incomplete)`);
|
|
20494
20570
|
startCountdown({
|
|
20495
20571
|
ctx,
|
|
20496
20572
|
sessionID,
|
|
@@ -20581,6 +20657,9 @@ function createTodoContinuationHandler(args) {
|
|
|
20581
20657
|
} = args;
|
|
20582
20658
|
return async ({ event }) => {
|
|
20583
20659
|
const props = event.properties;
|
|
20660
|
+
if (event.type === "session.idle") {
|
|
20661
|
+
console.error(`[TODO-DIAG] handler received session.idle event`, { sessionID: props?.sessionID });
|
|
20662
|
+
}
|
|
20584
20663
|
if (event.type === "session.error") {
|
|
20585
20664
|
const sessionID = props?.sessionID;
|
|
20586
20665
|
if (!sessionID)
|
|
@@ -36302,6 +36381,15 @@ function takePendingCall(callID) {
|
|
|
36302
36381
|
import * as fs6 from "fs";
|
|
36303
36382
|
import { tmpdir as tmpdir4 } from "os";
|
|
36304
36383
|
import { join as join30 } from "path";
|
|
36384
|
+
var ApplyPatchMetadataSchema = zod_default.object({
|
|
36385
|
+
files: zod_default.array(zod_default.object({
|
|
36386
|
+
filePath: zod_default.string(),
|
|
36387
|
+
movePath: zod_default.string().optional(),
|
|
36388
|
+
before: zod_default.string(),
|
|
36389
|
+
after: zod_default.string(),
|
|
36390
|
+
type: zod_default.string().optional()
|
|
36391
|
+
}))
|
|
36392
|
+
});
|
|
36305
36393
|
var DEBUG3 = process.env.COMMENT_CHECKER_DEBUG === "1";
|
|
36306
36394
|
var DEBUG_FILE3 = join30(tmpdir4(), "comment-checker-debug.log");
|
|
36307
36395
|
function debugLog3(...args) {
|
|
@@ -36362,15 +36450,6 @@ function createCommentCheckerHooks(config2) {
|
|
|
36362
36450
|
debugLog3("skipping due to tool failure in output");
|
|
36363
36451
|
return;
|
|
36364
36452
|
}
|
|
36365
|
-
const ApplyPatchMetadataSchema = zod_default.object({
|
|
36366
|
-
files: zod_default.array(zod_default.object({
|
|
36367
|
-
filePath: zod_default.string(),
|
|
36368
|
-
movePath: zod_default.string().optional(),
|
|
36369
|
-
before: zod_default.string(),
|
|
36370
|
-
after: zod_default.string(),
|
|
36371
|
-
type: zod_default.string().optional()
|
|
36372
|
-
}))
|
|
36373
|
-
});
|
|
36374
36453
|
if (toolLower === "apply_patch") {
|
|
36375
36454
|
const parsed = ApplyPatchMetadataSchema.safeParse(output.metadata);
|
|
36376
36455
|
if (!parsed.success) {
|
|
@@ -36841,7 +36920,7 @@ function isTokenLimitError(text) {
|
|
|
36841
36920
|
return false;
|
|
36842
36921
|
}
|
|
36843
36922
|
const lower = text.toLowerCase();
|
|
36844
|
-
return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw
|
|
36923
|
+
return TOKEN_LIMIT_KEYWORDS.some((kw) => lower.includes(kw));
|
|
36845
36924
|
}
|
|
36846
36925
|
function parseAnthropicTokenLimitError(err) {
|
|
36847
36926
|
try {
|
|
@@ -38259,7 +38338,6 @@ function createAnthropicContextWindowLimitRecoveryHook(ctx, options) {
|
|
|
38259
38338
|
};
|
|
38260
38339
|
}
|
|
38261
38340
|
// src/hooks/think-mode/detector.ts
|
|
38262
|
-
var ENGLISH_PATTERNS = [/\bultrathink\b/i, /\bthink\b/i];
|
|
38263
38341
|
var MULTILINGUAL_KEYWORDS = [
|
|
38264
38342
|
"\uC0DD\uAC01",
|
|
38265
38343
|
"\uAC80\uD1A0",
|
|
@@ -38345,8 +38423,7 @@ var MULTILINGUAL_KEYWORDS = [
|
|
|
38345
38423
|
"fikir",
|
|
38346
38424
|
"berfikir"
|
|
38347
38425
|
];
|
|
38348
|
-
var
|
|
38349
|
-
var THINK_PATTERNS = [...ENGLISH_PATTERNS, ...MULTILINGUAL_PATTERNS];
|
|
38426
|
+
var COMBINED_THINK_PATTERN = new RegExp(`\\b(?:ultrathink|think)\\b|${MULTILINGUAL_KEYWORDS.join("|")}`, "i");
|
|
38350
38427
|
var CODE_BLOCK_PATTERN = /```[\s\S]*?```/g;
|
|
38351
38428
|
var INLINE_CODE_PATTERN = /`[^`]+`/g;
|
|
38352
38429
|
function removeCodeBlocks(text) {
|
|
@@ -38354,7 +38431,7 @@ function removeCodeBlocks(text) {
|
|
|
38354
38431
|
}
|
|
38355
38432
|
function detectThinkKeyword(text) {
|
|
38356
38433
|
const textWithoutCode = removeCodeBlocks(text);
|
|
38357
|
-
return
|
|
38434
|
+
return COMBINED_THINK_PATTERN.test(textWithoutCode);
|
|
38358
38435
|
}
|
|
38359
38436
|
function extractPromptText(parts) {
|
|
38360
38437
|
return parts.filter((p) => p.type === "text").map((p) => p.text || "").join("");
|
|
@@ -39011,16 +39088,16 @@ async function loadPluginExtendedConfig() {
|
|
|
39011
39088
|
}
|
|
39012
39089
|
return merged;
|
|
39013
39090
|
}
|
|
39014
|
-
var
|
|
39091
|
+
var regexCache2 = new Map;
|
|
39015
39092
|
function getRegex(pattern) {
|
|
39016
|
-
let regex =
|
|
39093
|
+
let regex = regexCache2.get(pattern);
|
|
39017
39094
|
if (!regex) {
|
|
39018
39095
|
try {
|
|
39019
39096
|
regex = new RegExp(pattern);
|
|
39020
|
-
|
|
39097
|
+
regexCache2.set(pattern, regex);
|
|
39021
39098
|
} catch {
|
|
39022
39099
|
regex = new RegExp(pattern.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"));
|
|
39023
|
-
|
|
39100
|
+
regexCache2.set(pattern, regex);
|
|
39024
39101
|
}
|
|
39025
39102
|
}
|
|
39026
39103
|
return regex;
|
|
@@ -39886,8 +39963,6 @@ function createToolExecuteAfterHandler(ctx, config2) {
|
|
|
39886
39963
|
if (!output) {
|
|
39887
39964
|
return;
|
|
39888
39965
|
}
|
|
39889
|
-
const claudeConfig = await loadClaudeHooksConfig();
|
|
39890
|
-
const extendedConfig = await loadPluginExtendedConfig();
|
|
39891
39966
|
const cachedInput = getToolInput(input.sessionID, input.tool, input.callID) || {};
|
|
39892
39967
|
appendTranscriptEntry(input.sessionID, {
|
|
39893
39968
|
type: "tool_result",
|
|
@@ -39899,6 +39974,8 @@ function createToolExecuteAfterHandler(ctx, config2) {
|
|
|
39899
39974
|
if (isHookDisabled(config2, "PostToolUse")) {
|
|
39900
39975
|
return;
|
|
39901
39976
|
}
|
|
39977
|
+
const claudeConfig = await loadClaudeHooksConfig();
|
|
39978
|
+
const extendedConfig = await loadPluginExtendedConfig();
|
|
39902
39979
|
const postClient = {
|
|
39903
39980
|
session: {
|
|
39904
39981
|
messages: (opts) => ctx.client.session.messages(opts)
|
|
@@ -40079,8 +40156,6 @@ function createToolExecuteBeforeHandler(ctx, config2) {
|
|
|
40079
40156
|
output.args.todos = parsed;
|
|
40080
40157
|
log("todowrite: parsed todos string to array", { sessionID: input.sessionID });
|
|
40081
40158
|
}
|
|
40082
|
-
const claudeConfig = await loadClaudeHooksConfig();
|
|
40083
|
-
const extendedConfig = await loadPluginExtendedConfig();
|
|
40084
40159
|
appendTranscriptEntry(input.sessionID, {
|
|
40085
40160
|
type: "tool_use",
|
|
40086
40161
|
timestamp: new Date().toISOString(),
|
|
@@ -40091,6 +40166,8 @@ function createToolExecuteBeforeHandler(ctx, config2) {
|
|
|
40091
40166
|
if (isHookDisabled(config2, "PreToolUse")) {
|
|
40092
40167
|
return;
|
|
40093
40168
|
}
|
|
40169
|
+
const claudeConfig = await loadClaudeHooksConfig();
|
|
40170
|
+
const extendedConfig = await loadPluginExtendedConfig();
|
|
40094
40171
|
const preCtx = {
|
|
40095
40172
|
sessionId: input.sessionID,
|
|
40096
40173
|
toolName: input.tool,
|
|
@@ -43673,6 +43750,9 @@ async function injectContinuationPrompt(ctx, options) {
|
|
|
43673
43750
|
async function handleDetectedCompletion(ctx, input) {
|
|
43674
43751
|
const { sessionID, state: state3, loopState, directory, apiTimeoutMs } = input;
|
|
43675
43752
|
if (state3.ultrawork && !state3.verification_pending) {
|
|
43753
|
+
if (state3.verification_session_id) {
|
|
43754
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
43755
|
+
}
|
|
43676
43756
|
const verificationState = loopState.markVerificationPending(sessionID);
|
|
43677
43757
|
if (!verificationState) {
|
|
43678
43758
|
log(`[${HOOK_NAME3}] Failed to transition ultrawork loop to verification`, {
|
|
@@ -43909,6 +43989,9 @@ async function handleFailedVerification(ctx, input) {
|
|
|
43909
43989
|
});
|
|
43910
43990
|
return false;
|
|
43911
43991
|
}
|
|
43992
|
+
if (state3.verification_session_id) {
|
|
43993
|
+
ctx.client.session.abort({ path: { id: state3.verification_session_id } }).catch(() => {});
|
|
43994
|
+
}
|
|
43912
43995
|
const resumedState = loopState.restartAfterFailedVerification(parentSessionID, messageCountAtStart);
|
|
43913
43996
|
if (!resumedState) {
|
|
43914
43997
|
log(`[${HOOK_NAME3}] Failed to restart loop after verification failure`, {
|
|
@@ -47514,9 +47597,9 @@ var BabysittingConfigSchema = exports_external.object({
|
|
|
47514
47597
|
});
|
|
47515
47598
|
// src/config/schema/background-task.ts
|
|
47516
47599
|
var CircuitBreakerConfigSchema = exports_external.object({
|
|
47600
|
+
enabled: exports_external.boolean().optional(),
|
|
47517
47601
|
maxToolCalls: exports_external.number().int().min(10).optional(),
|
|
47518
|
-
|
|
47519
|
-
repetitionThresholdPercent: exports_external.number().gt(0).max(100).optional()
|
|
47602
|
+
consecutiveThreshold: exports_external.number().int().min(5).optional()
|
|
47520
47603
|
});
|
|
47521
47604
|
var BackgroundTaskConfigSchema = exports_external.object({
|
|
47522
47605
|
defaultConcurrency: exports_external.number().min(1).optional(),
|
|
@@ -47712,7 +47795,8 @@ var HookNameSchema = exports_external.enum([
|
|
|
47712
47795
|
"write-existing-file-guard",
|
|
47713
47796
|
"anthropic-effort",
|
|
47714
47797
|
"hashline-read-enhancer",
|
|
47715
|
-
"read-image-resizer"
|
|
47798
|
+
"read-image-resizer",
|
|
47799
|
+
"todo-description-override"
|
|
47716
47800
|
]);
|
|
47717
47801
|
// src/config/schema/notification.ts
|
|
47718
47802
|
var NotificationConfigSchema = exports_external.object({
|
|
@@ -49115,7 +49199,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49115
49199
|
- \`--worktree <path>\` (optional): absolute path to an existing git worktree to work in
|
|
49116
49200
|
- If specified and valid: hook pre-sets worktree_path in boulder.json
|
|
49117
49201
|
- If specified but invalid: you must run \`git worktree add <path> <branch>\` first
|
|
49118
|
-
- If omitted:
|
|
49202
|
+
- If omitted: work directly in the current project directory (no worktree)
|
|
49119
49203
|
|
|
49120
49204
|
## WHAT TO DO
|
|
49121
49205
|
|
|
@@ -49132,7 +49216,7 @@ var START_WORK_TEMPLATE = `You are starting a Sisyphus work session.
|
|
|
49132
49216
|
- If ONE plan: auto-select it
|
|
49133
49217
|
- If MULTIPLE plans: show list with timestamps, ask user to select
|
|
49134
49218
|
|
|
49135
|
-
4. **Worktree Setup** (when \`worktree_path\` not already set in boulder.json):
|
|
49219
|
+
4. **Worktree Setup** (ONLY when \`--worktree\` was explicitly specified and \`worktree_path\` not already set in boulder.json):
|
|
49136
49220
|
1. \`git worktree list --porcelain\` \u2014 see available worktrees
|
|
49137
49221
|
2. Create: \`git worktree add <absolute-path> <branch-or-HEAD>\`
|
|
49138
49222
|
3. Update boulder.json to add \`"worktree_path": "<absolute-path>"\`
|
|
@@ -49194,9 +49278,41 @@ Reading plan and beginning execution...
|
|
|
49194
49278
|
|
|
49195
49279
|
- The session_id is injected by the hook - use it directly
|
|
49196
49280
|
- Always update boulder.json BEFORE starting work
|
|
49197
|
-
-
|
|
49281
|
+
- If worktree_path is set in boulder.json, all work happens inside that worktree directory
|
|
49198
49282
|
- Read the FULL plan file before delegating any tasks
|
|
49199
|
-
- Follow atlas delegation protocols (7-section format)
|
|
49283
|
+
- Follow atlas delegation protocols (7-section format)
|
|
49284
|
+
|
|
49285
|
+
## TASK BREAKDOWN (MANDATORY)
|
|
49286
|
+
|
|
49287
|
+
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.
|
|
49288
|
+
|
|
49289
|
+
**How to break down**:
|
|
49290
|
+
- Each plan checkbox item (e.g., \`- [ ] Add user authentication\`) must be split into concrete, actionable sub-tasks
|
|
49291
|
+
- Sub-tasks should be specific enough that each one touches a clear set of files/functions
|
|
49292
|
+
- Include: file to modify, what to change, expected behavior, and how to verify
|
|
49293
|
+
- 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
|
|
49294
|
+
|
|
49295
|
+
**Example breakdown**:
|
|
49296
|
+
Plan task: \`- [ ] Add rate limiting to API\`
|
|
49297
|
+
\u2192 Todo items:
|
|
49298
|
+
1. Create \`src/middleware/rate-limiter.ts\` with sliding window algorithm (max 100 req/min per IP)
|
|
49299
|
+
2. Add RateLimiter middleware to \`src/app.ts\` router chain, before auth middleware
|
|
49300
|
+
3. Add rate limit headers (X-RateLimit-Limit, X-RateLimit-Remaining) to response in \`rate-limiter.ts\`
|
|
49301
|
+
4. Add test: verify 429 response after exceeding limit in \`src/middleware/rate-limiter.test.ts\`
|
|
49302
|
+
5. Add test: verify headers are present on normal responses
|
|
49303
|
+
|
|
49304
|
+
Register these as task/todo items so progress is tracked and visible throughout the session.
|
|
49305
|
+
|
|
49306
|
+
## WORKTREE COMPLETION
|
|
49307
|
+
|
|
49308
|
+
When working in a worktree (\`worktree_path\` is set in boulder.json) and ALL plan tasks are complete:
|
|
49309
|
+
1. Commit all remaining changes in the worktree
|
|
49310
|
+
2. Switch to the main working directory (the original repo, NOT the worktree)
|
|
49311
|
+
3. Merge the worktree branch into the current branch: \`git merge <worktree-branch>\`
|
|
49312
|
+
4. If merge succeeds, clean up: \`git worktree remove <worktree-path>\`
|
|
49313
|
+
5. Remove the boulder.json state
|
|
49314
|
+
|
|
49315
|
+
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).`;
|
|
49200
49316
|
|
|
49201
49317
|
// src/features/builtin-commands/templates/handoff.ts
|
|
49202
49318
|
var HANDOFF_TEMPLATE = `# Handoff Command
|
|
@@ -49578,9 +49694,6 @@ function skillToCommandInfo(skill) {
|
|
|
49578
49694
|
lazyContentLoader: skill.lazyContent
|
|
49579
49695
|
};
|
|
49580
49696
|
}
|
|
49581
|
-
function filterDiscoveredCommandsByScope(commands3, scope) {
|
|
49582
|
-
return commands3.filter((command) => command.scope === scope);
|
|
49583
|
-
}
|
|
49584
49697
|
async function discoverAllCommands(options) {
|
|
49585
49698
|
const discoveredCommands = discoverCommandsSync(process.cwd(), {
|
|
49586
49699
|
pluginsEnabled: options?.pluginsEnabled,
|
|
@@ -49588,14 +49701,17 @@ async function discoverAllCommands(options) {
|
|
|
49588
49701
|
});
|
|
49589
49702
|
const skills2 = options?.skills ?? await discoverAllSkills();
|
|
49590
49703
|
const skillCommands = skills2.map(skillToCommandInfo);
|
|
49704
|
+
const scopeOrder = ["project", "user", "opencode-project", "opencode", "builtin", "plugin"];
|
|
49705
|
+
const grouped = new Map;
|
|
49706
|
+
for (const cmd of discoveredCommands) {
|
|
49707
|
+
const list = grouped.get(cmd.scope) ?? [];
|
|
49708
|
+
list.push(cmd);
|
|
49709
|
+
grouped.set(cmd.scope, list);
|
|
49710
|
+
}
|
|
49711
|
+
const orderedCommands = scopeOrder.flatMap((scope) => grouped.get(scope) ?? []);
|
|
49591
49712
|
return [
|
|
49592
49713
|
...skillCommands,
|
|
49593
|
-
...
|
|
49594
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "user"),
|
|
49595
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "opencode-project"),
|
|
49596
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "opencode"),
|
|
49597
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "builtin"),
|
|
49598
|
-
...filterDiscoveredCommandsByScope(discoveredCommands, "plugin")
|
|
49714
|
+
...orderedCommands
|
|
49599
49715
|
];
|
|
49600
49716
|
}
|
|
49601
49717
|
async function findCommand2(commandName, options) {
|
|
@@ -53356,6 +53472,7 @@ function getErrorMessage2(error48) {
|
|
|
53356
53472
|
return "";
|
|
53357
53473
|
}
|
|
53358
53474
|
}
|
|
53475
|
+
var DEFAULT_RETRY_PATTERN = new RegExp(`\\b(${DEFAULT_CONFIG2.retry_on_errors.join("|")})\\b`);
|
|
53359
53476
|
function extractStatusCode(error48, retryOnErrors) {
|
|
53360
53477
|
if (!error48)
|
|
53361
53478
|
return;
|
|
@@ -53370,8 +53487,7 @@ function extractStatusCode(error48, retryOnErrors) {
|
|
|
53370
53487
|
if (statusCode !== undefined) {
|
|
53371
53488
|
return statusCode;
|
|
53372
53489
|
}
|
|
53373
|
-
const
|
|
53374
|
-
const pattern = new RegExp(`\\b(${codes.join("|")})\\b`);
|
|
53490
|
+
const pattern = retryOnErrors ? new RegExp(`\\b(${retryOnErrors.join("|")})\\b`) : DEFAULT_RETRY_PATTERN;
|
|
53375
53491
|
const message = getErrorMessage2(error48);
|
|
53376
53492
|
const statusMatch = message.match(pattern);
|
|
53377
53493
|
if (statusMatch) {
|
|
@@ -54652,97 +54768,97 @@ function toImageDimensions(width, height) {
|
|
|
54652
54768
|
}
|
|
54653
54769
|
return { width, height };
|
|
54654
54770
|
}
|
|
54655
|
-
function parsePngDimensions(
|
|
54656
|
-
if (
|
|
54771
|
+
function parsePngDimensions(buffer2) {
|
|
54772
|
+
if (buffer2.length < 24) {
|
|
54657
54773
|
return null;
|
|
54658
54774
|
}
|
|
54659
|
-
const isPngSignature =
|
|
54660
|
-
if (!isPngSignature ||
|
|
54775
|
+
const isPngSignature = buffer2[0] === 137 && buffer2[1] === 80 && buffer2[2] === 78 && buffer2[3] === 71 && buffer2[4] === 13 && buffer2[5] === 10 && buffer2[6] === 26 && buffer2[7] === 10;
|
|
54776
|
+
if (!isPngSignature || buffer2.toString("ascii", 12, 16) !== "IHDR") {
|
|
54661
54777
|
return null;
|
|
54662
54778
|
}
|
|
54663
|
-
const width =
|
|
54664
|
-
const height =
|
|
54779
|
+
const width = buffer2.readUInt32BE(16);
|
|
54780
|
+
const height = buffer2.readUInt32BE(20);
|
|
54665
54781
|
return toImageDimensions(width, height);
|
|
54666
54782
|
}
|
|
54667
|
-
function parseGifDimensions(
|
|
54668
|
-
if (
|
|
54783
|
+
function parseGifDimensions(buffer2) {
|
|
54784
|
+
if (buffer2.length < 10) {
|
|
54669
54785
|
return null;
|
|
54670
54786
|
}
|
|
54671
|
-
if (
|
|
54787
|
+
if (buffer2.toString("ascii", 0, 4) !== "GIF8") {
|
|
54672
54788
|
return null;
|
|
54673
54789
|
}
|
|
54674
|
-
const width =
|
|
54675
|
-
const height =
|
|
54790
|
+
const width = buffer2.readUInt16LE(6);
|
|
54791
|
+
const height = buffer2.readUInt16LE(8);
|
|
54676
54792
|
return toImageDimensions(width, height);
|
|
54677
54793
|
}
|
|
54678
|
-
function parseJpegDimensions(
|
|
54679
|
-
if (
|
|
54794
|
+
function parseJpegDimensions(buffer2) {
|
|
54795
|
+
if (buffer2.length < 4 || buffer2[0] !== 255 || buffer2[1] !== 216) {
|
|
54680
54796
|
return null;
|
|
54681
54797
|
}
|
|
54682
54798
|
let offset = 2;
|
|
54683
|
-
while (offset <
|
|
54684
|
-
if (
|
|
54799
|
+
while (offset < buffer2.length) {
|
|
54800
|
+
if (buffer2[offset] !== 255) {
|
|
54685
54801
|
offset += 1;
|
|
54686
54802
|
continue;
|
|
54687
54803
|
}
|
|
54688
|
-
while (offset <
|
|
54804
|
+
while (offset < buffer2.length && buffer2[offset] === 255) {
|
|
54689
54805
|
offset += 1;
|
|
54690
54806
|
}
|
|
54691
|
-
if (offset >=
|
|
54807
|
+
if (offset >= buffer2.length) {
|
|
54692
54808
|
return null;
|
|
54693
54809
|
}
|
|
54694
|
-
const marker =
|
|
54810
|
+
const marker = buffer2[offset];
|
|
54695
54811
|
offset += 1;
|
|
54696
54812
|
if (marker === 217 || marker === 218) {
|
|
54697
54813
|
break;
|
|
54698
54814
|
}
|
|
54699
|
-
if (offset + 1 >=
|
|
54815
|
+
if (offset + 1 >= buffer2.length) {
|
|
54700
54816
|
return null;
|
|
54701
54817
|
}
|
|
54702
|
-
const segmentLength =
|
|
54818
|
+
const segmentLength = buffer2.readUInt16BE(offset);
|
|
54703
54819
|
if (segmentLength < 2) {
|
|
54704
54820
|
return null;
|
|
54705
54821
|
}
|
|
54706
|
-
if ((marker === 192 || marker === 194) && offset + 7 <
|
|
54707
|
-
const height =
|
|
54708
|
-
const width =
|
|
54822
|
+
if ((marker === 192 || marker === 194) && offset + 7 < buffer2.length) {
|
|
54823
|
+
const height = buffer2.readUInt16BE(offset + 3);
|
|
54824
|
+
const width = buffer2.readUInt16BE(offset + 5);
|
|
54709
54825
|
return toImageDimensions(width, height);
|
|
54710
54826
|
}
|
|
54711
54827
|
offset += segmentLength;
|
|
54712
54828
|
}
|
|
54713
54829
|
return null;
|
|
54714
54830
|
}
|
|
54715
|
-
function readUInt24LE(
|
|
54716
|
-
return
|
|
54831
|
+
function readUInt24LE(buffer2, offset) {
|
|
54832
|
+
return buffer2[offset] | buffer2[offset + 1] << 8 | buffer2[offset + 2] << 16;
|
|
54717
54833
|
}
|
|
54718
|
-
function parseWebpDimensions(
|
|
54719
|
-
if (
|
|
54834
|
+
function parseWebpDimensions(buffer2) {
|
|
54835
|
+
if (buffer2.length < 16) {
|
|
54720
54836
|
return null;
|
|
54721
54837
|
}
|
|
54722
|
-
if (
|
|
54838
|
+
if (buffer2.toString("ascii", 0, 4) !== "RIFF" || buffer2.toString("ascii", 8, 12) !== "WEBP") {
|
|
54723
54839
|
return null;
|
|
54724
54840
|
}
|
|
54725
|
-
const chunkType =
|
|
54841
|
+
const chunkType = buffer2.toString("ascii", 12, 16);
|
|
54726
54842
|
if (chunkType === "VP8 ") {
|
|
54727
|
-
if (
|
|
54843
|
+
if (buffer2[23] !== 157 || buffer2[24] !== 1 || buffer2[25] !== 42) {
|
|
54728
54844
|
return null;
|
|
54729
54845
|
}
|
|
54730
|
-
const width =
|
|
54731
|
-
const height =
|
|
54846
|
+
const width = buffer2.readUInt16LE(26) & 16383;
|
|
54847
|
+
const height = buffer2.readUInt16LE(28) & 16383;
|
|
54732
54848
|
return toImageDimensions(width, height);
|
|
54733
54849
|
}
|
|
54734
54850
|
if (chunkType === "VP8L") {
|
|
54735
|
-
if (
|
|
54851
|
+
if (buffer2.length < 25 || buffer2[20] !== 47) {
|
|
54736
54852
|
return null;
|
|
54737
54853
|
}
|
|
54738
|
-
const bits =
|
|
54854
|
+
const bits = buffer2.readUInt32LE(21);
|
|
54739
54855
|
const width = (bits & 16383) + 1;
|
|
54740
54856
|
const height = (bits >>> 14 & 16383) + 1;
|
|
54741
54857
|
return toImageDimensions(width, height);
|
|
54742
54858
|
}
|
|
54743
54859
|
if (chunkType === "VP8X") {
|
|
54744
|
-
const width = readUInt24LE(
|
|
54745
|
-
const height = readUInt24LE(
|
|
54860
|
+
const width = readUInt24LE(buffer2, 24) + 1;
|
|
54861
|
+
const height = readUInt24LE(buffer2, 27) + 1;
|
|
54746
54862
|
return toImageDimensions(width, height);
|
|
54747
54863
|
}
|
|
54748
54864
|
return null;
|
|
@@ -54757,22 +54873,22 @@ function parseImageDimensions(base64DataUrl, mimeType) {
|
|
|
54757
54873
|
return null;
|
|
54758
54874
|
}
|
|
54759
54875
|
const headerBase64 = rawBase64.length > HEADER_BASE64_CHARS ? rawBase64.slice(0, HEADER_BASE64_CHARS) : rawBase64;
|
|
54760
|
-
const
|
|
54761
|
-
if (
|
|
54876
|
+
const buffer2 = Buffer.from(headerBase64, "base64");
|
|
54877
|
+
if (buffer2.length === 0) {
|
|
54762
54878
|
return null;
|
|
54763
54879
|
}
|
|
54764
54880
|
const normalizedMime = mimeType.toLowerCase();
|
|
54765
54881
|
if (normalizedMime === "image/png") {
|
|
54766
|
-
return parsePngDimensions(
|
|
54882
|
+
return parsePngDimensions(buffer2);
|
|
54767
54883
|
}
|
|
54768
54884
|
if (normalizedMime === "image/gif") {
|
|
54769
|
-
return parseGifDimensions(
|
|
54885
|
+
return parseGifDimensions(buffer2);
|
|
54770
54886
|
}
|
|
54771
54887
|
if (normalizedMime === "image/jpeg" || normalizedMime === "image/jpg") {
|
|
54772
|
-
return parseJpegDimensions(
|
|
54888
|
+
return parseJpegDimensions(buffer2);
|
|
54773
54889
|
}
|
|
54774
54890
|
if (normalizedMime === "image/webp") {
|
|
54775
|
-
return parseWebpDimensions(
|
|
54891
|
+
return parseWebpDimensions(buffer2);
|
|
54776
54892
|
}
|
|
54777
54893
|
return null;
|
|
54778
54894
|
} catch {
|
|
@@ -55062,6 +55178,46 @@ function createReadImageResizerHook(_ctx) {
|
|
|
55062
55178
|
}
|
|
55063
55179
|
};
|
|
55064
55180
|
}
|
|
55181
|
+
// src/hooks/todo-description-override/description.ts
|
|
55182
|
+
var TODOWRITE_DESCRIPTION = `Use this tool to create and manage a structured task list for tracking progress on multi-step work.
|
|
55183
|
+
|
|
55184
|
+
## Todo Format (MANDATORY)
|
|
55185
|
+
|
|
55186
|
+
Each todo title MUST encode four elements: WHERE, WHY, HOW, and EXPECTED RESULT.
|
|
55187
|
+
|
|
55188
|
+
Format: "[WHERE] [HOW] to [WHY] \u2014 expect [RESULT]"
|
|
55189
|
+
|
|
55190
|
+
GOOD:
|
|
55191
|
+
- "src/utils/validation.ts: Add validateEmail() for input sanitization \u2014 returns boolean"
|
|
55192
|
+
- "UserService.create(): Call validateEmail() before DB insert \u2014 rejects invalid emails with 400"
|
|
55193
|
+
- "validation.test.ts: Add test for missing @ sign \u2014 expect validateEmail('foo') to return false"
|
|
55194
|
+
|
|
55195
|
+
BAD:
|
|
55196
|
+
- "Implement email validation" (where? how? what result?)
|
|
55197
|
+
- "Add dark mode" (this is a feature, not a todo)
|
|
55198
|
+
- "Fix auth" (what file? what changes? what's expected?)
|
|
55199
|
+
|
|
55200
|
+
## Granularity Rules
|
|
55201
|
+
|
|
55202
|
+
Each todo MUST be a single atomic action completable in 1-3 tool calls. If it needs more, split it.
|
|
55203
|
+
|
|
55204
|
+
**Size test**: Can you complete this todo by editing one file or running one command? If not, it's too big.
|
|
55205
|
+
|
|
55206
|
+
## Task Management
|
|
55207
|
+
- One in_progress at a time. Complete it before starting the next.
|
|
55208
|
+
- Mark completed immediately after finishing each item.
|
|
55209
|
+
- Skip this tool for single trivial tasks (one-step, obvious action).`;
|
|
55210
|
+
|
|
55211
|
+
// src/hooks/todo-description-override/hook.ts
|
|
55212
|
+
function createTodoDescriptionOverrideHook() {
|
|
55213
|
+
return {
|
|
55214
|
+
"tool.definition": async (input, output) => {
|
|
55215
|
+
if (input.toolID === "todowrite") {
|
|
55216
|
+
output.description = TODOWRITE_DESCRIPTION;
|
|
55217
|
+
}
|
|
55218
|
+
}
|
|
55219
|
+
};
|
|
55220
|
+
}
|
|
55065
55221
|
// src/hooks/anthropic-effort/hook.ts
|
|
55066
55222
|
var OPUS_4_6_PATTERN = /claude-opus-4[-.]6/i;
|
|
55067
55223
|
function isClaudeProvider(providerID, modelID) {
|
|
@@ -73166,8 +73322,8 @@ function convertBase64ImageToJpeg(base64Data, mimeType) {
|
|
|
73166
73322
|
const tempFiles = [inputPath];
|
|
73167
73323
|
try {
|
|
73168
73324
|
const cleanBase64 = base64Data.replace(/^data:[^;]+;base64,/, "");
|
|
73169
|
-
const
|
|
73170
|
-
writeFileSync19(inputPath,
|
|
73325
|
+
const buffer2 = Buffer.from(cleanBase64, "base64");
|
|
73326
|
+
writeFileSync19(inputPath, buffer2);
|
|
73171
73327
|
log(`[image-converter] Converting Base64 ${mimeType} to JPEG`);
|
|
73172
73328
|
const outputPath = convertImageToJpeg(inputPath, mimeType);
|
|
73173
73329
|
tempFiles.push(outputPath);
|
|
@@ -75636,10 +75792,11 @@ Returns summary format: id, subject, status, owner, blockedBy (not full descript
|
|
|
75636
75792
|
allTasks.push(task);
|
|
75637
75793
|
}
|
|
75638
75794
|
}
|
|
75795
|
+
const taskMap = new Map(allTasks.map((t) => [t.id, t]));
|
|
75639
75796
|
const activeTasks = allTasks.filter((task) => task.status !== "completed" && task.status !== "deleted");
|
|
75640
75797
|
const summaries = activeTasks.map((task) => {
|
|
75641
75798
|
const unresolvedBlockers = task.blockedBy.filter((blockerId) => {
|
|
75642
|
-
const blockerTask =
|
|
75799
|
+
const blockerTask = taskMap.get(blockerId);
|
|
75643
75800
|
return !blockerTask || blockerTask.status !== "completed";
|
|
75644
75801
|
});
|
|
75645
75802
|
return {
|
|
@@ -76328,6 +76485,15 @@ function applyPrepend(lines, text) {
|
|
|
76328
76485
|
}
|
|
76329
76486
|
|
|
76330
76487
|
// src/tools/hashline-edit/edit-operations.ts
|
|
76488
|
+
function arraysEqual(a, b) {
|
|
76489
|
+
if (a.length !== b.length)
|
|
76490
|
+
return false;
|
|
76491
|
+
for (let i2 = 0;i2 < a.length; i2++) {
|
|
76492
|
+
if (a[i2] !== b[i2])
|
|
76493
|
+
return false;
|
|
76494
|
+
}
|
|
76495
|
+
return true;
|
|
76496
|
+
}
|
|
76331
76497
|
function applyHashlineEditsWithReport(content, edits) {
|
|
76332
76498
|
if (edits.length === 0) {
|
|
76333
76499
|
return {
|
|
@@ -76357,9 +76523,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76357
76523
|
switch (edit.op) {
|
|
76358
76524
|
case "replace": {
|
|
76359
76525
|
const next = edit.end ? applyReplaceLines(lines, edit.pos, edit.end, edit.lines, { skipValidation: true }) : applySetLine(lines, edit.pos, edit.lines, { skipValidation: true });
|
|
76360
|
-
if (next
|
|
76361
|
-
`) === lines.join(`
|
|
76362
|
-
`)) {
|
|
76526
|
+
if (arraysEqual(next, lines)) {
|
|
76363
76527
|
noopEdits += 1;
|
|
76364
76528
|
break;
|
|
76365
76529
|
}
|
|
@@ -76368,9 +76532,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76368
76532
|
}
|
|
76369
76533
|
case "append": {
|
|
76370
76534
|
const next = edit.pos ? applyInsertAfter(lines, edit.pos, edit.lines, { skipValidation: true }) : applyAppend(lines, edit.lines);
|
|
76371
|
-
if (next
|
|
76372
|
-
`) === lines.join(`
|
|
76373
|
-
`)) {
|
|
76535
|
+
if (arraysEqual(next, lines)) {
|
|
76374
76536
|
noopEdits += 1;
|
|
76375
76537
|
break;
|
|
76376
76538
|
}
|
|
@@ -76379,9 +76541,7 @@ function applyHashlineEditsWithReport(content, edits) {
|
|
|
76379
76541
|
}
|
|
76380
76542
|
case "prepend": {
|
|
76381
76543
|
const next = edit.pos ? applyInsertBefore(lines, edit.pos, edit.lines, { skipValidation: true }) : applyPrepend(lines, edit.lines);
|
|
76382
|
-
if (next
|
|
76383
|
-
`) === lines.join(`
|
|
76384
|
-
`)) {
|
|
76544
|
+
if (arraysEqual(next, lines)) {
|
|
76385
76545
|
noopEdits += 1;
|
|
76386
76546
|
break;
|
|
76387
76547
|
}
|
|
@@ -77406,6 +77566,7 @@ function createToolGuardHooks(args) {
|
|
|
77406
77566
|
const hashlineReadEnhancer = isHookEnabled("hashline-read-enhancer") ? safeHook("hashline-read-enhancer", () => createHashlineReadEnhancerHook(ctx, { hashline_edit: { enabled: pluginConfig.hashline_edit ?? false } })) : null;
|
|
77407
77567
|
const jsonErrorRecovery = isHookEnabled("json-error-recovery") ? safeHook("json-error-recovery", () => createJsonErrorRecoveryHook(ctx)) : null;
|
|
77408
77568
|
const readImageResizer = isHookEnabled("read-image-resizer") ? safeHook("read-image-resizer", () => createReadImageResizerHook(ctx)) : null;
|
|
77569
|
+
const todoDescriptionOverride = isHookEnabled("todo-description-override") ? safeHook("todo-description-override", () => createTodoDescriptionOverrideHook()) : null;
|
|
77409
77570
|
return {
|
|
77410
77571
|
commentChecker,
|
|
77411
77572
|
toolOutputTruncator,
|
|
@@ -77417,7 +77578,8 @@ function createToolGuardHooks(args) {
|
|
|
77417
77578
|
writeExistingFileGuard,
|
|
77418
77579
|
hashlineReadEnhancer,
|
|
77419
77580
|
jsonErrorRecovery,
|
|
77420
|
-
readImageResizer
|
|
77581
|
+
readImageResizer,
|
|
77582
|
+
todoDescriptionOverride
|
|
77421
77583
|
};
|
|
77422
77584
|
}
|
|
77423
77585
|
|
|
@@ -77944,8 +78106,8 @@ var MIN_STABILITY_TIME_MS2 = 10 * 1000;
|
|
|
77944
78106
|
var DEFAULT_STALE_TIMEOUT_MS = 1200000;
|
|
77945
78107
|
var DEFAULT_MESSAGE_STALENESS_TIMEOUT_MS = 1800000;
|
|
77946
78108
|
var DEFAULT_MAX_TOOL_CALLS = 200;
|
|
77947
|
-
var
|
|
77948
|
-
var
|
|
78109
|
+
var DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD = 20;
|
|
78110
|
+
var DEFAULT_CIRCUIT_BREAKER_ENABLED = true;
|
|
77949
78111
|
var MIN_RUNTIME_BEFORE_STALE_MS = 30000;
|
|
77950
78112
|
var MIN_IDLE_TIME_MS = 5000;
|
|
77951
78113
|
var POLLING_INTERVAL_MS = 3000;
|
|
@@ -78366,6 +78528,22 @@ function removeTaskToastTracking(taskId) {
|
|
|
78366
78528
|
}
|
|
78367
78529
|
}
|
|
78368
78530
|
|
|
78531
|
+
// src/features/background-agent/session-status-classifier.ts
|
|
78532
|
+
var ACTIVE_SESSION_STATUSES = new Set(["busy", "retry", "running"]);
|
|
78533
|
+
var KNOWN_TERMINAL_STATUSES = new Set(["idle", "interrupted"]);
|
|
78534
|
+
function isActiveSessionStatus(type2) {
|
|
78535
|
+
if (ACTIVE_SESSION_STATUSES.has(type2)) {
|
|
78536
|
+
return true;
|
|
78537
|
+
}
|
|
78538
|
+
if (!KNOWN_TERMINAL_STATUSES.has(type2)) {
|
|
78539
|
+
log("[background-agent] Unknown session status type encountered:", type2);
|
|
78540
|
+
}
|
|
78541
|
+
return false;
|
|
78542
|
+
}
|
|
78543
|
+
function isTerminalSessionStatus(type2) {
|
|
78544
|
+
return KNOWN_TERMINAL_STATUSES.has(type2) && type2 !== "idle";
|
|
78545
|
+
}
|
|
78546
|
+
|
|
78369
78547
|
// src/features/background-agent/task-poller.ts
|
|
78370
78548
|
var TERMINAL_TASK_STATUSES = new Set([
|
|
78371
78549
|
"completed",
|
|
@@ -78444,7 +78622,7 @@ async function checkAndInterruptStaleTasks(args) {
|
|
|
78444
78622
|
if (!startedAt || !sessionID)
|
|
78445
78623
|
continue;
|
|
78446
78624
|
const sessionStatus = sessionStatuses?.[sessionID]?.type;
|
|
78447
|
-
const sessionIsRunning = sessionStatus !== undefined && sessionStatus
|
|
78625
|
+
const sessionIsRunning = sessionStatus !== undefined && isActiveSessionStatus(sessionStatus);
|
|
78448
78626
|
const runtime = now - startedAt.getTime();
|
|
78449
78627
|
if (!task.progress?.lastUpdate) {
|
|
78450
78628
|
if (sessionIsRunning)
|
|
@@ -78500,51 +78678,57 @@ async function checkAndInterruptStaleTasks(args) {
|
|
|
78500
78678
|
// src/features/background-agent/loop-detector.ts
|
|
78501
78679
|
function resolveCircuitBreakerSettings(config4) {
|
|
78502
78680
|
return {
|
|
78681
|
+
enabled: config4?.circuitBreaker?.enabled ?? DEFAULT_CIRCUIT_BREAKER_ENABLED,
|
|
78503
78682
|
maxToolCalls: config4?.circuitBreaker?.maxToolCalls ?? config4?.maxToolCalls ?? DEFAULT_MAX_TOOL_CALLS,
|
|
78504
|
-
|
|
78505
|
-
repetitionThresholdPercent: config4?.circuitBreaker?.repetitionThresholdPercent ?? DEFAULT_CIRCUIT_BREAKER_REPETITION_THRESHOLD_PERCENT
|
|
78683
|
+
consecutiveThreshold: config4?.circuitBreaker?.consecutiveThreshold ?? DEFAULT_CIRCUIT_BREAKER_CONSECUTIVE_THRESHOLD
|
|
78506
78684
|
};
|
|
78507
78685
|
}
|
|
78508
|
-
function recordToolCall(window, toolName, settings) {
|
|
78509
|
-
const
|
|
78510
|
-
|
|
78686
|
+
function recordToolCall(window, toolName, settings, toolInput) {
|
|
78687
|
+
const signature = createToolCallSignature(toolName, toolInput);
|
|
78688
|
+
if (window && window.lastSignature === signature) {
|
|
78689
|
+
return {
|
|
78690
|
+
lastSignature: signature,
|
|
78691
|
+
consecutiveCount: window.consecutiveCount + 1,
|
|
78692
|
+
threshold: settings.consecutiveThreshold
|
|
78693
|
+
};
|
|
78694
|
+
}
|
|
78511
78695
|
return {
|
|
78512
|
-
|
|
78513
|
-
|
|
78514
|
-
|
|
78696
|
+
lastSignature: signature,
|
|
78697
|
+
consecutiveCount: 1,
|
|
78698
|
+
threshold: settings.consecutiveThreshold
|
|
78515
78699
|
};
|
|
78516
78700
|
}
|
|
78517
|
-
function
|
|
78518
|
-
if (
|
|
78519
|
-
return
|
|
78520
|
-
|
|
78521
|
-
|
|
78522
|
-
|
|
78523
|
-
|
|
78701
|
+
function sortObject2(obj) {
|
|
78702
|
+
if (obj === null || obj === undefined)
|
|
78703
|
+
return obj;
|
|
78704
|
+
if (typeof obj !== "object")
|
|
78705
|
+
return obj;
|
|
78706
|
+
if (Array.isArray(obj))
|
|
78707
|
+
return obj.map(sortObject2);
|
|
78708
|
+
const sorted = {};
|
|
78709
|
+
const keys = Object.keys(obj).sort();
|
|
78710
|
+
for (const key of keys) {
|
|
78711
|
+
sorted[key] = sortObject2(obj[key]);
|
|
78524
78712
|
}
|
|
78525
|
-
|
|
78526
|
-
|
|
78527
|
-
|
|
78528
|
-
|
|
78529
|
-
|
|
78530
|
-
repeatedCount = count;
|
|
78531
|
-
}
|
|
78713
|
+
return sorted;
|
|
78714
|
+
}
|
|
78715
|
+
function createToolCallSignature(toolName, toolInput) {
|
|
78716
|
+
if (toolInput === undefined || toolInput === null) {
|
|
78717
|
+
return toolName;
|
|
78532
78718
|
}
|
|
78533
|
-
|
|
78534
|
-
|
|
78535
|
-
if (sampleSize < minimumSampleSize) {
|
|
78536
|
-
return { triggered: false };
|
|
78719
|
+
if (Object.keys(toolInput).length === 0) {
|
|
78720
|
+
return toolName;
|
|
78537
78721
|
}
|
|
78538
|
-
|
|
78539
|
-
|
|
78722
|
+
return `${toolName}::${JSON.stringify(sortObject2(toolInput))}`;
|
|
78723
|
+
}
|
|
78724
|
+
function detectRepetitiveToolUse(window) {
|
|
78725
|
+
if (!window || window.consecutiveCount < window.threshold) {
|
|
78540
78726
|
return { triggered: false };
|
|
78541
78727
|
}
|
|
78542
78728
|
return {
|
|
78543
78729
|
triggered: true,
|
|
78544
|
-
toolName:
|
|
78545
|
-
repeatedCount
|
|
78546
|
-
sampleSize,
|
|
78547
|
-
thresholdPercent: window.thresholdPercent
|
|
78730
|
+
toolName: window.lastSignature.split("::")[0],
|
|
78731
|
+
repeatedCount: window.consecutiveCount
|
|
78548
78732
|
};
|
|
78549
78733
|
}
|
|
78550
78734
|
|
|
@@ -78643,6 +78827,7 @@ class BackgroundManager {
|
|
|
78643
78827
|
preStartDescendantReservations;
|
|
78644
78828
|
enableParentSessionNotifications;
|
|
78645
78829
|
taskHistory = new TaskHistory;
|
|
78830
|
+
cachedCircuitBreakerSettings;
|
|
78646
78831
|
constructor(ctx, config4, options) {
|
|
78647
78832
|
this.tasks = new Map;
|
|
78648
78833
|
this.notifications = new Map;
|
|
@@ -79235,35 +79420,36 @@ class BackgroundManager {
|
|
|
79235
79420
|
}
|
|
79236
79421
|
task.progress.lastUpdate = new Date;
|
|
79237
79422
|
if (partInfo?.type === "tool" || partInfo?.tool) {
|
|
79238
|
-
const countedToolPartIDs = task.progress.countedToolPartIDs ??
|
|
79239
|
-
const shouldCountToolCall = !partInfo.id || partInfo.state?.status !== "running" || !countedToolPartIDs.
|
|
79423
|
+
const countedToolPartIDs = task.progress.countedToolPartIDs ?? new Set;
|
|
79424
|
+
const shouldCountToolCall = !partInfo.id || partInfo.state?.status !== "running" || !countedToolPartIDs.has(partInfo.id);
|
|
79240
79425
|
if (!shouldCountToolCall) {
|
|
79241
79426
|
return;
|
|
79242
79427
|
}
|
|
79243
79428
|
if (partInfo.id && partInfo.state?.status === "running") {
|
|
79244
|
-
|
|
79429
|
+
countedToolPartIDs.add(partInfo.id);
|
|
79430
|
+
task.progress.countedToolPartIDs = countedToolPartIDs;
|
|
79245
79431
|
}
|
|
79246
79432
|
task.progress.toolCalls += 1;
|
|
79247
79433
|
task.progress.lastTool = partInfo.tool;
|
|
79248
|
-
const circuitBreaker = resolveCircuitBreakerSettings(this.config);
|
|
79434
|
+
const circuitBreaker = this.cachedCircuitBreakerSettings ?? (this.cachedCircuitBreakerSettings = resolveCircuitBreakerSettings(this.config));
|
|
79249
79435
|
if (partInfo.tool) {
|
|
79250
|
-
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker);
|
|
79251
|
-
|
|
79252
|
-
|
|
79253
|
-
|
|
79254
|
-
|
|
79255
|
-
|
|
79256
|
-
|
|
79257
|
-
|
|
79258
|
-
|
|
79259
|
-
|
|
79260
|
-
|
|
79261
|
-
|
|
79262
|
-
|
|
79263
|
-
|
|
79264
|
-
|
|
79265
|
-
|
|
79266
|
-
|
|
79436
|
+
task.progress.toolCallWindow = recordToolCall(task.progress.toolCallWindow, partInfo.tool, circuitBreaker, partInfo.state?.input);
|
|
79437
|
+
if (circuitBreaker.enabled) {
|
|
79438
|
+
const loopDetection = detectRepetitiveToolUse(task.progress.toolCallWindow);
|
|
79439
|
+
if (loopDetection.triggered) {
|
|
79440
|
+
log("[background-agent] Circuit breaker: consecutive tool usage detected", {
|
|
79441
|
+
taskId: task.id,
|
|
79442
|
+
agent: task.agent,
|
|
79443
|
+
sessionID,
|
|
79444
|
+
toolName: loopDetection.toolName,
|
|
79445
|
+
repeatedCount: loopDetection.repeatedCount
|
|
79446
|
+
});
|
|
79447
|
+
this.cancelTask(task.id, {
|
|
79448
|
+
source: "circuit-breaker",
|
|
79449
|
+
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.`
|
|
79450
|
+
});
|
|
79451
|
+
return;
|
|
79452
|
+
}
|
|
79267
79453
|
}
|
|
79268
79454
|
}
|
|
79269
79455
|
const maxToolCalls = circuitBreaker.maxToolCalls;
|
|
@@ -79910,7 +80096,7 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
79910
80096
|
continue;
|
|
79911
80097
|
}
|
|
79912
80098
|
}
|
|
79913
|
-
if (sessionStatus && sessionStatus.type
|
|
80099
|
+
if (sessionStatus && isActiveSessionStatus(sessionStatus.type)) {
|
|
79914
80100
|
log("[background-agent] Session still running, relying on event-based progress:", {
|
|
79915
80101
|
taskId: task.id,
|
|
79916
80102
|
sessionID,
|
|
@@ -79919,6 +80105,17 @@ Use \`background_output(task_id="${task.id}")\` to retrieve this result when rea
|
|
|
79919
80105
|
});
|
|
79920
80106
|
continue;
|
|
79921
80107
|
}
|
|
80108
|
+
if (sessionStatus && isTerminalSessionStatus(sessionStatus.type)) {
|
|
80109
|
+
await this.tryCompleteTask(task, `polling (terminal session status: ${sessionStatus.type})`);
|
|
80110
|
+
continue;
|
|
80111
|
+
}
|
|
80112
|
+
if (sessionStatus && sessionStatus.type !== "idle") {
|
|
80113
|
+
log("[background-agent] Unknown session status, treating as potentially idle:", {
|
|
80114
|
+
taskId: task.id,
|
|
80115
|
+
sessionID,
|
|
80116
|
+
sessionStatus: sessionStatus.type
|
|
80117
|
+
});
|
|
80118
|
+
}
|
|
79922
80119
|
const completionSource = sessionStatus?.type === "idle" ? "polling (idle status)" : "polling (session gone from status)";
|
|
79923
80120
|
const hasValidOutput = await this.validateSessionHasOutput(sessionID);
|
|
79924
80121
|
if (!hasValidOutput) {
|
|
@@ -82575,8 +82772,8 @@ async function generateVerifier(length) {
|
|
|
82575
82772
|
return await random(length);
|
|
82576
82773
|
}
|
|
82577
82774
|
async function generateChallenge(code_verifier) {
|
|
82578
|
-
const
|
|
82579
|
-
return btoa(String.fromCharCode(...new Uint8Array(
|
|
82775
|
+
const buffer2 = await (await crypto3).subtle.digest("SHA-256", new TextEncoder().encode(code_verifier));
|
|
82776
|
+
return btoa(String.fromCharCode(...new Uint8Array(buffer2))).replace(/\//g, "_").replace(/\+/g, "-").replace(/=/g, "");
|
|
82580
82777
|
}
|
|
82581
82778
|
async function pkceChallenge(length) {
|
|
82582
82779
|
if (!length)
|
|
@@ -97677,10 +97874,7 @@ function createPluginInterface(args) {
|
|
|
97677
97874
|
const { ctx, pluginConfig, firstMessageVariantGate, managers, hooks: hooks2, tools } = args;
|
|
97678
97875
|
return {
|
|
97679
97876
|
tool: tools,
|
|
97680
|
-
"chat.params":
|
|
97681
|
-
const handler = createChatParamsHandler({ anthropicEffort: hooks2.anthropicEffort });
|
|
97682
|
-
await handler(input, output);
|
|
97683
|
-
},
|
|
97877
|
+
"chat.params": createChatParamsHandler({ anthropicEffort: hooks2.anthropicEffort }),
|
|
97684
97878
|
"chat.headers": createChatHeadersHandler({ ctx }),
|
|
97685
97879
|
"chat.message": createChatMessageHandler3({
|
|
97686
97880
|
ctx,
|
|
@@ -97707,7 +97901,10 @@ function createPluginInterface(args) {
|
|
|
97707
97901
|
"tool.execute.after": createToolExecuteAfterHandler3({
|
|
97708
97902
|
ctx,
|
|
97709
97903
|
hooks: hooks2
|
|
97710
|
-
})
|
|
97904
|
+
}),
|
|
97905
|
+
"tool.definition": async (input, output) => {
|
|
97906
|
+
await hooks2.todoDescriptionOverride?.["tool.definition"]?.(input, output);
|
|
97907
|
+
}
|
|
97711
97908
|
};
|
|
97712
97909
|
}
|
|
97713
97910
|
|