@visorcraft/idlehands 1.1.0 → 1.1.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/agent.js +46 -39
- package/dist/agent.js.map +1 -1
- package/dist/bot/discord.js +140 -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 +123 -67
- package/dist/bot/telegram.js.map +1 -1
- package/dist/tui/controller.js +74 -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);
|
|
@@ -1452,6 +1487,7 @@ export async function createSession(opts) {
|
|
|
1452
1487
|
messages = compacted;
|
|
1453
1488
|
if (dropped.length) {
|
|
1454
1489
|
messages.push({ role: 'system', content: `[compacted: ${dropped.length} messages archived to Vault - vault_search to recall]` });
|
|
1490
|
+
await injectVaultContext().catch(() => { });
|
|
1455
1491
|
}
|
|
1456
1492
|
}
|
|
1457
1493
|
return {
|
|
@@ -1810,7 +1846,6 @@ export async function createSession(opts) {
|
|
|
1810
1846
|
// that happen back-to-back with no other tool calls in between.
|
|
1811
1847
|
let lastTurnSigs = new Set();
|
|
1812
1848
|
const consecutiveCounts = new Map();
|
|
1813
|
-
let lastPassiveVaultQuery = '';
|
|
1814
1849
|
let malformedCount = 0;
|
|
1815
1850
|
let noProgressTurns = 0;
|
|
1816
1851
|
const NO_PROGRESS_TURN_CAP = 3;
|
|
@@ -1823,34 +1858,6 @@ export async function createSession(opts) {
|
|
|
1823
1858
|
let lastSuccessfulTestRun = null;
|
|
1824
1859
|
// One-time nudge to prevent post-success churn after green test runs.
|
|
1825
1860
|
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
1861
|
const archiveToolOutputForVault = async (msg) => {
|
|
1855
1862
|
if (!lens || !vault || msg.role !== 'tool' || typeof msg.content !== 'string')
|
|
1856
1863
|
return msg;
|
|
@@ -1952,8 +1959,9 @@ export async function createSession(opts) {
|
|
|
1952
1959
|
}
|
|
1953
1960
|
}
|
|
1954
1961
|
messages = compacted;
|
|
1955
|
-
if (
|
|
1956
|
-
|
|
1962
|
+
if (dropped.length) {
|
|
1963
|
+
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.]` });
|
|
1964
|
+
await injectVaultContext().catch(() => { });
|
|
1957
1965
|
}
|
|
1958
1966
|
const ac = makeAbortController();
|
|
1959
1967
|
inFlight = ac;
|
|
@@ -2240,9 +2248,13 @@ export async function createSession(opts) {
|
|
|
2240
2248
|
// Update to "now" for next turn.
|
|
2241
2249
|
mutationVersionBySig.set(sig, mutationVersion);
|
|
2242
2250
|
if (!hasMutatedSince) {
|
|
2243
|
-
|
|
2251
|
+
const count = sigCounts.get(sig) ?? 0;
|
|
2244
2252
|
const loopThreshold = harness.quirks.loopsOnToolError ? 3 : 6;
|
|
2245
|
-
|
|
2253
|
+
// At 3x, inject vault context so the model gets the data it needs
|
|
2254
|
+
if (count >= 3 && count < loopThreshold) {
|
|
2255
|
+
await injectVaultContext().catch(() => { });
|
|
2256
|
+
}
|
|
2257
|
+
if (count >= loopThreshold) {
|
|
2246
2258
|
const args = sig.slice(toolName.length + 1);
|
|
2247
2259
|
const argsPreview = args.length > 220 ? args.slice(0, 220) + '…' : args;
|
|
2248
2260
|
throw new Error(`tool ${toolName}: identical call repeated ${loopThreshold}x across turns; breaking loop. ` +
|
|
@@ -2264,12 +2276,7 @@ export async function createSession(opts) {
|
|
|
2264
2276
|
}
|
|
2265
2277
|
const consec = consecutiveCounts.get(sig) ?? 1;
|
|
2266
2278
|
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
|
-
});
|
|
2279
|
+
await injectVaultContext().catch(() => { });
|
|
2273
2280
|
}
|
|
2274
2281
|
// Hard-break: after 6 consecutive identical reads, stop the session
|
|
2275
2282
|
if (consec >= 6) {
|