@visorcraft/idlehands 1.1.11 → 1.1.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 +54 -4
- package/dist/agent.js.map +1 -1
- package/dist/bot/commands.js +16 -4
- package/dist/bot/commands.js.map +1 -1
- package/dist/bot/dir-guard.js +93 -0
- package/dist/bot/dir-guard.js.map +1 -0
- package/dist/bot/discord.js +43 -11
- package/dist/bot/discord.js.map +1 -1
- package/dist/bot/session-manager.js +30 -10
- package/dist/bot/session-manager.js.map +1 -1
- package/dist/bot/telegram.js +42 -4
- package/dist/bot/telegram.js.map +1 -1
- package/dist/sys/context.js +4 -1
- package/dist/sys/context.js.map +1 -1
- package/dist/tools.js +70 -8
- package/dist/tools.js.map +1 -1
- package/dist/tui/controller.js +32 -5
- package/dist/tui/controller.js.map +1 -1
- package/package.json +1 -1
package/dist/agent.js
CHANGED
|
@@ -1068,6 +1068,10 @@ export async function createSession(opts) {
|
|
|
1068
1068
|
noConfirm: cfg.no_confirm || cfg.approval_mode === 'yolo',
|
|
1069
1069
|
dryRun: cfg.dry_run,
|
|
1070
1070
|
mode: cfg.mode ?? 'code',
|
|
1071
|
+
allowedWriteRoots: cfg.allowed_write_roots,
|
|
1072
|
+
requireDirPinForMutations: cfg.require_dir_pin_for_mutations,
|
|
1073
|
+
dirPinned: cfg.dir_pinned,
|
|
1074
|
+
repoCandidates: cfg.repo_candidates,
|
|
1071
1075
|
confirm: overrides?.confirmBridge ?? defaultConfirmBridge,
|
|
1072
1076
|
replay,
|
|
1073
1077
|
vault,
|
|
@@ -1213,6 +1217,21 @@ export async function createSession(opts) {
|
|
|
1213
1217
|
if (!opts?.dry) {
|
|
1214
1218
|
if (dropped.length && vault) {
|
|
1215
1219
|
try {
|
|
1220
|
+
// Store the original/current user prompt before compaction so it survives context loss.
|
|
1221
|
+
let userPromptToPreserve = null;
|
|
1222
|
+
for (let i = messages.length - 1; i >= 0; i--) {
|
|
1223
|
+
const m = messages[i];
|
|
1224
|
+
if (m.role === 'user') {
|
|
1225
|
+
const text = userContentToText((m.content ?? '')).trim();
|
|
1226
|
+
if (text && !text.startsWith('[Trifecta Vault') && !text.startsWith('[Vault context') && text.length > 20) {
|
|
1227
|
+
userPromptToPreserve = text;
|
|
1228
|
+
break;
|
|
1229
|
+
}
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
if (userPromptToPreserve) {
|
|
1233
|
+
await vault.upsertNote('current_task', userPromptToPreserve.slice(0, 2000), 'system');
|
|
1234
|
+
}
|
|
1216
1235
|
await vault.archiveToolMessages(dropped, new Map());
|
|
1217
1236
|
await vault.note('compaction_summary', `Dropped ${dropped.length} messages (${freedTokens} tokens).`);
|
|
1218
1237
|
}
|
|
@@ -1855,6 +1874,23 @@ export async function createSession(opts) {
|
|
|
1855
1874
|
const dropped = beforeMsgs.filter((m) => !compactedByRefs.has(m));
|
|
1856
1875
|
if (dropped.length && vault) {
|
|
1857
1876
|
try {
|
|
1877
|
+
// Store the original/current user prompt before compaction so it survives context loss.
|
|
1878
|
+
// Find the last substantive user message that looks like a task/instruction.
|
|
1879
|
+
let userPromptToPreserve = null;
|
|
1880
|
+
for (let i = beforeMsgs.length - 1; i >= 0; i--) {
|
|
1881
|
+
const m = beforeMsgs[i];
|
|
1882
|
+
if (m.role === 'user') {
|
|
1883
|
+
const text = userContentToText((m.content ?? '')).trim();
|
|
1884
|
+
// Skip vault injection messages and short prompts
|
|
1885
|
+
if (text && !text.startsWith('[Trifecta Vault') && !text.startsWith('[Vault context') && text.length > 20) {
|
|
1886
|
+
userPromptToPreserve = text;
|
|
1887
|
+
break;
|
|
1888
|
+
}
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
if (userPromptToPreserve) {
|
|
1892
|
+
await vault.upsertNote('current_task', userPromptToPreserve.slice(0, 2000), 'system');
|
|
1893
|
+
}
|
|
1858
1894
|
const toArchive = lens
|
|
1859
1895
|
? await Promise.all(dropped.map((m) => archiveToolOutputForVault(m)))
|
|
1860
1896
|
: dropped;
|
|
@@ -2163,7 +2199,13 @@ export async function createSession(opts) {
|
|
|
2163
2199
|
mutationVersionBySig.set(sig, mutationVersion);
|
|
2164
2200
|
if (!hasMutatedSince) {
|
|
2165
2201
|
const count = sigCounts.get(sig) ?? 0;
|
|
2166
|
-
|
|
2202
|
+
let loopThreshold = harness.quirks.loopsOnToolError ? 3 : 6;
|
|
2203
|
+
// If the cached observation already tells the model "no matches found",
|
|
2204
|
+
// break much earlier — the model is ignoring the hint.
|
|
2205
|
+
const cachedObs = execObservationCacheBySig.get(sig) ?? '';
|
|
2206
|
+
if (cachedObs.includes('Do NOT retry')) {
|
|
2207
|
+
loopThreshold = Math.min(loopThreshold, 3);
|
|
2208
|
+
}
|
|
2167
2209
|
// At 3x, inject vault context so the model gets the data it needs
|
|
2168
2210
|
if (count >= 3 && count < loopThreshold) {
|
|
2169
2211
|
await injectVaultContext().catch(() => { });
|
|
@@ -2190,7 +2232,7 @@ export async function createSession(opts) {
|
|
|
2190
2232
|
}
|
|
2191
2233
|
// Read-only tools: only count consecutive identical calls (back-to-back turns
|
|
2192
2234
|
// with no other tool calls in between). A read → edit → read cycle is normal
|
|
2193
|
-
// and resets the counter.
|
|
2235
|
+
// and resets the counter.
|
|
2194
2236
|
if (isReadOnlyTool(toolName)) {
|
|
2195
2237
|
// Check if this sig was also in the previous turn's set
|
|
2196
2238
|
if (lastTurnSigs.has(sig)) {
|
|
@@ -2203,8 +2245,8 @@ export async function createSession(opts) {
|
|
|
2203
2245
|
if (consec >= 3) {
|
|
2204
2246
|
await injectVaultContext().catch(() => { });
|
|
2205
2247
|
}
|
|
2206
|
-
// Hard-break: after
|
|
2207
|
-
if (consec >=
|
|
2248
|
+
// Hard-break: after 4 consecutive identical reads, stop the session
|
|
2249
|
+
if (consec >= 4) {
|
|
2208
2250
|
throw new Error(`tool ${toolName}: identical read repeated ${consec}x consecutively; breaking loop. ` +
|
|
2209
2251
|
`The resource content has not changed between reads.`);
|
|
2210
2252
|
}
|
|
@@ -2442,6 +2484,14 @@ export async function createSession(opts) {
|
|
|
2442
2484
|
content = await mcpManager.callTool(name, callArgs);
|
|
2443
2485
|
}
|
|
2444
2486
|
}
|
|
2487
|
+
// Append a hint when a read-only tool is called consecutively with
|
|
2488
|
+
// identical arguments — the model may not realize the content hasn't changed.
|
|
2489
|
+
if (isReadOnlyToolDynamic(name)) {
|
|
2490
|
+
const consec = consecutiveCounts.get(sig) ?? 0;
|
|
2491
|
+
if (consec >= 2) {
|
|
2492
|
+
content += `\n\n[WARNING: You have read this exact same resource ${consec}x consecutively with identical arguments. The content has NOT changed. Do NOT read it again. Use the information above and move on to the next step.]`;
|
|
2493
|
+
}
|
|
2494
|
+
}
|
|
2445
2495
|
// Hook: onToolResult (Phase 8.5 + Phase 7 rich display)
|
|
2446
2496
|
let toolSuccess = true;
|
|
2447
2497
|
let summary = reusedCachedReadOnlyExec
|