@blockrun/runcode 2.5.0 → 2.5.1
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/loop.js +1 -0
- package/dist/agent/streaming-executor.js +19 -13
- package/dist/commands/start.js +0 -2
- package/dist/index.js +2 -0
- package/dist/tools/bash.js +16 -9
- package/dist/ui/terminal.d.ts +9 -1
- package/dist/ui/terminal.js +55 -36
- package/package.json +1 -1
package/dist/agent/loop.js
CHANGED
|
@@ -267,6 +267,7 @@ export async function interactiveSession(config, getUserInput, onEvent, onAbortR
|
|
|
267
267
|
if (microCompacted !== history) {
|
|
268
268
|
history.length = 0;
|
|
269
269
|
history.push(...microCompacted);
|
|
270
|
+
resetTokenAnchor(); // History shrunk — resync token tracking
|
|
270
271
|
}
|
|
271
272
|
}
|
|
272
273
|
// 3. Auto-compact: summarize history if approaching context limit
|
|
@@ -39,21 +39,27 @@ export class StreamingExecutor {
|
|
|
39
39
|
async collectResults(allInvocations) {
|
|
40
40
|
const results = [];
|
|
41
41
|
const alreadyStarted = new Set(this.pending.map(p => p.invocation.id));
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
results
|
|
42
|
+
const pendingSnapshot = [...this.pending];
|
|
43
|
+
this.pending = []; // Clear immediately so errors don't leave stale state
|
|
44
|
+
try {
|
|
45
|
+
// Wait for concurrent results that were started during streaming
|
|
46
|
+
for (const p of pendingSnapshot) {
|
|
47
|
+
const result = await p.promise;
|
|
48
|
+
results.push([p.invocation, result]);
|
|
49
|
+
}
|
|
50
|
+
// Execute sequential (non-concurrent) tools now
|
|
51
|
+
for (const inv of allInvocations) {
|
|
52
|
+
if (alreadyStarted.has(inv.id))
|
|
53
|
+
continue;
|
|
54
|
+
this.onStart(inv.id, inv.name);
|
|
55
|
+
const result = await this.executeWithPermissions(inv);
|
|
56
|
+
results.push([inv, result]);
|
|
57
|
+
}
|
|
46
58
|
}
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
continue;
|
|
51
|
-
this.onStart(inv.id, inv.name);
|
|
52
|
-
const result = await this.executeWithPermissions(inv);
|
|
53
|
-
results.push([inv, result]);
|
|
59
|
+
catch (err) {
|
|
60
|
+
// Return partial results rather than losing them; caller handles errors
|
|
61
|
+
throw err;
|
|
54
62
|
}
|
|
55
|
-
// Clear for next round
|
|
56
|
-
this.pending = [];
|
|
57
63
|
return results;
|
|
58
64
|
}
|
|
59
65
|
async executeWithPermissions(invocation) {
|
package/dist/commands/start.js
CHANGED
|
@@ -170,7 +170,6 @@ async function runWithInkUI(agentConfig, model, workDir, version, walletInfo, on
|
|
|
170
170
|
flushStats();
|
|
171
171
|
await disconnectMcpServers();
|
|
172
172
|
console.log(chalk.dim('\nGoodbye.\n'));
|
|
173
|
-
process.exit(0);
|
|
174
173
|
}
|
|
175
174
|
// ─── Basic readline UI (piped input) ───────────────────────────────────────
|
|
176
175
|
async function runWithBasicUI(agentConfig, model, workDir) {
|
|
@@ -224,7 +223,6 @@ async function runWithBasicUI(agentConfig, model, workDir) {
|
|
|
224
223
|
}
|
|
225
224
|
ui.printGoodbye();
|
|
226
225
|
flushStats();
|
|
227
|
-
process.exit(0);
|
|
228
226
|
}
|
|
229
227
|
async function handleSlashCommand(cmd, config, ui) {
|
|
230
228
|
const parts = cmd.trim().split(/\s+/);
|
package/dist/index.js
CHANGED
|
@@ -108,6 +108,7 @@ if (firstArg === 'solana' || firstArg === 'base') {
|
|
|
108
108
|
}
|
|
109
109
|
}
|
|
110
110
|
await startCommand(startOpts);
|
|
111
|
+
process.exit(0);
|
|
111
112
|
}
|
|
112
113
|
else if (!firstArg || (firstArg.startsWith('-') && !['-h', '--help', '-V', '--version'].includes(firstArg))) {
|
|
113
114
|
// No subcommand or only flags — treat as 'start' with flags
|
|
@@ -122,6 +123,7 @@ else if (!firstArg || (firstArg.startsWith('-') && !['-h', '--help', '-V', '--ve
|
|
|
122
123
|
}
|
|
123
124
|
}
|
|
124
125
|
await startCommand(startOpts);
|
|
126
|
+
process.exit(0);
|
|
125
127
|
}
|
|
126
128
|
else {
|
|
127
129
|
program.parse();
|
package/dist/tools/bash.js
CHANGED
|
@@ -12,15 +12,22 @@ async function execute(input, ctx) {
|
|
|
12
12
|
const timeoutMs = Math.min(timeout ?? DEFAULT_TIMEOUT_MS, 600_000);
|
|
13
13
|
return new Promise((resolve) => {
|
|
14
14
|
const shell = process.env.SHELL || '/bin/bash';
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
15
|
+
let child;
|
|
16
|
+
try {
|
|
17
|
+
child = spawn(shell, ['-c', command], {
|
|
18
|
+
cwd: ctx.workingDir,
|
|
19
|
+
env: {
|
|
20
|
+
...process.env,
|
|
21
|
+
RUNCODE: '1', // Let scripts detect they're running inside runcode
|
|
22
|
+
RUNCODE_WORKDIR: ctx.workingDir,
|
|
23
|
+
},
|
|
24
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
25
|
+
});
|
|
26
|
+
}
|
|
27
|
+
catch (spawnErr) {
|
|
28
|
+
resolve({ output: `Error spawning shell: ${spawnErr.message}`, isError: true });
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
24
31
|
let stdout = '';
|
|
25
32
|
let stderr = '';
|
|
26
33
|
let outputBytes = 0;
|
package/dist/ui/terminal.d.ts
CHANGED
|
@@ -10,15 +10,23 @@ export declare class TerminalUI {
|
|
|
10
10
|
private totalInputTokens;
|
|
11
11
|
private totalOutputTokens;
|
|
12
12
|
private mdRenderer;
|
|
13
|
+
private lineQueue;
|
|
14
|
+
private lineWaiters;
|
|
15
|
+
private stdinEOF;
|
|
16
|
+
constructor();
|
|
13
17
|
/**
|
|
14
18
|
* Prompt the user for input. Returns null on EOF/exit.
|
|
19
|
+
* Uses a line-queue approach so piped input works across multiple calls.
|
|
15
20
|
*/
|
|
16
21
|
promptUser(promptText?: string): Promise<string | null>;
|
|
22
|
+
private nextLine;
|
|
23
|
+
/** No-op kept for API compatibility — readline closes when stdin EOF. */
|
|
24
|
+
closeInput(): void;
|
|
17
25
|
/**
|
|
18
26
|
* Handle a stream event from the agent loop.
|
|
19
27
|
*/
|
|
20
28
|
handleEvent(event: StreamEvent): void;
|
|
21
|
-
/** Check if input is a slash command. Returns true if handled. */
|
|
29
|
+
/** Check if input is a slash command. Returns true if handled locally (don't pass to agent). */
|
|
22
30
|
handleSlashCommand(input: string): boolean;
|
|
23
31
|
printWelcome(model: string, workDir: string): void;
|
|
24
32
|
printUsageSummary(): void;
|
package/dist/ui/terminal.js
CHANGED
|
@@ -131,38 +131,65 @@ export class TerminalUI {
|
|
|
131
131
|
totalInputTokens = 0;
|
|
132
132
|
totalOutputTokens = 0;
|
|
133
133
|
mdRenderer = new MarkdownRenderer();
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
134
|
+
// Line queue for piped (non-TTY) input — buffers all stdin lines eagerly
|
|
135
|
+
lineQueue = [];
|
|
136
|
+
lineWaiters = [];
|
|
137
|
+
stdinEOF = false;
|
|
138
|
+
constructor() {
|
|
138
139
|
const rl = readline.createInterface({
|
|
139
140
|
input: process.stdin,
|
|
140
141
|
output: process.stderr,
|
|
141
|
-
terminal:
|
|
142
|
+
terminal: false, // Always treat as non-TTY so line events fire for piped input
|
|
143
|
+
});
|
|
144
|
+
rl.on('line', (line) => {
|
|
145
|
+
if (this.lineWaiters.length > 0) {
|
|
146
|
+
// Someone is already waiting — deliver immediately
|
|
147
|
+
const waiter = this.lineWaiters.shift();
|
|
148
|
+
waiter(line);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
// Buffer the line for the next promptUser() call
|
|
152
|
+
this.lineQueue.push(line);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
155
|
+
rl.on('close', () => {
|
|
156
|
+
this.stdinEOF = true;
|
|
157
|
+
this.lineQueue = []; // Don't deliver buffered lines after EOF — signal exit cleanly
|
|
158
|
+
for (const waiter of this.lineWaiters)
|
|
159
|
+
waiter(null);
|
|
160
|
+
this.lineWaiters = [];
|
|
142
161
|
});
|
|
162
|
+
}
|
|
163
|
+
/**
|
|
164
|
+
* Prompt the user for input. Returns null on EOF/exit.
|
|
165
|
+
* Uses a line-queue approach so piped input works across multiple calls.
|
|
166
|
+
*/
|
|
167
|
+
async promptUser(promptText) {
|
|
168
|
+
const prompt = promptText ?? chalk.bold.green('> ');
|
|
169
|
+
process.stderr.write(prompt);
|
|
170
|
+
const raw = await this.nextLine();
|
|
171
|
+
if (raw === null)
|
|
172
|
+
return null;
|
|
173
|
+
const trimmed = raw.trim();
|
|
174
|
+
if (trimmed === '/exit' || trimmed === '/quit')
|
|
175
|
+
return null;
|
|
176
|
+
return trimmed;
|
|
177
|
+
}
|
|
178
|
+
nextLine() {
|
|
179
|
+
if (this.lineQueue.length > 0) {
|
|
180
|
+
return Promise.resolve(this.lineQueue.shift());
|
|
181
|
+
}
|
|
182
|
+
if (this.stdinEOF) {
|
|
183
|
+
return Promise.resolve(null);
|
|
184
|
+
}
|
|
143
185
|
return new Promise((resolve) => {
|
|
144
|
-
|
|
145
|
-
const prompt = promptText ?? chalk.bold.green('> ');
|
|
146
|
-
rl.question(prompt, (answer) => {
|
|
147
|
-
answered = true;
|
|
148
|
-
rl.close();
|
|
149
|
-
const trimmed = answer.trim();
|
|
150
|
-
if (trimmed === '/exit' || trimmed === '/quit') {
|
|
151
|
-
resolve(null);
|
|
152
|
-
}
|
|
153
|
-
else if (trimmed === '') {
|
|
154
|
-
resolve('');
|
|
155
|
-
}
|
|
156
|
-
else {
|
|
157
|
-
resolve(trimmed);
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
rl.on('close', () => {
|
|
161
|
-
if (!answered)
|
|
162
|
-
resolve(null);
|
|
163
|
-
});
|
|
186
|
+
this.lineWaiters.push(resolve);
|
|
164
187
|
});
|
|
165
188
|
}
|
|
189
|
+
/** No-op kept for API compatibility — readline closes when stdin EOF. */
|
|
190
|
+
closeInput() {
|
|
191
|
+
// Nothing to do — readline closes itself on stdin EOF
|
|
192
|
+
}
|
|
166
193
|
/**
|
|
167
194
|
* Handle a stream event from the agent loop.
|
|
168
195
|
*/
|
|
@@ -260,26 +287,17 @@ export class TerminalUI {
|
|
|
260
287
|
}
|
|
261
288
|
}
|
|
262
289
|
}
|
|
263
|
-
/** Check if input is a slash command. Returns true if handled. */
|
|
290
|
+
/** Check if input is a slash command. Returns true if handled locally (don't pass to agent). */
|
|
264
291
|
handleSlashCommand(input) {
|
|
265
292
|
const parts = input.trim().split(/\s+/);
|
|
266
293
|
const cmd = parts[0].toLowerCase();
|
|
267
294
|
switch (cmd) {
|
|
268
|
-
case '/help':
|
|
269
|
-
console.error(chalk.bold('\n Commands:'));
|
|
270
|
-
console.error(' /model [name] — switch model (e.g. /model sonnet)');
|
|
271
|
-
console.error(' /cost — session cost and tokens');
|
|
272
|
-
console.error(' /retry — retry the last prompt');
|
|
273
|
-
console.error(' /compact — compress conversation history');
|
|
274
|
-
console.error(' /exit — quit');
|
|
275
|
-
console.error(' /help — this help\n');
|
|
276
|
-
console.error(chalk.dim(' Shortcuts: sonnet, opus, gpt, gemini, deepseek, flash, free, r1, o4\n'));
|
|
277
|
-
return true;
|
|
278
295
|
case '/cost':
|
|
279
296
|
case '/usage':
|
|
280
297
|
console.error(chalk.dim(`\n Tokens: ${this.totalInputTokens.toLocaleString()} in / ${this.totalOutputTokens.toLocaleString()} out\n`));
|
|
281
298
|
return true;
|
|
282
299
|
default:
|
|
300
|
+
// All other slash commands pass through to the agent loop (commands.ts handles them)
|
|
283
301
|
return false;
|
|
284
302
|
}
|
|
285
303
|
}
|
|
@@ -294,6 +312,7 @@ export class TerminalUI {
|
|
|
294
312
|
}
|
|
295
313
|
}
|
|
296
314
|
printGoodbye() {
|
|
315
|
+
this.closeInput();
|
|
297
316
|
this.printUsageSummary();
|
|
298
317
|
console.error(chalk.dim('\nGoodbye.\n'));
|
|
299
318
|
}
|