Skip to content

Commit 3bf9950

Browse files
committed
fix: thinkingText reset, /compact force mode, token map cleanup (v1.3.1)
1 parent 3bf3520 commit 3bf9950

File tree

10 files changed

+94
-11
lines changed

10 files changed

+94
-11
lines changed

dist/agent/compact.d.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,13 @@ export declare function autoCompactIfNeeded(history: Dialogue[], model: string,
1313
history: Dialogue[];
1414
compacted: boolean;
1515
}>;
16+
/**
17+
* Force compaction regardless of threshold (for /compact command).
18+
*/
19+
export declare function forceCompact(history: Dialogue[], model: string, client: ModelClient, debug?: boolean): Promise<{
20+
history: Dialogue[];
21+
compacted: boolean;
22+
}>;
1623
/**
1724
* Clear old tool results in-place to save tokens (microcompaction).
1825
* Replaces tool result content with a short summary for all but the last N results.

dist/agent/compact.js

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,26 @@ export async function autoCompactIfNeeded(history, model, client, debug) {
5151
return { history: truncated, compacted: true };
5252
}
5353
}
54+
/**
55+
* Force compaction regardless of threshold (for /compact command).
56+
*/
57+
export async function forceCompact(history, model, client, debug) {
58+
if (history.length <= 4) {
59+
return { history, compacted: false };
60+
}
61+
try {
62+
const compacted = await compactHistory(history, model, client, debug);
63+
return { history: compacted, compacted: true };
64+
}
65+
catch (err) {
66+
if (debug) {
67+
console.error(`[runcode] Force compaction failed: ${err.message}`);
68+
}
69+
const threshold = getCompactionThreshold(model);
70+
const truncated = emergencyTruncate(history, threshold);
71+
return { history: truncated, compacted: true };
72+
}
73+
}
5474
/**
5575
* Compact conversation history by summarizing older messages.
5676
*/

dist/agent/loop.js

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
* Original implementation with different architecture from any reference codebase.
55
*/
66
import { ModelClient } from './llm.js';
7-
import { autoCompactIfNeeded, microCompact } from './compact.js';
7+
import { autoCompactIfNeeded, forceCompact, microCompact } from './compact.js';
88
import { estimateHistoryTokens } from './tokens.js';
99
import { PermissionManager } from './permissions.js';
1010
import { StreamingExecutor } from './streaming-executor.js';
@@ -217,15 +217,15 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
217217
// Handle /compact command — force compaction without sending to model
218218
if (input === '/compact') {
219219
const beforeTokens = estimateHistoryTokens(history);
220-
const { history: compacted, compacted: didCompact } = await autoCompactIfNeeded(history, config.model, client, config.debug);
220+
const { history: compacted, compacted: didCompact } = await forceCompact(history, config.model, client, config.debug);
221221
if (didCompact) {
222222
history.length = 0;
223223
history.push(...compacted);
224224
}
225225
const afterTokens = estimateHistoryTokens(history);
226226
onEvent({ kind: 'text_delta', text: didCompact
227227
? `Compacted: ~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens\n`
228-
: `History is ${beforeTokens.toLocaleString()} tokens — no compaction needed.\n`
228+
: `History too short to compact (${beforeTokens.toLocaleString()} tokens, ${history.length} messages).\n`
229229
});
230230
onEvent({ kind: 'turn_done', reason: 'completed' });
231231
continue;

dist/proxy/server.js

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,16 @@ function log(...args) {
4141
}
4242
const DEFAULT_MAX_TOKENS = 4096;
4343
// Per-model last output tokens for adaptive max_tokens (avoids cross-request pollution)
44+
const MAX_TRACKED_MODELS = 50;
4445
const lastOutputByModel = new Map();
46+
function trackOutputTokens(model, tokens) {
47+
if (lastOutputByModel.size >= MAX_TRACKED_MODELS) {
48+
const firstKey = lastOutputByModel.keys().next().value;
49+
if (firstKey)
50+
lastOutputByModel.delete(firstKey);
51+
}
52+
lastOutputByModel.set(model, tokens);
53+
}
4554
// Model shortcuts for quick switching
4655
const MODEL_SHORTCUTS = {
4756
// Routing profiles
@@ -388,7 +397,7 @@ export function createProxy(options) {
388397
const inputMatch = fullResponse.match(/"input_tokens"\s*:\s*(\d+)/);
389398
if (lastOutputMatch) {
390399
const outputTokens = parseInt(lastOutputMatch[1], 10);
391-
lastOutputByModel.set(finalModel, outputTokens);
400+
trackOutputTokens(finalModel, outputTokens);
392401
const inputTokens = inputMatch
393402
? parseInt(inputMatch[1], 10)
394403
: 0;
@@ -419,7 +428,7 @@ export function createProxy(options) {
419428
const parsed = JSON.parse(text);
420429
if (parsed.usage?.output_tokens) {
421430
const outputTokens = parsed.usage.output_tokens;
422-
lastOutputByModel.set(finalModel, outputTokens);
431+
trackOutputTokens(finalModel, outputTokens);
423432
const inputTokens = parsed.usage?.input_tokens || 0;
424433
const latencyMs = Date.now() - requestStartTime;
425434
const cost = estimateCost(finalModel, inputTokens, outputTokens);

dist/ui/app.js

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,12 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
153153
setTimeout(() => setStatusMsg(''), 3000);
154154
return;
155155
case '/compact':
156+
setStreamText('');
157+
setThinking(false);
158+
setThinkingText('');
159+
setTools(new Map());
160+
setWaiting(true);
161+
setReady(false);
156162
onSubmit('/compact');
157163
return;
158164
default:
@@ -233,6 +239,7 @@ function RunCodeApp({ initialModel, workDir, walletAddress, walletBalance, chain
233239
setReady(true);
234240
setWaiting(false);
235241
setThinking(false);
242+
setThinkingText('');
236243
break;
237244
}
238245
},

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@blockrun/runcode",
3-
"version": "1.3.0",
3+
"version": "1.3.1",
44
"description": "RunCode — AI coding agent powered by 41+ models. Pay per use with USDC.",
55
"type": "module",
66
"bin": {

src/agent/compact.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,31 @@ export async function autoCompactIfNeeded(
7171
}
7272
}
7373

74+
/**
75+
* Force compaction regardless of threshold (for /compact command).
76+
*/
77+
export async function forceCompact(
78+
history: Dialogue[],
79+
model: string,
80+
client: ModelClient,
81+
debug?: boolean
82+
): Promise<{ history: Dialogue[]; compacted: boolean }> {
83+
if (history.length <= 4) {
84+
return { history, compacted: false };
85+
}
86+
try {
87+
const compacted = await compactHistory(history, model, client, debug);
88+
return { history: compacted, compacted: true };
89+
} catch (err) {
90+
if (debug) {
91+
console.error(`[runcode] Force compaction failed: ${(err as Error).message}`);
92+
}
93+
const threshold = getCompactionThreshold(model);
94+
const truncated = emergencyTruncate(history, threshold);
95+
return { history: truncated, compacted: true };
96+
}
97+
}
98+
7499
/**
75100
* Compact conversation history by summarizing older messages.
76101
*/

src/agent/loop.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
*/
66

77
import { ModelClient } from './llm.js';
8-
import { autoCompactIfNeeded, microCompact } from './compact.js';
8+
import { autoCompactIfNeeded, forceCompact, microCompact } from './compact.js';
99
import { estimateHistoryTokens } from './tokens.js';
1010
import { PermissionManager } from './permissions.js';
1111
import { StreamingExecutor } from './streaming-executor.js';
@@ -317,15 +317,15 @@ export async function interactiveSession(
317317
if (input === '/compact') {
318318
const beforeTokens = estimateHistoryTokens(history);
319319
const { history: compacted, compacted: didCompact } =
320-
await autoCompactIfNeeded(history, config.model, client, config.debug);
320+
await forceCompact(history, config.model, client, config.debug);
321321
if (didCompact) {
322322
history.length = 0;
323323
history.push(...compacted);
324324
}
325325
const afterTokens = estimateHistoryTokens(history);
326326
onEvent({ kind: 'text_delta', text: didCompact
327327
? `Compacted: ~${beforeTokens.toLocaleString()} → ~${afterTokens.toLocaleString()} tokens\n`
328-
: `History is ${beforeTokens.toLocaleString()} tokens — no compaction needed.\n`
328+
: `History too short to compact (${beforeTokens.toLocaleString()} tokens, ${history.length} messages).\n`
329329
});
330330
onEvent({ kind: 'turn_done', reason: 'completed' });
331331
continue;

src/proxy/server.ts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,15 @@ function log(...args: unknown[]) {
7373

7474
const DEFAULT_MAX_TOKENS = 4096;
7575
// Per-model last output tokens for adaptive max_tokens (avoids cross-request pollution)
76+
const MAX_TRACKED_MODELS = 50;
7677
const lastOutputByModel = new Map<string, number>();
78+
function trackOutputTokens(model: string, tokens: number) {
79+
if (lastOutputByModel.size >= MAX_TRACKED_MODELS) {
80+
const firstKey = lastOutputByModel.keys().next().value;
81+
if (firstKey) lastOutputByModel.delete(firstKey);
82+
}
83+
lastOutputByModel.set(model, tokens);
84+
}
7785

7886
// Model shortcuts for quick switching
7987
const MODEL_SHORTCUTS: Record<string, string> = {
@@ -491,7 +499,7 @@ export function createProxy(options: ProxyOptions): http.Server {
491499
);
492500
if (lastOutputMatch) {
493501
const outputTokens = parseInt(lastOutputMatch[1], 10);
494-
lastOutputByModel.set(finalModel, outputTokens);
502+
trackOutputTokens(finalModel, outputTokens);
495503
const inputTokens = inputMatch
496504
? parseInt(inputMatch[1], 10)
497505
: 0;
@@ -536,7 +544,7 @@ export function createProxy(options: ProxyOptions): http.Server {
536544
const parsed = JSON.parse(text);
537545
if (parsed.usage?.output_tokens) {
538546
const outputTokens = parsed.usage.output_tokens;
539-
lastOutputByModel.set(finalModel, outputTokens);
547+
trackOutputTokens(finalModel, outputTokens);
540548
const inputTokens = parsed.usage?.input_tokens || 0;
541549
const latencyMs = Date.now() - requestStartTime;
542550
const cost = estimateCost(

src/ui/app.tsx

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -225,6 +225,12 @@ function RunCodeApp({
225225
return;
226226

227227
case '/compact':
228+
setStreamText('');
229+
setThinking(false);
230+
setThinkingText('');
231+
setTools(new Map());
232+
setWaiting(true);
233+
setReady(false);
228234
onSubmit('/compact');
229235
return;
230236

@@ -308,6 +314,7 @@ function RunCodeApp({
308314
setReady(true);
309315
setWaiting(false);
310316
setThinking(false);
317+
setThinkingText('');
311318
break;
312319
}
313320
},

0 commit comments

Comments
 (0)