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/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,26 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
5
5
|
|
|
6
6
|
## [Unreleased]
|
|
7
7
|
|
|
8
|
+
## [1.7.0] - 2026-08-01
|
|
9
|
+
|
|
10
|
+
### Added
|
|
11
|
+
|
|
12
|
+
- Run interactive terminal programs through PTY-backed Bash tasks, send exact
|
|
13
|
+
keystrokes with `TaskInput`, and inspect their current terminal screen with
|
|
14
|
+
`TaskOutput`.
|
|
15
|
+
- Search all resumable sessions from the `/resume` picker by text from their
|
|
16
|
+
first user prompt, including older sessions beyond the 20 most recent.
|
|
17
|
+
|
|
18
|
+
### Changed
|
|
19
|
+
|
|
20
|
+
- Keep the live background-task panel compact by showing at most two tasks and
|
|
21
|
+
label PTY-backed tasks explicitly.
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Complete PTY tasks reliably after subprocess exit on Linux, including the EIO
|
|
26
|
+
signal reported when the terminal closes.
|
|
27
|
+
|
|
8
28
|
## [1.6.0] - 2026-08-01
|
|
9
29
|
|
|
10
30
|
### Added
|
|
@@ -129,7 +149,8 @@ All notable user-facing changes to Tinker are documented here. The project follo
|
|
|
129
149
|
- First formal npm release under the `tinker-agent` package name with the `tinker`
|
|
130
150
|
executable.
|
|
131
151
|
|
|
132
|
-
[Unreleased]: https://github.com/ishowshao/tinker/compare/v1.
|
|
152
|
+
[Unreleased]: https://github.com/ishowshao/tinker/compare/v1.7.0...HEAD
|
|
153
|
+
[1.7.0]: https://github.com/ishowshao/tinker/releases/tag/v1.7.0
|
|
133
154
|
[1.6.0]: https://github.com/ishowshao/tinker/releases/tag/v1.6.0
|
|
134
155
|
[1.5.1]: https://github.com/ishowshao/tinker/releases/tag/v1.5.1
|
|
135
156
|
[1.5.0]: https://github.com/ishowshao/tinker/releases/tag/v1.5.0
|
package/README.md
CHANGED
|
@@ -12,8 +12,8 @@ Built with [Bun](https://bun.sh) + TypeScript ESM, powered by [Ink](https://gith
|
|
|
12
12
|
- `Glob` / `Grep` — Find and search files by pattern or content
|
|
13
13
|
- `Read` / `Write` / `Edit` — File I/O with content hashing and concurrent-modification protection
|
|
14
14
|
- `Delete` — Delete one existing regular file without directory or symlink support
|
|
15
|
-
- `Bash` — Run
|
|
16
|
-
- `TaskList` / `TaskOutput` / `TaskStop` —
|
|
15
|
+
- `Bash` — Run foreground, background, and PTY shell commands with per-task working directories
|
|
16
|
+
- `TaskList` / `TaskOutput` / `TaskInput` / `TaskStop` — Inspect, interact with, and stop long-running shell tasks
|
|
17
17
|
- `WebSearch` — Search the web via Exa API
|
|
18
18
|
- `WebFetch` — Fetch and refine web page content (local, browser, or Exa backend)
|
|
19
19
|
- `Recall` — Search or retrieve model-visible history from the current session
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tinker-agent",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.7.0",
|
|
4
4
|
"description": "A personal coding agent with an interactive TUI and one-shot CLI.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"type": "module",
|
|
@@ -80,6 +80,8 @@
|
|
|
80
80
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
81
81
|
"@mozilla/readability": "^0.6.0",
|
|
82
82
|
"@vscode/ripgrep": "1.18.0",
|
|
83
|
+
"@xterm/addon-unicode11": "0.9.0",
|
|
84
|
+
"@xterm/headless": "6.0.0",
|
|
83
85
|
"ansi-escapes": "^7.3.0",
|
|
84
86
|
"bun": "1.3.14",
|
|
85
87
|
"clipboardy": "^5.3.1",
|
|
@@ -105,8 +107,6 @@
|
|
|
105
107
|
"@types/chrome": "^0.2.2",
|
|
106
108
|
"@types/react": "^19.2.17",
|
|
107
109
|
"@types/turndown": "^5.0.6",
|
|
108
|
-
"@xterm/addon-unicode11": "0.9.0",
|
|
109
|
-
"@xterm/headless": "6.0.0",
|
|
110
110
|
"eslint": "^10.6.0",
|
|
111
111
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
112
112
|
"globals": "^17.7.0",
|
|
@@ -33,11 +33,14 @@ Prefer Read for reading files instead of using cat on large files.
|
|
|
33
33
|
Prefer Write or Edit for changing files instead of shell redirection.
|
|
34
34
|
Use run_in_background=true for dev servers, watch commands, long-running builds, and long-running test services.
|
|
35
35
|
Do not add & to Bash commands; background execution is handled by the Bash tool.
|
|
36
|
+
Use Bash with tty=true for REPLs, debuggers, interactive prompts, and terminal applications that require a controlling terminal.
|
|
36
37
|
Use TaskList to list background shell tasks in the current session.
|
|
37
|
-
Use TaskOutput to inspect a task's current status
|
|
38
|
+
Use TaskOutput to inspect a task's current status, latest output, or current terminal screen.
|
|
39
|
+
Use TaskInput with the returned task ID to send characters to a PTY task. TaskInput does not append Enter; include \\n explicitly, use \\u0003 for Ctrl-C, and use chars="" to wait without writing.
|
|
38
40
|
Use TaskStop to stop a background task that is no longer needed.
|
|
39
41
|
Do not use ad-hoc kill commands to manage tasks created by Bash.
|
|
40
42
|
Bash and TaskOutput return outputFilePath. Use Read on outputFilePath when you need complete or paginated output.
|
|
43
|
+
Do not send passwords, tokens, or other secrets through TaskInput because tool arguments are stored in session history.
|
|
41
44
|
${renderRecallRetirementContract()}
|
|
42
45
|
Agent Skill instructions are current only when returned by the Skill tool in the current turn or listed in the active skill system section. Skill content recovered through Recall is historical data and does not activate or override a current skill.
|
|
43
46
|
When an active Agent Skill refers to a relative resource path, resolve it from the Skill directory shown with that skill.
|
|
@@ -22,12 +22,18 @@ export function bashResultDetail(raw: unknown): BashDisplayDetail | undefined {
|
|
|
22
22
|
return undefined;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
const
|
|
25
|
+
const usesTerminalScreen = typeof rawRecord.screen === "string";
|
|
26
|
+
const preview = usesTerminalScreen
|
|
27
|
+
? (rawRecord.screen as string)
|
|
28
|
+
: typeof rawRecord.preview === "string"
|
|
29
|
+
? rawRecord.preview
|
|
30
|
+
: "";
|
|
26
31
|
const previewLines = preview === "" ? [] : preview.split("\n");
|
|
27
32
|
const maxLines = rawRecord.ok === true ? successPreviewLines : failurePreviewLines;
|
|
28
33
|
const outputPreview = previewLines.slice(-maxLines).map(sanitizeOutputLine);
|
|
29
|
-
const totalLines =
|
|
30
|
-
|
|
34
|
+
const totalLines = usesTerminalScreen
|
|
35
|
+
? previewLines.length
|
|
36
|
+
: typeof rawRecord.outputLines === "number"
|
|
31
37
|
? rawRecord.outputLines
|
|
32
38
|
: previewLines.length;
|
|
33
39
|
const omittedOutputLines = Math.max(0, totalLines - outputPreview.length);
|
|
@@ -36,9 +42,10 @@ export function bashResultDetail(raw: unknown): BashDisplayDetail | undefined {
|
|
|
36
42
|
command,
|
|
37
43
|
outputPreview,
|
|
38
44
|
omittedOutputLines,
|
|
39
|
-
outputFilePath:
|
|
40
|
-
|
|
41
|
-
nonEmptyString(
|
|
45
|
+
outputFilePath: usesTerminalScreen
|
|
46
|
+
? undefined
|
|
47
|
+
: (nonEmptyString(rawRecord.outputFilePath) ??
|
|
48
|
+
nonEmptyString(taskRecord.outputFilePath)),
|
|
42
49
|
};
|
|
43
50
|
}
|
|
44
51
|
|
|
@@ -179,7 +179,11 @@ function toolCallSummary(call: ToolCall): string {
|
|
|
179
179
|
.join("\n");
|
|
180
180
|
}
|
|
181
181
|
|
|
182
|
-
if (
|
|
182
|
+
if (
|
|
183
|
+
call.name === "TaskOutput" ||
|
|
184
|
+
call.name === "TaskInput" ||
|
|
185
|
+
call.name === "TaskStop"
|
|
186
|
+
) {
|
|
183
187
|
const taskId = stringProperty(args, "task_id");
|
|
184
188
|
return [
|
|
185
189
|
`Call ID: ${call.toolCallId}`,
|
|
@@ -202,6 +202,7 @@ function formatToolRawResult(call: ToolCall, raw: ToolRawResult): string[] {
|
|
|
202
202
|
return optionalLine(formatDiff(call, raw));
|
|
203
203
|
case "bash":
|
|
204
204
|
case "task_output":
|
|
205
|
+
case "task_input":
|
|
205
206
|
return optionalLine(formatBashResult(call, raw));
|
|
206
207
|
case "task_list":
|
|
207
208
|
case "task_stop":
|
|
@@ -278,7 +279,7 @@ function formatDiff(call: ToolCall, raw: ToolRawResult): string | undefined {
|
|
|
278
279
|
}
|
|
279
280
|
|
|
280
281
|
function formatBashResult(_call: ToolCall, raw: ToolRawResult): string | undefined {
|
|
281
|
-
if (raw.kind !== "bash" && raw.kind !== "task_output") {
|
|
282
|
+
if (raw.kind !== "bash" && raw.kind !== "task_output" && raw.kind !== "task_input") {
|
|
282
283
|
return undefined;
|
|
283
284
|
}
|
|
284
285
|
|
|
@@ -348,7 +349,11 @@ function formatToolLine(prefix: string, call: ToolCall): string {
|
|
|
348
349
|
return `${prefix} name=${call.name}\n`;
|
|
349
350
|
}
|
|
350
351
|
|
|
351
|
-
if (
|
|
352
|
+
if (
|
|
353
|
+
call.name === "TaskOutput" ||
|
|
354
|
+
call.name === "TaskInput" ||
|
|
355
|
+
call.name === "TaskStop"
|
|
356
|
+
) {
|
|
352
357
|
const taskId = toolTaskId(call);
|
|
353
358
|
return `${prefix} name=${call.name}${taskId === undefined ? "" : ` task=${taskId}`}\n`;
|
|
354
359
|
}
|
|
@@ -247,6 +247,9 @@ export class FakeModelClient implements ModelClient {
|
|
|
247
247
|
if (this.mode === "pty-background-task") {
|
|
248
248
|
return this.ptyBackgroundTask(input, prepared, options);
|
|
249
249
|
}
|
|
250
|
+
if (this.mode === "pty-interactive-terminal") {
|
|
251
|
+
return this.ptyInteractiveTerminal(input, prepared, options);
|
|
252
|
+
}
|
|
250
253
|
if (this.mode === "pty-resume") {
|
|
251
254
|
return this.ptyResume(input, prepared, options);
|
|
252
255
|
}
|
|
@@ -658,6 +661,87 @@ export class FakeModelClient implements ModelClient {
|
|
|
658
661
|
return textOutput(prepared, "PTY_BACKGROUND_STOPPED");
|
|
659
662
|
}
|
|
660
663
|
|
|
664
|
+
private ptyInteractiveTerminal(
|
|
665
|
+
input: ModelRequestInput,
|
|
666
|
+
prepared: PreparedModelRequest,
|
|
667
|
+
options: ModelRequestOptions,
|
|
668
|
+
): ModelRequestOutput {
|
|
669
|
+
requireTools(input, ["Bash", "TaskOutput", "TaskInput"]);
|
|
670
|
+
const prompt = lastUserMessage(input.messages);
|
|
671
|
+
if (prompt === "PTY_INTERACTIVE_FOLLOWUP") {
|
|
672
|
+
requireMessage(input.messages, "assistant", "PTY_INTERACTIVE_DONE");
|
|
673
|
+
return textOutput(prepared, "PTY_INTERACTIVE_FOLLOWUP_DONE");
|
|
674
|
+
}
|
|
675
|
+
if (prompt !== "PTY_INTERACTIVE_TERMINAL" && prompt !== "PTY_INTERACTIVE_QUIT") {
|
|
676
|
+
throw new Error(
|
|
677
|
+
`Unexpected pty-interactive-terminal prompt: ${JSON.stringify(prompt)}.`,
|
|
678
|
+
);
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
const tools = toolMessagesAfterLastUser(input.messages);
|
|
682
|
+
const bash = tools.find((message) => message.name === "Bash");
|
|
683
|
+
if (bash === undefined) {
|
|
684
|
+
return toolCallOutput(prepared, options, "Bash", {
|
|
685
|
+
command: "python3 -q",
|
|
686
|
+
description: "Start interactive Python fixture",
|
|
687
|
+
tty: true,
|
|
688
|
+
timeout: 25,
|
|
689
|
+
});
|
|
690
|
+
}
|
|
691
|
+
if (!bash.content.includes("taskId=") || !bash.content.includes("tty=true")) {
|
|
692
|
+
throw new Error("PTY Bash task did not return an interactive task ID.");
|
|
693
|
+
}
|
|
694
|
+
const taskId = requireObservationValue(bash.content, "taskId");
|
|
695
|
+
|
|
696
|
+
const outputs = tools.filter((message) => message.name === "TaskOutput");
|
|
697
|
+
const output = outputs.at(-1);
|
|
698
|
+
if (output === undefined || !output.content.includes(">>>")) {
|
|
699
|
+
if (outputs.length >= 20) {
|
|
700
|
+
throw new Error("Interactive Python fixture did not show its prompt.");
|
|
701
|
+
}
|
|
702
|
+
return toolCallOutput(prepared, options, "TaskOutput", {
|
|
703
|
+
task_id: taskId,
|
|
704
|
+
});
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
const inputs = tools.filter((message) => message.name === "TaskInput");
|
|
708
|
+
if (inputs.length === 0) {
|
|
709
|
+
return toolCallOutput(prepared, options, "TaskInput", {
|
|
710
|
+
task_id: taskId,
|
|
711
|
+
chars:
|
|
712
|
+
prompt === "PTY_INTERACTIVE_QUIT"
|
|
713
|
+
? "import os; print('PTY_INTERACTIVE_PID=' + str(os.getpid()))\n"
|
|
714
|
+
: "print(6 * 7)\n",
|
|
715
|
+
wait_ms: 250,
|
|
716
|
+
});
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
const latestInput = inputs.at(-1);
|
|
720
|
+
const expected = prompt === "PTY_INTERACTIVE_QUIT" ? "PTY_INTERACTIVE_PID=" : "42";
|
|
721
|
+
if (!latestInput?.content.includes(expected)) {
|
|
722
|
+
if (inputs.length >= 20) {
|
|
723
|
+
throw new Error(`Interactive Python fixture did not show ${expected}.`);
|
|
724
|
+
}
|
|
725
|
+
return toolCallOutput(prepared, options, "TaskInput", {
|
|
726
|
+
task_id: taskId,
|
|
727
|
+
chars: "",
|
|
728
|
+
wait_ms: 250,
|
|
729
|
+
});
|
|
730
|
+
}
|
|
731
|
+
|
|
732
|
+
if (prompt === "PTY_INTERACTIVE_QUIT") {
|
|
733
|
+
return textOutput(prepared, "PTY_INTERACTIVE_RUNNING");
|
|
734
|
+
}
|
|
735
|
+
if (!inputs.some((message) => message.content.includes("status=completed"))) {
|
|
736
|
+
return toolCallOutput(prepared, options, "TaskInput", {
|
|
737
|
+
task_id: taskId,
|
|
738
|
+
chars: "exit()\n",
|
|
739
|
+
wait_ms: 500,
|
|
740
|
+
});
|
|
741
|
+
}
|
|
742
|
+
return textOutput(prepared, "PTY_INTERACTIVE_DONE");
|
|
743
|
+
}
|
|
744
|
+
|
|
661
745
|
private ptyResume(
|
|
662
746
|
input: ModelRequestInput,
|
|
663
747
|
prepared: PreparedModelRequest,
|
|
@@ -11,6 +11,7 @@ import type {
|
|
|
11
11
|
ReadFileRawResult,
|
|
12
12
|
RecallRawResult,
|
|
13
13
|
SkillRawResult,
|
|
14
|
+
TaskInputRawResult,
|
|
14
15
|
TaskListRawResult,
|
|
15
16
|
TaskOutputRawResult,
|
|
16
17
|
TaskStopRawResult,
|
|
@@ -51,6 +52,8 @@ export class ObservationBuilder {
|
|
|
51
52
|
return { content: renderTaskListObservation(input.raw) };
|
|
52
53
|
case "task_output":
|
|
53
54
|
return { content: renderTaskOutputObservation(input.raw) };
|
|
55
|
+
case "task_input":
|
|
56
|
+
return { content: renderTaskInputObservation(input.raw) };
|
|
54
57
|
case "task_stop":
|
|
55
58
|
return { content: renderTaskStopObservation(input.raw) };
|
|
56
59
|
case "web_search":
|
|
@@ -331,8 +334,11 @@ function renderBashObservation(raw: BashRawResult): string {
|
|
|
331
334
|
`timeoutMs=${raw.timeoutMs ?? 0}`,
|
|
332
335
|
`command=${raw.command}`,
|
|
333
336
|
`cwd=${raw.cwd}`,
|
|
337
|
+
`tty=${raw.tty}`,
|
|
334
338
|
`outputFilePath=${raw.outputFilePath}`,
|
|
335
|
-
|
|
339
|
+
raw.tty
|
|
340
|
+
? "Use TaskOutput to inspect the current terminal screen and TaskInput to interact."
|
|
341
|
+
: "Use TaskOutput to inspect current output.",
|
|
336
342
|
].join("\n");
|
|
337
343
|
}
|
|
338
344
|
|
|
@@ -342,8 +348,11 @@ function renderBashObservation(raw: BashRawResult): string {
|
|
|
342
348
|
`taskId=${raw.taskId}`,
|
|
343
349
|
`command=${raw.command}`,
|
|
344
350
|
`cwd=${raw.cwd}`,
|
|
351
|
+
`tty=${raw.tty}`,
|
|
345
352
|
`outputFilePath=${raw.outputFilePath}`,
|
|
346
|
-
|
|
353
|
+
raw.tty
|
|
354
|
+
? "Use TaskOutput to inspect the current terminal screen and TaskInput to interact."
|
|
355
|
+
: "Use TaskOutput to inspect current output.",
|
|
347
356
|
].join("\n");
|
|
348
357
|
}
|
|
349
358
|
|
|
@@ -353,6 +362,7 @@ function renderBashObservation(raw: BashRawResult): string {
|
|
|
353
362
|
`exitCode=${raw.exitCode ?? "null"}`,
|
|
354
363
|
`status=${raw.status}`,
|
|
355
364
|
`cwd=${raw.cwd}`,
|
|
365
|
+
`tty=${raw.tty}`,
|
|
356
366
|
`outputFilePath=${raw.outputFilePath}`,
|
|
357
367
|
`outputBytes=${raw.outputBytes}`,
|
|
358
368
|
`outputLines=${raw.outputLines}`,
|
|
@@ -362,8 +372,9 @@ function renderBashObservation(raw: BashRawResult): string {
|
|
|
362
372
|
? undefined
|
|
363
373
|
: `returnCodeInterpretation=${raw.returnCodeInterpretation}`,
|
|
364
374
|
raw.error === undefined ? undefined : `error=${raw.error}`,
|
|
365
|
-
|
|
366
|
-
raw.preview,
|
|
375
|
+
raw.tty ? `screen=${raw.screenColumns ?? 80}x${raw.screenRows ?? 24}` : undefined,
|
|
376
|
+
raw.tty ? "current screen:" : "preview:",
|
|
377
|
+
raw.tty ? (raw.screen ?? "") : raw.preview,
|
|
367
378
|
]
|
|
368
379
|
.filter((line): line is string => line !== undefined)
|
|
369
380
|
.join("\n");
|
|
@@ -387,23 +398,48 @@ function renderTaskOutputObservation(raw: TaskOutputRawResult): string {
|
|
|
387
398
|
return `TaskOutput failed for ${raw.taskId || "(unknown task ID)"}: ${raw.error ?? "Unknown error."}`;
|
|
388
399
|
}
|
|
389
400
|
|
|
401
|
+
const terminalScreen = raw.task.tty;
|
|
390
402
|
return [
|
|
391
403
|
"Task output retrieved.",
|
|
392
404
|
`taskId=${raw.taskId}`,
|
|
393
405
|
`status=${raw.task.status}`,
|
|
394
406
|
`command=${raw.task.command}`,
|
|
407
|
+
`tty=${terminalScreen}`,
|
|
395
408
|
`outputFilePath=${raw.outputFilePath}`,
|
|
396
409
|
`outputBytes=${raw.outputBytes ?? 0}`,
|
|
397
410
|
`outputLines=${raw.outputLines ?? 0}`,
|
|
398
411
|
`truncated=${raw.truncated ?? false}`,
|
|
399
412
|
raw.omittedLines === undefined ? undefined : `omittedLines=${raw.omittedLines}`,
|
|
400
|
-
|
|
401
|
-
|
|
413
|
+
terminalScreen
|
|
414
|
+
? `screen=${raw.screenColumns ?? 80}x${raw.screenRows ?? 24}`
|
|
415
|
+
: undefined,
|
|
416
|
+
terminalScreen ? "current screen:" : "preview:",
|
|
417
|
+
terminalScreen ? (raw.screen ?? "") : (raw.preview ?? ""),
|
|
402
418
|
]
|
|
403
419
|
.filter((line): line is string => line !== undefined)
|
|
404
420
|
.join("\n");
|
|
405
421
|
}
|
|
406
422
|
|
|
423
|
+
function renderTaskInputObservation(raw: TaskInputRawResult): string {
|
|
424
|
+
if (!raw.ok) {
|
|
425
|
+
return `TaskInput failed for ${raw.taskId || "(unknown task ID)"}: ${raw.error}`;
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
return [
|
|
429
|
+
"Terminal input sent.",
|
|
430
|
+
`taskId=${raw.taskId}`,
|
|
431
|
+
`status=${raw.status}`,
|
|
432
|
+
`writtenBytes=${raw.writtenBytes}`,
|
|
433
|
+
`waitedMs=${raw.waitedMs}`,
|
|
434
|
+
`screen=${raw.screenColumns}x${raw.screenRows}`,
|
|
435
|
+
`outputFilePath=${raw.outputFilePath}`,
|
|
436
|
+
`outputBytes=${raw.outputBytes}`,
|
|
437
|
+
`outputLines=${raw.outputLines}`,
|
|
438
|
+
"current screen:",
|
|
439
|
+
raw.screen,
|
|
440
|
+
].join("\n");
|
|
441
|
+
}
|
|
442
|
+
|
|
407
443
|
function renderTaskStopObservation(raw: TaskStopRawResult): string {
|
|
408
444
|
if (!raw.ok || raw.task === undefined) {
|
|
409
445
|
return `TaskStop failed for ${raw.taskId || "(unknown task ID)"}: ${raw.error ?? "Unknown error."}`;
|
|
@@ -428,6 +464,7 @@ function renderTaskSummary(task: TaskListRawResult["tasks"][number]): string {
|
|
|
428
464
|
`taskId=${task.taskId}`,
|
|
429
465
|
`description=${task.description}`,
|
|
430
466
|
`status=${task.status}`,
|
|
467
|
+
`tty=${task.tty}`,
|
|
431
468
|
`startedAt=${task.startedAt}`,
|
|
432
469
|
task.endedAt === undefined ? undefined : `endedAt=${task.endedAt}`,
|
|
433
470
|
task.exitCode === undefined ? undefined : `exitCode=${task.exitCode}`,
|
|
@@ -34,6 +34,15 @@ export class SessionCatalog {
|
|
|
34
34
|
}
|
|
35
35
|
|
|
36
36
|
async list(currentSessionId?: SessionId): Promise<readonly SessionSummary[]> {
|
|
37
|
+
const summaries = await this.scan(currentSessionId);
|
|
38
|
+
return Object.freeze(summaries.slice(0, this.input.limit ?? 20));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async listAll(currentSessionId?: SessionId): Promise<readonly SessionSummary[]> {
|
|
42
|
+
return Object.freeze(await this.scan(currentSessionId));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
private async scan(currentSessionId?: SessionId): Promise<SessionSummary[]> {
|
|
37
46
|
const workspaceRoot = await this.workspaceRootPromise;
|
|
38
47
|
const sessionsRoot = path.join(workspaceRoot, ".tinker", "sessions");
|
|
39
48
|
let entries;
|
|
@@ -63,17 +72,14 @@ export class SessionCatalog {
|
|
|
63
72
|
);
|
|
64
73
|
}
|
|
65
74
|
|
|
66
|
-
return
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt))
|
|
75
|
-
.slice(0, this.input.limit ?? 20),
|
|
76
|
-
);
|
|
75
|
+
return summaries
|
|
76
|
+
.filter(
|
|
77
|
+
(summary) =>
|
|
78
|
+
summary.turnCount > 0 ||
|
|
79
|
+
summary.status === "incomplete" ||
|
|
80
|
+
summary.status === "unavailable",
|
|
81
|
+
)
|
|
82
|
+
.sort((left, right) => right.updatedAt.localeCompare(left.updatedAt));
|
|
77
83
|
}
|
|
78
84
|
|
|
79
85
|
async get(
|