@visorcraft/idlehands 1.1.0 → 1.1.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/agent.js +52 -43
- package/dist/agent.js.map +1 -1
- package/dist/bot/discord.js +145 -74
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/session-manager.js +3 -0
- package/dist/bot/session-manager.js.map +1 -1
- package/dist/bot/telegram.js +128 -67
- package/dist/bot/telegram.js.map +1 -1
- package/dist/history.js +5 -3
- package/dist/history.js.map +1 -1
- package/dist/tui/controller.js +79 -11
- package/dist/tui/controller.js.map +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -1408,6 +1408,41 @@ export async function createSession(opts) {
|
|
|
1408
1408
|
const clearPlan = () => {
|
|
1409
1409
|
planSteps = [];
|
|
1410
1410
|
};
|
|
1411
|
+
// Session-level vault context injection: search vault for entries relevant to
|
|
1412
|
+
// the last user message and inject them into the conversation. Used after any
|
|
1413
|
+
// compaction to restore context the model lost when messages were dropped.
|
|
1414
|
+
let lastVaultInjectionQuery = '';
|
|
1415
|
+
const injectVaultContext = async () => {
|
|
1416
|
+
if (!vault)
|
|
1417
|
+
return;
|
|
1418
|
+
let lastUser = null;
|
|
1419
|
+
for (let j = messages.length - 1; j >= 0; j--) {
|
|
1420
|
+
if (messages[j].role === 'user') {
|
|
1421
|
+
lastUser = messages[j];
|
|
1422
|
+
break;
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
const userText = userContentToText((lastUser?.content ?? '')).trim();
|
|
1426
|
+
if (!userText)
|
|
1427
|
+
return;
|
|
1428
|
+
const query = userText.slice(0, 200);
|
|
1429
|
+
if (query === lastVaultInjectionQuery)
|
|
1430
|
+
return;
|
|
1431
|
+
const hits = await vault.search(query, 4);
|
|
1432
|
+
if (!hits.length)
|
|
1433
|
+
return;
|
|
1434
|
+
const lines = hits.map((r) => `${r.updatedAt} ${r.kind} ${r.key ?? r.tool ?? r.id} ${String(r.value ?? r.snippet ?? '').replace(/\s+/g, ' ').slice(0, 180)}`);
|
|
1435
|
+
if (!lines.length)
|
|
1436
|
+
return;
|
|
1437
|
+
lastVaultInjectionQuery = query;
|
|
1438
|
+
const vaultContextHeader = vaultMode === 'passive'
|
|
1439
|
+
? '[Trifecta Vault (passive)]'
|
|
1440
|
+
: '[Vault context after compaction]';
|
|
1441
|
+
messages.push({
|
|
1442
|
+
role: 'user',
|
|
1443
|
+
content: `${vaultContextHeader} Relevant entries for "${query}":\n${lines.join('\n')}`
|
|
1444
|
+
});
|
|
1445
|
+
};
|
|
1411
1446
|
const compactHistory = async (opts) => {
|
|
1412
1447
|
const beforeMessages = messages.length;
|
|
1413
1448
|
const beforeTokens = estimateTokensFromMessages(messages);
|
|
@@ -1422,9 +1457,10 @@ export async function createSession(opts) {
|
|
|
1422
1457
|
messages,
|
|
1423
1458
|
contextWindow,
|
|
1424
1459
|
maxTokens,
|
|
1425
|
-
minTailMessages: 12,
|
|
1426
|
-
compactAt: cfg.compact_at ?? 0.8,
|
|
1460
|
+
minTailMessages: opts?.force ? 2 : 12,
|
|
1461
|
+
compactAt: opts?.force ? 0.5 : (cfg.compact_at ?? 0.8),
|
|
1427
1462
|
toolSchemaTokens: estimateToolSchemaTokens(getToolsSchema()),
|
|
1463
|
+
force: opts?.force,
|
|
1428
1464
|
});
|
|
1429
1465
|
}
|
|
1430
1466
|
const compactedByRefs = new Set(compacted);
|
|
@@ -1452,6 +1488,7 @@ export async function createSession(opts) {
|
|
|
1452
1488
|
messages = compacted;
|
|
1453
1489
|
if (dropped.length) {
|
|
1454
1490
|
messages.push({ role: 'system', content: `[compacted: ${dropped.length} messages archived to Vault - vault_search to recall]` });
|
|
1491
|
+
await injectVaultContext().catch(() => { });
|
|
1455
1492
|
}
|
|
1456
1493
|
}
|
|
1457
1494
|
return {
|
|
@@ -1810,7 +1847,6 @@ export async function createSession(opts) {
|
|
|
1810
1847
|
// that happen back-to-back with no other tool calls in between.
|
|
1811
1848
|
let lastTurnSigs = new Set();
|
|
1812
1849
|
const consecutiveCounts = new Map();
|
|
1813
|
-
let lastPassiveVaultQuery = '';
|
|
1814
1850
|
let malformedCount = 0;
|
|
1815
1851
|
let noProgressTurns = 0;
|
|
1816
1852
|
const NO_PROGRESS_TURN_CAP = 3;
|
|
@@ -1823,34 +1859,6 @@ export async function createSession(opts) {
|
|
|
1823
1859
|
let lastSuccessfulTestRun = null;
|
|
1824
1860
|
// One-time nudge to prevent post-success churn after green test runs.
|
|
1825
1861
|
let finalizeAfterTestsNudgeUsed = false;
|
|
1826
|
-
const maybeInjectVaultContext = async () => {
|
|
1827
|
-
if (!vault || vaultMode !== 'passive')
|
|
1828
|
-
return;
|
|
1829
|
-
let lastUser = null;
|
|
1830
|
-
for (let j = messages.length - 1; j >= 0; j--) {
|
|
1831
|
-
if (messages[j].role === 'user') {
|
|
1832
|
-
lastUser = messages[j];
|
|
1833
|
-
break;
|
|
1834
|
-
}
|
|
1835
|
-
}
|
|
1836
|
-
const userText = userContentToText((lastUser?.content ?? '')).trim();
|
|
1837
|
-
if (!userText)
|
|
1838
|
-
return;
|
|
1839
|
-
const query = userText.slice(0, 200);
|
|
1840
|
-
if (query === lastPassiveVaultQuery)
|
|
1841
|
-
return;
|
|
1842
|
-
const hits = await vault.search(query, 4);
|
|
1843
|
-
if (!hits.length)
|
|
1844
|
-
return;
|
|
1845
|
-
const lines = hits.map((r) => `${r.updatedAt} ${r.kind} ${r.key ?? r.tool ?? r.id} ${String(r.value ?? r.snippet ?? '').replace(/\s+/g, ' ').slice(0, 180)}`);
|
|
1846
|
-
if (!lines.length)
|
|
1847
|
-
return;
|
|
1848
|
-
lastPassiveVaultQuery = query;
|
|
1849
|
-
messages.push({
|
|
1850
|
-
role: 'user',
|
|
1851
|
-
content: `[Trifecta Vault (passive)] Relevant entries for "${query}":\n${lines.join('\n')}`
|
|
1852
|
-
});
|
|
1853
|
-
};
|
|
1854
1862
|
const archiveToolOutputForVault = async (msg) => {
|
|
1855
1863
|
if (!lens || !vault || msg.role !== 'tool' || typeof msg.content !== 'string')
|
|
1856
1864
|
return msg;
|
|
@@ -1952,8 +1960,9 @@ export async function createSession(opts) {
|
|
|
1952
1960
|
}
|
|
1953
1961
|
}
|
|
1954
1962
|
messages = compacted;
|
|
1955
|
-
if (
|
|
1956
|
-
|
|
1963
|
+
if (dropped.length) {
|
|
1964
|
+
messages.push({ role: 'system', content: `[auto-compacted: ${dropped.length} old messages dropped to stay within context budget. Do NOT re-read files or re-run commands you have already seen — use vault_search to recall prior results if needed.]` });
|
|
1965
|
+
await injectVaultContext().catch(() => { });
|
|
1957
1966
|
}
|
|
1958
1967
|
const ac = makeAbortController();
|
|
1959
1968
|
inFlight = ac;
|
|
@@ -1961,10 +1970,11 @@ export async function createSession(opts) {
|
|
|
1961
1970
|
const callerSignal = hookObj.signal;
|
|
1962
1971
|
const onCallerAbort = () => ac.abort();
|
|
1963
1972
|
callerSignal?.addEventListener('abort', onCallerAbort, { once: true });
|
|
1964
|
-
// Per-request timeout: the lesser of
|
|
1973
|
+
// Per-request timeout: the lesser of response_timeout (default 300s) or the remaining session wall time.
|
|
1965
1974
|
// This prevents a single slow request from consuming the entire session budget.
|
|
1975
|
+
const perReqCap = cfg.response_timeout && cfg.response_timeout > 0 ? cfg.response_timeout : 300;
|
|
1966
1976
|
const wallRemaining = Math.max(0, cfg.timeout - (Date.now() - wallStart) / 1000);
|
|
1967
|
-
const reqTimeout = Math.min(
|
|
1977
|
+
const reqTimeout = Math.min(perReqCap, Math.max(10, wallRemaining));
|
|
1968
1978
|
const timer = setTimeout(() => ac.abort(), reqTimeout * 1000);
|
|
1969
1979
|
reqCounter++;
|
|
1970
1980
|
const turnStartMs = Date.now();
|
|
@@ -2240,9 +2250,13 @@ export async function createSession(opts) {
|
|
|
2240
2250
|
// Update to "now" for next turn.
|
|
2241
2251
|
mutationVersionBySig.set(sig, mutationVersion);
|
|
2242
2252
|
if (!hasMutatedSince) {
|
|
2243
|
-
|
|
2253
|
+
const count = sigCounts.get(sig) ?? 0;
|
|
2244
2254
|
const loopThreshold = harness.quirks.loopsOnToolError ? 3 : 6;
|
|
2245
|
-
|
|
2255
|
+
// At 3x, inject vault context so the model gets the data it needs
|
|
2256
|
+
if (count >= 3 && count < loopThreshold) {
|
|
2257
|
+
await injectVaultContext().catch(() => { });
|
|
2258
|
+
}
|
|
2259
|
+
if (count >= loopThreshold) {
|
|
2246
2260
|
const args = sig.slice(toolName.length + 1);
|
|
2247
2261
|
const argsPreview = args.length > 220 ? args.slice(0, 220) + '…' : args;
|
|
2248
2262
|
throw new Error(`tool ${toolName}: identical call repeated ${loopThreshold}x across turns; breaking loop. ` +
|
|
@@ -2264,12 +2278,7 @@ export async function createSession(opts) {
|
|
|
2264
2278
|
}
|
|
2265
2279
|
const consec = consecutiveCounts.get(sig) ?? 1;
|
|
2266
2280
|
if (consec >= 3) {
|
|
2267
|
-
|
|
2268
|
-
const argsPreview = args.length > 220 ? args.slice(0, 220) + '…' : args;
|
|
2269
|
-
messages.push({
|
|
2270
|
-
role: 'user',
|
|
2271
|
-
content: `[System] STOP READING: You have read the same resource ${consec} consecutive times (${toolName} ${argsPreview}). The content has NOT changed. You already have this data. Proceed immediately with your next action (write_file, edit_file, exec, etc.) — do NOT read this resource again.`,
|
|
2272
|
-
});
|
|
2281
|
+
await injectVaultContext().catch(() => { });
|
|
2273
2282
|
}
|
|
2274
2283
|
// Hard-break: after 6 consecutive identical reads, stop the session
|
|
2275
2284
|
if (consec >= 6) {
|