@visorcraft/idlehands 2.2.12 → 2.2.14
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/agent.js +111 -8
- package/dist/agent.js.map +1 -1
- package/dist/anton/verifier.js +65 -7
- package/dist/anton/verifier.js.map +1 -1
- package/dist/bot/metrics-command.js +2 -0
- package/dist/bot/metrics-command.js.map +1 -1
- package/dist/client.js +23 -3
- package/dist/client.js.map +1 -1
- package/dist/tools/patch-apply.js +54 -8
- package/dist/tools/patch-apply.js.map +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -312,6 +312,71 @@ export async function createSession(opts) {
|
|
|
312
312
|
if (recentToolUsage.length > 60)
|
|
313
313
|
recentToolUsage.shift();
|
|
314
314
|
};
|
|
315
|
+
const extractPartialToolArgsPreview = (toolName, rawArgs) => {
|
|
316
|
+
const out = {};
|
|
317
|
+
const text = String(rawArgs ?? '');
|
|
318
|
+
if (!text.trim())
|
|
319
|
+
return out;
|
|
320
|
+
const pickString = (key) => {
|
|
321
|
+
const m = text.match(new RegExp(`"${key}"\\s*:\\s*"([^\\n\"]*)`));
|
|
322
|
+
return m?.[1];
|
|
323
|
+
};
|
|
324
|
+
const pickNumber = (key) => {
|
|
325
|
+
const m = text.match(new RegExp(`"${key}"\\s*:\\s*(-?\\d+)`));
|
|
326
|
+
if (!m)
|
|
327
|
+
return undefined;
|
|
328
|
+
const n = Number.parseInt(m[1], 10);
|
|
329
|
+
return Number.isFinite(n) ? n : undefined;
|
|
330
|
+
};
|
|
331
|
+
const pathLikeTools = new Set([
|
|
332
|
+
'read_file',
|
|
333
|
+
'write_file',
|
|
334
|
+
'edit_range',
|
|
335
|
+
'edit_file',
|
|
336
|
+
'insert_file',
|
|
337
|
+
'list_dir',
|
|
338
|
+
'lsp_diagnostics',
|
|
339
|
+
'lsp_symbols',
|
|
340
|
+
'lsp_hover',
|
|
341
|
+
'lsp_definition',
|
|
342
|
+
'lsp_references',
|
|
343
|
+
]);
|
|
344
|
+
if (pathLikeTools.has(toolName)) {
|
|
345
|
+
const path = pickString('path');
|
|
346
|
+
if (path)
|
|
347
|
+
out.path = path;
|
|
348
|
+
}
|
|
349
|
+
if (toolName === 'search_files') {
|
|
350
|
+
const pattern = pickString('pattern');
|
|
351
|
+
const path = pickString('path');
|
|
352
|
+
if (pattern)
|
|
353
|
+
out.pattern = pattern;
|
|
354
|
+
if (path)
|
|
355
|
+
out.path = path;
|
|
356
|
+
}
|
|
357
|
+
if (toolName === 'exec') {
|
|
358
|
+
const command = pickString('command');
|
|
359
|
+
const cwd = pickString('cwd');
|
|
360
|
+
if (command)
|
|
361
|
+
out.command = command;
|
|
362
|
+
if (cwd)
|
|
363
|
+
out.cwd = cwd;
|
|
364
|
+
}
|
|
365
|
+
if (toolName === 'vault_search') {
|
|
366
|
+
const query = pickString('query');
|
|
367
|
+
if (query)
|
|
368
|
+
out.query = query;
|
|
369
|
+
}
|
|
370
|
+
if (toolName === 'edit_range') {
|
|
371
|
+
const start = pickNumber('start_line');
|
|
372
|
+
const end = pickNumber('end_line');
|
|
373
|
+
if (start != null)
|
|
374
|
+
out.start_line = start;
|
|
375
|
+
if (end != null)
|
|
376
|
+
out.end_line = end;
|
|
377
|
+
}
|
|
378
|
+
return out;
|
|
379
|
+
};
|
|
315
380
|
const vault = vaultEnabled
|
|
316
381
|
? (opts.runtime?.vault ??
|
|
317
382
|
new VaultStore({
|
|
@@ -1994,6 +2059,7 @@ export async function createSession(opts) {
|
|
|
1994
2059
|
let forceToollessRecoveryTurn = false;
|
|
1995
2060
|
let toollessRecoveryUsed = false;
|
|
1996
2061
|
const streamedToolCallPreviews = new Set();
|
|
2062
|
+
const streamedToolCallPreviewScores = new Map();
|
|
1997
2063
|
// ── Security: credential leak detection + prompt injection guard ──
|
|
1998
2064
|
const leakDetector = new LeakDetector();
|
|
1999
2065
|
const promptGuard = new PromptGuard('warn');
|
|
@@ -2279,6 +2345,7 @@ export async function createSession(opts) {
|
|
|
2279
2345
|
hookObj.onFirstDelta?.();
|
|
2280
2346
|
};
|
|
2281
2347
|
let resp;
|
|
2348
|
+
let streamFallbackDiag;
|
|
2282
2349
|
try {
|
|
2283
2350
|
try {
|
|
2284
2351
|
// turns is 1-indexed (incremented at loop top), so first iteration = 1.
|
|
@@ -2314,6 +2381,24 @@ export async function createSession(opts) {
|
|
|
2314
2381
|
if (cfg.verbose) {
|
|
2315
2382
|
console.error(`[turn-debug] prompt_bytes=${promptBytesEstimate} tools=${toolsForTurn.length} tool_schema_bytes=${toolSchemaBytesEstimate} tool_schema_tokens~=${toolSchemaTokenEstimate}`);
|
|
2316
2383
|
}
|
|
2384
|
+
const noteStreamFallback = (providerName, response) => {
|
|
2385
|
+
const fallback = response?.meta?.stream_fallback;
|
|
2386
|
+
if (!fallback || typeof fallback !== 'object')
|
|
2387
|
+
return;
|
|
2388
|
+
const reason = String(fallback.reason ?? 'unknown');
|
|
2389
|
+
const attempt = Number(fallback.attempt ?? NaN);
|
|
2390
|
+
const status = Number(fallback.status ?? NaN);
|
|
2391
|
+
const detail = [
|
|
2392
|
+
Number.isFinite(attempt) ? `attempt=${attempt}` : null,
|
|
2393
|
+
Number.isFinite(status) ? `status=${status}` : null,
|
|
2394
|
+
]
|
|
2395
|
+
.filter(Boolean)
|
|
2396
|
+
.join(' ');
|
|
2397
|
+
streamFallbackDiag = `${providerName}:${reason}${detail ? ` (${detail})` : ''}`;
|
|
2398
|
+
if (cfg.verbose) {
|
|
2399
|
+
console.warn(`[routing] stream fallback provider=${providerName} reason=${reason}${detail ? ` ${detail}` : ''}`);
|
|
2400
|
+
}
|
|
2401
|
+
};
|
|
2317
2402
|
// ── Response cache: check for cached response ──────────────
|
|
2318
2403
|
// Only cache tool-less turns (final answers, explanations) since
|
|
2319
2404
|
// tool-calling turns have side effects that shouldn't be replayed.
|
|
@@ -2365,9 +2450,6 @@ export async function createSession(opts) {
|
|
|
2365
2450
|
? delta.id
|
|
2366
2451
|
: `stream_call_${delta.index}`;
|
|
2367
2452
|
const previewKey = `${turns}:${id}:${name}`;
|
|
2368
|
-
if (streamedToolCallPreviews.has(previewKey))
|
|
2369
|
-
return;
|
|
2370
|
-
streamedToolCallPreviews.add(previewKey);
|
|
2371
2453
|
let parsedArgs = {};
|
|
2372
2454
|
const rawArgs = typeof delta.argumentsSoFar === 'string' ? delta.argumentsSoFar.trim() : '';
|
|
2373
2455
|
if (rawArgs) {
|
|
@@ -2380,7 +2462,17 @@ export async function createSession(opts) {
|
|
|
2380
2462
|
catch {
|
|
2381
2463
|
// partial JSON chunks are expected during streaming
|
|
2382
2464
|
}
|
|
2465
|
+
if (!Object.keys(parsedArgs).length) {
|
|
2466
|
+
parsedArgs = extractPartialToolArgsPreview(name, rawArgs);
|
|
2467
|
+
}
|
|
2383
2468
|
}
|
|
2469
|
+
const score = Object.keys(parsedArgs).length + (rawArgs ? 1 : 0);
|
|
2470
|
+
const prevScore = streamedToolCallPreviewScores.get(previewKey) ?? 0;
|
|
2471
|
+
const shouldEmit = !streamedToolCallPreviews.has(previewKey) || score > prevScore;
|
|
2472
|
+
if (!shouldEmit)
|
|
2473
|
+
return;
|
|
2474
|
+
streamedToolCallPreviews.add(previewKey);
|
|
2475
|
+
streamedToolCallPreviewScores.set(previewKey, Math.max(prevScore, score));
|
|
2384
2476
|
void emitToolCall(id, name, parsedArgs, 'planned');
|
|
2385
2477
|
},
|
|
2386
2478
|
};
|
|
@@ -2410,6 +2502,7 @@ export async function createSession(opts) {
|
|
|
2410
2502
|
}
|
|
2411
2503
|
},
|
|
2412
2504
|
});
|
|
2505
|
+
noteStreamFallback('runtime-router', resp);
|
|
2413
2506
|
}
|
|
2414
2507
|
else {
|
|
2415
2508
|
const isLikelyAuthError = (errMsg) => {
|
|
@@ -2457,6 +2550,7 @@ export async function createSession(opts) {
|
|
|
2457
2550
|
}
|
|
2458
2551
|
},
|
|
2459
2552
|
});
|
|
2553
|
+
noteStreamFallback(target.name ?? 'default', resp);
|
|
2460
2554
|
break;
|
|
2461
2555
|
}
|
|
2462
2556
|
catch (providerErr) {
|
|
@@ -2476,6 +2570,9 @@ export async function createSession(opts) {
|
|
|
2476
2570
|
}
|
|
2477
2571
|
}
|
|
2478
2572
|
} // end if (!resp) — cache miss path
|
|
2573
|
+
if (streamFallbackDiag && lastTurnDebug) {
|
|
2574
|
+
lastTurnDebug.streamFallback = streamFallbackDiag;
|
|
2575
|
+
}
|
|
2479
2576
|
// Successful response resets overflow recovery budget.
|
|
2480
2577
|
overflowCompactionAttempts = 0;
|
|
2481
2578
|
// ── Response cache: store cacheable responses ─────────────
|
|
@@ -3133,8 +3230,10 @@ export async function createSession(opts) {
|
|
|
3133
3230
|
if (name === 'read_file' || name === 'read_files') {
|
|
3134
3231
|
const filePath = typeof args.path === 'string' ? args.path : '';
|
|
3135
3232
|
const searchTerm = typeof args.search === 'string' ? args.search : '';
|
|
3136
|
-
// Fix 1: Hard cumulative budget — refuse reads
|
|
3137
|
-
|
|
3233
|
+
// Fix 1: Hard cumulative budget — refuse reads once hard cap is reached.
|
|
3234
|
+
// Count only actual executed read-only calls (not cache replays), so this check
|
|
3235
|
+
// blocks the next call exactly at the configured cap.
|
|
3236
|
+
if (cumulativeReadOnlyCalls >= READ_BUDGET_HARD) {
|
|
3138
3237
|
await emitToolCall(callId, name, args);
|
|
3139
3238
|
await emitToolResult({
|
|
3140
3239
|
id: callId,
|
|
@@ -3539,6 +3638,13 @@ export async function createSession(opts) {
|
|
|
3539
3638
|
toolCallId: callId,
|
|
3540
3639
|
result: content,
|
|
3541
3640
|
});
|
|
3641
|
+
// Count only actual read-only executions toward cumulative read budget.
|
|
3642
|
+
// Cached/replayed read observations should not consume budget.
|
|
3643
|
+
if (isReadOnlyToolDynamic(name) &&
|
|
3644
|
+
!reusedCachedReadTool &&
|
|
3645
|
+
!reusedCachedReadOnlyExec) {
|
|
3646
|
+
cumulativeReadOnlyCalls += 1;
|
|
3647
|
+
}
|
|
3542
3648
|
// ── Per-file mutation spiral detection ──
|
|
3543
3649
|
// Track edits to the same file. If the model keeps editing the same file
|
|
3544
3650
|
// over and over, it's likely in an edit→break→read→edit corruption spiral.
|
|
@@ -3706,9 +3812,6 @@ export async function createSession(opts) {
|
|
|
3706
3812
|
console.warn(`[guardrail] capped ${droppedCount} read-only tool calls (per-turn limit ${READ_ONLY_PER_TURN_CAP})`);
|
|
3707
3813
|
}
|
|
3708
3814
|
}
|
|
3709
|
-
// Fix 1: Hard cumulative read budget — escalating enforcement
|
|
3710
|
-
const readOnlyThisTurn = toolCallsArr.filter((tc) => isReadOnlyToolDynamic(tc.function.name));
|
|
3711
|
-
cumulativeReadOnlyCalls += readOnlyThisTurn.length;
|
|
3712
3815
|
if (harness.toolCalls.parallelCalls) {
|
|
3713
3816
|
// Models that support parallel calls: read-only in parallel, mutations sequential
|
|
3714
3817
|
const readonly = toolCallsArr.filter((tc) => isReadOnlyToolDynamic(tc.function.name));
|