tinker-agent 1.6.0 → 1.7.0
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/CHANGELOG.md +22 -1
- package/README.md +2 -2
- package/package.json +3 -3
- package/src/cli/runner-dependencies.ts +4 -1
- package/src/events/bash-result-detail.ts +13 -6
- package/src/events/observation-text-log.ts +5 -1
- package/src/events/stdout-event-printer.ts +7 -2
- package/src/model/fake-model-client.ts +84 -0
- package/src/observation/observation-builder.ts +43 -6
- package/src/session/session-catalog.ts +17 -11
- package/src/session/session-store.ts +1 -0
- package/src/tools/bash-task.ts +129 -90
- package/src/tools/bash.ts +44 -13
- package/src/tools/registry.ts +2 -0
- package/src/tools/shell-process.ts +296 -0
- package/src/tools/task-input.ts +229 -0
- package/src/tools/task-output-tool.ts +4 -1
- package/src/tools/terminal-screen.ts +105 -0
- package/src/tools/types.ts +32 -0
- package/src/tui/components/background-tasks.tsx +3 -2
- package/src/tui/components/resume-session-picker.tsx +292 -46
- package/src/tui/event-store.ts +14 -1
- package/src/tui/tui-session-controller.ts +1 -1
package/src/tools/bash-task.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { spawn, type ChildProcessWithoutNullStreams } from "node:child_process";
|
|
2
1
|
import { mkdir, open, readFile, unlink } from "node:fs/promises";
|
|
3
2
|
import path from "node:path";
|
|
4
3
|
import type {
|
|
@@ -8,7 +7,19 @@ import type {
|
|
|
8
7
|
import type { ToolCallIdentity } from "../agent/types";
|
|
9
8
|
import { createUuidV7 } from "../ids/uuid-v7";
|
|
10
9
|
import { isWorkspaceLocalCwd, type CwdState } from "./cwd-state";
|
|
10
|
+
import {
|
|
11
|
+
type ProcessExitResult,
|
|
12
|
+
type ShellProcessHandle,
|
|
13
|
+
type ShellProcessMode,
|
|
14
|
+
spawnShellProcess,
|
|
15
|
+
} from "./shell-process";
|
|
11
16
|
import { TaskOutput, type TaskOutputSnapshot } from "./task-output";
|
|
17
|
+
import {
|
|
18
|
+
createTerminalScreen,
|
|
19
|
+
TERMINAL_SCREEN_COLUMNS,
|
|
20
|
+
TERMINAL_SCREEN_ROWS,
|
|
21
|
+
type TerminalScreen,
|
|
22
|
+
} from "./terminal-screen";
|
|
12
23
|
|
|
13
24
|
export type ShellTaskStatus =
|
|
14
25
|
| "running"
|
|
@@ -38,11 +49,15 @@ export type ShellTaskSnapshot = {
|
|
|
38
49
|
outputBytes: number;
|
|
39
50
|
outputLines: number;
|
|
40
51
|
cwd: string;
|
|
52
|
+
tty: boolean;
|
|
41
53
|
};
|
|
42
54
|
|
|
43
55
|
export type ShellTaskInspection = {
|
|
44
56
|
task: ShellTaskSnapshot;
|
|
45
57
|
output: TaskOutputSnapshot;
|
|
58
|
+
screenRows?: number;
|
|
59
|
+
screenColumns?: number;
|
|
60
|
+
screen?: string;
|
|
46
61
|
};
|
|
47
62
|
|
|
48
63
|
export type ShellTaskHandle = {
|
|
@@ -81,9 +96,12 @@ type ManagedShellTask = {
|
|
|
81
96
|
outputFilePath: string;
|
|
82
97
|
cwdFilePath: string;
|
|
83
98
|
cwd: string;
|
|
84
|
-
|
|
99
|
+
mode: ShellProcessMode;
|
|
100
|
+
process: ShellProcessHandle;
|
|
85
101
|
processGroupId: number;
|
|
86
102
|
output: TaskOutput;
|
|
103
|
+
terminalScreen?: TerminalScreen;
|
|
104
|
+
finalScreen?: string;
|
|
87
105
|
completion: Promise<ShellTaskSnapshot>;
|
|
88
106
|
stopPromise?: Promise<StopTaskResult>;
|
|
89
107
|
terminalEventEmitted: boolean;
|
|
@@ -115,6 +133,7 @@ export class ShellTaskManager {
|
|
|
115
133
|
command: string;
|
|
116
134
|
description: string;
|
|
117
135
|
origin: ShellTaskOrigin;
|
|
136
|
+
tty: boolean;
|
|
118
137
|
}): Promise<ShellTaskHandle> {
|
|
119
138
|
if (!this.acceptingTasks) {
|
|
120
139
|
throw new Error("Cannot start a Bash task after task manager shutdown.");
|
|
@@ -142,22 +161,26 @@ export class ShellTaskManager {
|
|
|
142
161
|
throw new Error("Cannot start a Bash task after task manager shutdown.");
|
|
143
162
|
}
|
|
144
163
|
|
|
145
|
-
const
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
164
|
+
const terminalScreen = input.tty ? createTerminalScreen() : undefined;
|
|
165
|
+
let shellProcess: ShellProcessHandle;
|
|
166
|
+
try {
|
|
167
|
+
shellProcess = await spawnShellProcess({
|
|
168
|
+
mode: input.tty ? "pty" : "pipe",
|
|
169
|
+
command: input.command,
|
|
170
|
+
cwd: this.options.cwdState.cwd,
|
|
171
|
+
cwdFilePath,
|
|
172
|
+
onOutput(bytes) {
|
|
173
|
+
output.write(Buffer.from(bytes));
|
|
174
|
+
if (terminalScreen !== undefined) {
|
|
175
|
+
void terminalScreen.write(bytes).catch(() => undefined);
|
|
176
|
+
}
|
|
177
|
+
},
|
|
178
|
+
});
|
|
179
|
+
} catch (error) {
|
|
180
|
+
terminalScreen?.dispose();
|
|
158
181
|
await output.end();
|
|
159
182
|
await unlinkIfExists(cwdFilePath);
|
|
160
|
-
throw
|
|
183
|
+
throw error;
|
|
161
184
|
}
|
|
162
185
|
|
|
163
186
|
const task: ManagedShellTask = {
|
|
@@ -170,15 +193,15 @@ export class ShellTaskManager {
|
|
|
170
193
|
outputFilePath,
|
|
171
194
|
cwdFilePath,
|
|
172
195
|
cwd: this.options.cwdState.cwd,
|
|
173
|
-
|
|
174
|
-
|
|
196
|
+
mode: shellProcess.mode,
|
|
197
|
+
process: shellProcess,
|
|
198
|
+
processGroupId: shellProcess.pid,
|
|
175
199
|
output,
|
|
200
|
+
terminalScreen,
|
|
176
201
|
completion: Promise.resolve(undefined as never),
|
|
177
202
|
terminalEventEmitted: false,
|
|
178
203
|
};
|
|
179
204
|
|
|
180
|
-
pipeTaskOutput(task.process.stdout, task.output);
|
|
181
|
-
pipeTaskOutput(task.process.stderr, task.output);
|
|
182
205
|
task.completion = this.monitorTaskSafely(task);
|
|
183
206
|
this.tasks.set(id, task);
|
|
184
207
|
|
|
@@ -231,10 +254,45 @@ export class ShellTaskManager {
|
|
|
231
254
|
}
|
|
232
255
|
|
|
233
256
|
this.synchronizeTerminalState(task);
|
|
234
|
-
return
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
257
|
+
return this.inspection(task);
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
async inspectTaskOutput(taskId: string): Promise<ShellTaskInspection | undefined> {
|
|
261
|
+
const task = this.tasks.get(taskId);
|
|
262
|
+
if (task === undefined) {
|
|
263
|
+
return undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
this.synchronizeTerminalState(task);
|
|
267
|
+
if (
|
|
268
|
+
task.mode === "pty" &&
|
|
269
|
+
isTerminalStatus(task.status) &&
|
|
270
|
+
task.finalScreen === undefined
|
|
271
|
+
) {
|
|
272
|
+
await task.completion;
|
|
273
|
+
} else {
|
|
274
|
+
await task.terminalScreen?.flush();
|
|
275
|
+
}
|
|
276
|
+
return this.inspection(task);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
taskCompletion(taskId: string): Promise<ShellTaskSnapshot> {
|
|
280
|
+
return this.requireTask(taskId).completion;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async writeTaskInput(taskId: string, chars: string): Promise<number> {
|
|
284
|
+
const task = this.requireTask(taskId);
|
|
285
|
+
this.synchronizeTerminalState(task);
|
|
286
|
+
if (task.mode !== "pty" || task.process.write === undefined) {
|
|
287
|
+
throw new Error(
|
|
288
|
+
`Task ${taskId} does not accept terminal input; start it with Bash tty=true.`,
|
|
289
|
+
);
|
|
290
|
+
}
|
|
291
|
+
if (task.status !== "running") {
|
|
292
|
+
throw new Error(`Task ${taskId} is not running (status=${task.status}).`);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
return task.process.write(chars);
|
|
238
296
|
}
|
|
239
297
|
|
|
240
298
|
async stopTask(taskId: string, reason: StopTaskReason): Promise<StopTaskResult> {
|
|
@@ -372,20 +430,43 @@ export class ShellTaskManager {
|
|
|
372
430
|
outputError instanceof Error ? outputError.message : String(outputError)
|
|
373
431
|
}`;
|
|
374
432
|
}
|
|
433
|
+
if (task.terminalScreen !== undefined) {
|
|
434
|
+
try {
|
|
435
|
+
await task.terminalScreen.flush();
|
|
436
|
+
task.finalScreen = task.terminalScreen.text();
|
|
437
|
+
} catch {
|
|
438
|
+
// The original monitor error remains the primary task failure.
|
|
439
|
+
}
|
|
440
|
+
task.terminalScreen.dispose();
|
|
441
|
+
}
|
|
442
|
+
task.process.close();
|
|
375
443
|
await unlinkIfExists(task.cwdFilePath);
|
|
376
444
|
|
|
377
|
-
|
|
445
|
+
const snapshot = this.snapshot(task);
|
|
446
|
+
if (task.backgroundedAt !== undefined && !task.terminalEventEmitted) {
|
|
447
|
+
task.terminalEventEmitted = true;
|
|
448
|
+
await this.options.runtimeSession.append({
|
|
449
|
+
type: "bash.task.finished",
|
|
450
|
+
...task.origin,
|
|
451
|
+
data: { task: snapshot },
|
|
452
|
+
});
|
|
453
|
+
}
|
|
454
|
+
return snapshot;
|
|
378
455
|
}
|
|
379
456
|
}
|
|
380
457
|
|
|
381
458
|
private async monitorTask(task: ManagedShellTask): Promise<ShellTaskSnapshot> {
|
|
382
|
-
const
|
|
383
|
-
const close = waitForProcessClose(task.process);
|
|
384
|
-
const result = await exit;
|
|
459
|
+
const result = await task.process.wait();
|
|
385
460
|
|
|
386
461
|
this.applyTermination(task, result);
|
|
387
|
-
await
|
|
462
|
+
await task.process.waitForOutputClose();
|
|
388
463
|
await task.output.end();
|
|
464
|
+
if (task.terminalScreen !== undefined) {
|
|
465
|
+
await task.terminalScreen.flush();
|
|
466
|
+
task.finalScreen = task.terminalScreen.text();
|
|
467
|
+
task.terminalScreen.dispose();
|
|
468
|
+
}
|
|
469
|
+
task.process.close();
|
|
389
470
|
await this.updateCwdFromFile(task);
|
|
390
471
|
await unlinkIfExists(task.cwdFilePath);
|
|
391
472
|
|
|
@@ -464,6 +545,25 @@ export class ShellTaskManager {
|
|
|
464
545
|
outputBytes: output.outputBytes,
|
|
465
546
|
outputLines: output.outputLines,
|
|
466
547
|
cwd: task.cwd,
|
|
548
|
+
tty: task.mode === "pty",
|
|
549
|
+
};
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
private inspection(task: ManagedShellTask): ShellTaskInspection {
|
|
553
|
+
const screen =
|
|
554
|
+
task.mode === "pty"
|
|
555
|
+
? (task.finalScreen ?? task.terminalScreen?.text() ?? "")
|
|
556
|
+
: undefined;
|
|
557
|
+
return {
|
|
558
|
+
task: this.snapshot(task),
|
|
559
|
+
output: task.output.snapshot(),
|
|
560
|
+
...(screen === undefined
|
|
561
|
+
? {}
|
|
562
|
+
: {
|
|
563
|
+
screenRows: TERMINAL_SCREEN_ROWS,
|
|
564
|
+
screenColumns: TERMINAL_SCREEN_COLUMNS,
|
|
565
|
+
screen,
|
|
566
|
+
}),
|
|
467
567
|
};
|
|
468
568
|
}
|
|
469
569
|
|
|
@@ -488,54 +588,6 @@ export class ShellTaskManager {
|
|
|
488
588
|
}
|
|
489
589
|
}
|
|
490
590
|
|
|
491
|
-
type ProcessExitResult = {
|
|
492
|
-
code: number | null;
|
|
493
|
-
signal: NodeJS.Signals | null;
|
|
494
|
-
error?: string;
|
|
495
|
-
};
|
|
496
|
-
|
|
497
|
-
function waitForProcessExit(
|
|
498
|
-
process: ChildProcessWithoutNullStreams,
|
|
499
|
-
): Promise<ProcessExitResult> {
|
|
500
|
-
return new Promise((resolve) => {
|
|
501
|
-
let settled = false;
|
|
502
|
-
const finish = (result: ProcessExitResult) => {
|
|
503
|
-
if (!settled) {
|
|
504
|
-
settled = true;
|
|
505
|
-
resolve(result);
|
|
506
|
-
}
|
|
507
|
-
};
|
|
508
|
-
|
|
509
|
-
process.once("error", (error) => {
|
|
510
|
-
finish({ code: null, signal: null, error: error.message });
|
|
511
|
-
});
|
|
512
|
-
process.once("exit", (code, signal) => {
|
|
513
|
-
finish({ code, signal });
|
|
514
|
-
});
|
|
515
|
-
});
|
|
516
|
-
}
|
|
517
|
-
|
|
518
|
-
function waitForProcessError(process: ChildProcessWithoutNullStreams): Promise<Error> {
|
|
519
|
-
return new Promise((resolve) => {
|
|
520
|
-
process.once("error", resolve);
|
|
521
|
-
});
|
|
522
|
-
}
|
|
523
|
-
|
|
524
|
-
function waitForProcessClose(process: ChildProcessWithoutNullStreams): Promise<void> {
|
|
525
|
-
return new Promise((resolve) => {
|
|
526
|
-
let settled = false;
|
|
527
|
-
const finish = () => {
|
|
528
|
-
if (!settled) {
|
|
529
|
-
settled = true;
|
|
530
|
-
resolve();
|
|
531
|
-
}
|
|
532
|
-
};
|
|
533
|
-
|
|
534
|
-
process.once("error", finish);
|
|
535
|
-
process.once("close", finish);
|
|
536
|
-
});
|
|
537
|
-
}
|
|
538
|
-
|
|
539
591
|
function signalProcessGroup(
|
|
540
592
|
task: ManagedShellTask,
|
|
541
593
|
signal: "SIGTERM" | "SIGKILL",
|
|
@@ -573,12 +625,6 @@ function isTerminalStatus(status: ShellTaskStatus): boolean {
|
|
|
573
625
|
return status === "completed" || status === "failed" || status === "killed";
|
|
574
626
|
}
|
|
575
627
|
|
|
576
|
-
function pipeTaskOutput(stream: NodeJS.ReadableStream, output: TaskOutput): void {
|
|
577
|
-
stream.on("data", (chunk: Buffer | string) => {
|
|
578
|
-
output.write(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
|
579
|
-
});
|
|
580
|
-
}
|
|
581
|
-
|
|
582
628
|
async function ensureEmptyFile(filePath: string): Promise<void> {
|
|
583
629
|
await mkdir(path.dirname(filePath), { recursive: true });
|
|
584
630
|
const file = await open(filePath, "w");
|
|
@@ -608,10 +654,3 @@ function errorCode(error: unknown): unknown {
|
|
|
608
654
|
? error.code
|
|
609
655
|
: undefined;
|
|
610
656
|
}
|
|
611
|
-
|
|
612
|
-
const bashWrapperScript = `
|
|
613
|
-
eval "$TINKER_BASH_COMMAND"
|
|
614
|
-
exit_code=$?
|
|
615
|
-
pwd -P > "$TINKER_BASH_CWD_FILE"
|
|
616
|
-
exit "$exit_code"
|
|
617
|
-
`;
|
package/src/tools/bash.ts
CHANGED
|
@@ -19,6 +19,7 @@ type BashArgs = {
|
|
|
19
19
|
timeout?: number;
|
|
20
20
|
description?: string;
|
|
21
21
|
run_in_background?: boolean;
|
|
22
|
+
tty?: boolean;
|
|
22
23
|
};
|
|
23
24
|
|
|
24
25
|
export type BashToolOptions = {
|
|
@@ -66,6 +67,11 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
66
67
|
type: "boolean",
|
|
67
68
|
description: "Run the command in the background and return immediately.",
|
|
68
69
|
},
|
|
70
|
+
tty: {
|
|
71
|
+
type: "boolean",
|
|
72
|
+
description:
|
|
73
|
+
"Run the command in a pseudo-terminal so it can receive interactive input.",
|
|
74
|
+
},
|
|
69
75
|
},
|
|
70
76
|
required: ["command"],
|
|
71
77
|
},
|
|
@@ -87,6 +93,7 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
87
93
|
outputLines: 0,
|
|
88
94
|
preview: "",
|
|
89
95
|
truncated: false,
|
|
96
|
+
tty: false,
|
|
90
97
|
error: parsed.error,
|
|
91
98
|
};
|
|
92
99
|
}
|
|
@@ -118,6 +125,7 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
118
125
|
outputLines: 0,
|
|
119
126
|
preview: "",
|
|
120
127
|
truncated: false,
|
|
128
|
+
tty: input.tty === true,
|
|
121
129
|
error: `Command denied: ${risk.reason}. ${suffix}`,
|
|
122
130
|
};
|
|
123
131
|
}
|
|
@@ -128,15 +136,19 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
128
136
|
command: input.command,
|
|
129
137
|
description: input.description ?? input.command,
|
|
130
138
|
origin: call,
|
|
139
|
+
tty: input.tty === true,
|
|
131
140
|
});
|
|
132
141
|
|
|
133
142
|
if (input.run_in_background === true) {
|
|
134
143
|
// Starting and publishing an explicit background task is one commit
|
|
135
144
|
// boundary. Cancellation is observed after its result is recorded.
|
|
136
145
|
await options.taskManager.markBackgrounded(task.taskId, "requested");
|
|
137
|
-
const inspection =
|
|
146
|
+
const inspection = await requireTaskOutputInspection(
|
|
147
|
+
options.taskManager,
|
|
148
|
+
task.taskId,
|
|
149
|
+
);
|
|
138
150
|
if (inspection.task.status !== "running") {
|
|
139
|
-
return buildCompletedResult(inspection
|
|
151
|
+
return buildCompletedResult(inspection);
|
|
140
152
|
}
|
|
141
153
|
|
|
142
154
|
return buildRunningResult({
|
|
@@ -155,9 +167,12 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
155
167
|
// Timeout wins ownership. Marking the task backgrounded and returning
|
|
156
168
|
// its task ID is an uninterrupted commit boundary.
|
|
157
169
|
await options.taskManager.markBackgrounded(task.taskId, "foreground_timeout");
|
|
158
|
-
const inspection =
|
|
170
|
+
const inspection = await requireTaskOutputInspection(
|
|
171
|
+
options.taskManager,
|
|
172
|
+
task.taskId,
|
|
173
|
+
);
|
|
159
174
|
if (inspection.task.status !== "running") {
|
|
160
|
-
return buildCompletedResult(inspection
|
|
175
|
+
return buildCompletedResult(inspection);
|
|
161
176
|
}
|
|
162
177
|
|
|
163
178
|
return buildRunningResult({
|
|
@@ -169,11 +184,14 @@ export function createBashToolExecutor(options: BashToolOptions): ToolExecutor {
|
|
|
169
184
|
});
|
|
170
185
|
}
|
|
171
186
|
|
|
172
|
-
const inspection =
|
|
173
|
-
|
|
187
|
+
const inspection = await requireTaskOutputInspection(
|
|
188
|
+
options.taskManager,
|
|
189
|
+
task.taskId,
|
|
190
|
+
);
|
|
191
|
+
const raw = await buildCompletedResult(inspection);
|
|
174
192
|
updateCwdStateAfterForegroundCommand({
|
|
175
193
|
raw,
|
|
176
|
-
task:
|
|
194
|
+
task: inspection.task,
|
|
177
195
|
cwdState: options.cwdState,
|
|
178
196
|
workspaceRoot: options.workspaceRoot,
|
|
179
197
|
});
|
|
@@ -210,6 +228,10 @@ export function parseBashArgs(
|
|
|
210
228
|
return { ok: false, error: "Bash.run_in_background must be a boolean." };
|
|
211
229
|
}
|
|
212
230
|
|
|
231
|
+
if (args.tty !== undefined && typeof args.tty !== "boolean") {
|
|
232
|
+
return { ok: false, error: "Bash.tty must be a boolean." };
|
|
233
|
+
}
|
|
234
|
+
|
|
213
235
|
return {
|
|
214
236
|
ok: true,
|
|
215
237
|
value: {
|
|
@@ -220,6 +242,7 @@ export function parseBashArgs(
|
|
|
220
242
|
? undefined
|
|
221
243
|
: args.description,
|
|
222
244
|
run_in_background: args.run_in_background,
|
|
245
|
+
tty: args.tty,
|
|
223
246
|
},
|
|
224
247
|
};
|
|
225
248
|
}
|
|
@@ -325,18 +348,22 @@ function buildRunningResult(input: {
|
|
|
325
348
|
timeoutMs: input.timeoutMs,
|
|
326
349
|
backgrounded: input.backgrounded,
|
|
327
350
|
backgroundedDueToTimeout: input.backgroundedDueToTimeout,
|
|
351
|
+
tty: task.tty,
|
|
352
|
+
screenRows: input.inspection.screenRows,
|
|
353
|
+
screenColumns: input.inspection.screenColumns,
|
|
354
|
+
screen: input.inspection.screen,
|
|
328
355
|
};
|
|
329
356
|
}
|
|
330
357
|
|
|
331
358
|
async function buildCompletedResult(
|
|
332
|
-
|
|
333
|
-
fallbackOutput: TaskOutputSnapshot,
|
|
359
|
+
inspection: ShellTaskInspection,
|
|
334
360
|
): Promise<BashRawResult> {
|
|
361
|
+
const { task } = inspection;
|
|
335
362
|
if (task.status === "running" || task.status === "stopping") {
|
|
336
363
|
throw new Error(`Bash task ${task.taskId} completed with status=${task.status}.`);
|
|
337
364
|
}
|
|
338
365
|
|
|
339
|
-
const snapshot = await snapshotCompletedOutput(task,
|
|
366
|
+
const snapshot = await snapshotCompletedOutput(task, inspection.output);
|
|
340
367
|
const interpretation = interpretCommandResult({
|
|
341
368
|
command: task.command,
|
|
342
369
|
exitCode: task.exitCode,
|
|
@@ -359,6 +386,10 @@ async function buildCompletedResult(
|
|
|
359
386
|
truncated: snapshot.truncated,
|
|
360
387
|
omittedLines: snapshot.omittedLines,
|
|
361
388
|
returnCodeInterpretation: interpretation.interpretation,
|
|
389
|
+
tty: task.tty,
|
|
390
|
+
screenRows: inspection.screenRows,
|
|
391
|
+
screenColumns: inspection.screenColumns,
|
|
392
|
+
screen: inspection.screen,
|
|
362
393
|
error: task.error,
|
|
363
394
|
};
|
|
364
395
|
}
|
|
@@ -390,11 +421,11 @@ function updateCwdStateAfterForegroundCommand(input: {
|
|
|
390
421
|
}
|
|
391
422
|
}
|
|
392
423
|
|
|
393
|
-
function
|
|
424
|
+
async function requireTaskOutputInspection(
|
|
394
425
|
taskManager: ShellTaskManager,
|
|
395
426
|
taskId: string,
|
|
396
|
-
): ShellTaskInspection {
|
|
397
|
-
const inspection = taskManager.
|
|
427
|
+
): Promise<ShellTaskInspection> {
|
|
428
|
+
const inspection = await taskManager.inspectTaskOutput(taskId);
|
|
398
429
|
if (inspection === undefined) {
|
|
399
430
|
throw new Error(`Bash task disappeared from task manager: ${taskId}`);
|
|
400
431
|
}
|
package/src/tools/registry.ts
CHANGED
|
@@ -8,6 +8,7 @@ import { createGrepToolExecutor } from "./grep";
|
|
|
8
8
|
import { createReadToolExecutor } from "./read";
|
|
9
9
|
import { createRecallToolExecutor } from "./recall";
|
|
10
10
|
import { createTaskListToolExecutor } from "./task-list";
|
|
11
|
+
import { createTaskInputToolExecutor } from "./task-input";
|
|
11
12
|
import { createTaskOutputToolExecutor } from "./task-output-tool";
|
|
12
13
|
import { createTaskStopToolExecutor } from "./task-stop";
|
|
13
14
|
import { createWebFetchToolExecutor } from "./web-fetch";
|
|
@@ -256,6 +257,7 @@ export function createDefaultTooling(options: {
|
|
|
256
257
|
);
|
|
257
258
|
registry.register(createTaskListToolExecutor({ taskManager }));
|
|
258
259
|
registry.register(createTaskOutputToolExecutor({ taskManager }));
|
|
260
|
+
registry.register(createTaskInputToolExecutor({ taskManager }));
|
|
259
261
|
registry.register(createTaskStopToolExecutor({ taskManager }));
|
|
260
262
|
|
|
261
263
|
const exaApiKey = options.exaApiKey ?? toolingConfig.exaApiKey;
|