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
|
@@ -5,7 +5,7 @@ export type BackgroundTasksProps = {
|
|
|
5
5
|
tasks: readonly ShellTaskSnapshot[];
|
|
6
6
|
};
|
|
7
7
|
|
|
8
|
-
const MAX_VISIBLE_TASKS =
|
|
8
|
+
const MAX_VISIBLE_TASKS = 2;
|
|
9
9
|
|
|
10
10
|
export function BackgroundTasks(props: BackgroundTasksProps) {
|
|
11
11
|
if (props.tasks.length === 0) {
|
|
@@ -26,7 +26,8 @@ export function BackgroundTasks(props: BackgroundTasksProps) {
|
|
|
26
26
|
{visibleTasks.map((task) => (
|
|
27
27
|
<Box key={task.taskId} flexDirection="column">
|
|
28
28
|
<Text color={colorForStatus(task.status)}>
|
|
29
|
-
{symbolForStatus(task.status)} {task.status}
|
|
29
|
+
{symbolForStatus(task.status)} {task.status}
|
|
30
|
+
{task.tty ? " tty" : ""} {taskDescription(task)}
|
|
30
31
|
{taskResult(task) === undefined ? "" : ` ${taskResult(task)}`}
|
|
31
32
|
</Text>
|
|
32
33
|
<Text dimColor>{taskTiming(task)}</Text>
|
|
@@ -1,9 +1,24 @@
|
|
|
1
|
-
import { Box, Text, useInput, useWindowSize } from "ink";
|
|
2
|
-
import {
|
|
1
|
+
import { Box, Text, useInput, usePaste, useWindowSize } from "ink";
|
|
2
|
+
import { useReducer, useRef } from "react";
|
|
3
3
|
import type { SessionSummary } from "../../session/session-catalog";
|
|
4
|
+
import {
|
|
5
|
+
backspace,
|
|
6
|
+
createLineEditorState,
|
|
7
|
+
deleteForward,
|
|
8
|
+
deleteToLineStart,
|
|
9
|
+
insert,
|
|
10
|
+
moveLeft,
|
|
11
|
+
moveRight,
|
|
12
|
+
moveToLineEnd,
|
|
13
|
+
moveToLineStart,
|
|
14
|
+
splitAtCursor,
|
|
15
|
+
type LineEditorState,
|
|
16
|
+
} from "../line-editor";
|
|
4
17
|
|
|
5
18
|
const SESSION_ROWS = 3;
|
|
6
|
-
const
|
|
19
|
+
const BROWSE_CHROME_ROWS = 3;
|
|
20
|
+
const SEARCH_CHROME_ROWS = 4;
|
|
21
|
+
const MAX_DISPLAYED_SESSIONS = 20;
|
|
7
22
|
|
|
8
23
|
export type ResumeSessionPickerProps = {
|
|
9
24
|
sessions: readonly SessionSummary[];
|
|
@@ -16,11 +31,20 @@ export type ResumeSessionPickerProps = {
|
|
|
16
31
|
onSelect: (session: SessionSummary) => void;
|
|
17
32
|
};
|
|
18
33
|
|
|
19
|
-
type
|
|
34
|
+
type PickerState = {
|
|
35
|
+
mode: "browse" | "search";
|
|
36
|
+
editor: LineEditorState;
|
|
20
37
|
selectedIndex: number;
|
|
21
38
|
windowStart: number;
|
|
22
39
|
};
|
|
23
40
|
|
|
41
|
+
type PickerAction =
|
|
42
|
+
| { type: "enter_search" }
|
|
43
|
+
| { type: "clear_search" }
|
|
44
|
+
| { type: "move_selection"; direction: -1 | 1 }
|
|
45
|
+
| { type: "move_editor_cursor"; update: (editor: LineEditorState) => LineEditorState }
|
|
46
|
+
| { type: "change_query"; update: (editor: LineEditorState) => LineEditorState };
|
|
47
|
+
|
|
24
48
|
export function ResumeSessionPicker(props: ResumeSessionPickerProps) {
|
|
25
49
|
if (props.sessions.length === 0) {
|
|
26
50
|
throw new Error("ResumeSessionPicker requires at least one session.");
|
|
@@ -32,37 +56,206 @@ export function ResumeSessionPicker(props: ResumeSessionPickerProps) {
|
|
|
32
56
|
function ResumeSessionPickerContent(props: ResumeSessionPickerProps) {
|
|
33
57
|
const windowSize = useWindowSize();
|
|
34
58
|
const rows = props.viewportRows ?? windowSize.rows - 1;
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
|
|
60
|
+
const displayedFor = (value: string): readonly SessionSummary[] => {
|
|
61
|
+
const nextCandidates =
|
|
62
|
+
normalizeSearchText(value) === ""
|
|
63
|
+
? props.sessions
|
|
64
|
+
: props.sessions.filter((session) => matchesSessionPreview(session, value));
|
|
65
|
+
return nextCandidates.slice(0, MAX_DISPLAYED_SESSIONS);
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
const visibleCountFor = (mode: PickerState["mode"], displayedCount: number) => {
|
|
69
|
+
const chromeRows = mode === "search" ? SEARCH_CHROME_ROWS : BROWSE_CHROME_ROWS;
|
|
70
|
+
return Math.min(
|
|
71
|
+
Math.max(displayedCount, 1),
|
|
72
|
+
Math.max(
|
|
73
|
+
1,
|
|
74
|
+
Math.floor(props.visibleItemCount ?? (rows - chromeRows) / SESSION_ROWS),
|
|
75
|
+
),
|
|
76
|
+
);
|
|
77
|
+
};
|
|
78
|
+
|
|
79
|
+
const reduce = (state: PickerState, action: PickerAction): PickerState => {
|
|
80
|
+
switch (action.type) {
|
|
81
|
+
case "enter_search": {
|
|
82
|
+
if (state.mode === "search") {
|
|
83
|
+
return state;
|
|
84
|
+
}
|
|
85
|
+
return { ...state, mode: "search" };
|
|
86
|
+
}
|
|
87
|
+
case "clear_search": {
|
|
88
|
+
const displayed = displayedFor("");
|
|
89
|
+
return {
|
|
90
|
+
mode: "browse",
|
|
91
|
+
editor: createLineEditorState(),
|
|
92
|
+
selectedIndex: initialSelectedIndex(displayed),
|
|
93
|
+
windowStart: 0,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
case "move_selection": {
|
|
97
|
+
const query = state.mode === "search" ? state.editor.value : "";
|
|
98
|
+
const displayed = displayedFor(query);
|
|
99
|
+
if (displayed.length === 0) {
|
|
100
|
+
return state;
|
|
101
|
+
}
|
|
102
|
+
const selectedIndex = clamp(
|
|
103
|
+
state.selectedIndex + action.direction,
|
|
104
|
+
0,
|
|
105
|
+
displayed.length - 1,
|
|
106
|
+
);
|
|
107
|
+
const windowStart = keepSelectionVisible(
|
|
108
|
+
state.windowStart,
|
|
109
|
+
selectedIndex,
|
|
110
|
+
visibleCountFor(state.mode, displayed.length),
|
|
111
|
+
displayed.length,
|
|
112
|
+
);
|
|
113
|
+
if (
|
|
114
|
+
selectedIndex === state.selectedIndex &&
|
|
115
|
+
windowStart === state.windowStart
|
|
116
|
+
) {
|
|
117
|
+
return state;
|
|
118
|
+
}
|
|
119
|
+
return { ...state, selectedIndex, windowStart };
|
|
120
|
+
}
|
|
121
|
+
case "move_editor_cursor": {
|
|
122
|
+
const editor = action.update(state.editor);
|
|
123
|
+
return editor === state.editor ? state : { ...state, editor };
|
|
124
|
+
}
|
|
125
|
+
case "change_query": {
|
|
126
|
+
const editor = action.update(state.editor);
|
|
127
|
+
if (editor === state.editor) {
|
|
128
|
+
return state;
|
|
129
|
+
}
|
|
130
|
+
const displayed = displayedFor(editor.value);
|
|
131
|
+
return {
|
|
132
|
+
mode: "search",
|
|
133
|
+
editor,
|
|
134
|
+
selectedIndex: displayed.length === 0 ? 0 : initialSelectedIndex(displayed),
|
|
135
|
+
windowStart: 0,
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
const [state, baseDispatch] = useReducer(reduce, undefined, () => ({
|
|
142
|
+
mode: "browse" as const,
|
|
143
|
+
editor: createLineEditorState(),
|
|
144
|
+
selectedIndex: initialSelectedIndex(
|
|
145
|
+
props.sessions.slice(0, MAX_DISPLAYED_SESSIONS),
|
|
40
146
|
),
|
|
41
|
-
);
|
|
42
|
-
const [position, setPosition] = useState<PickerPosition>(() => ({
|
|
43
|
-
selectedIndex: initialSelectedIndex(props.sessions),
|
|
44
147
|
windowStart: 0,
|
|
45
148
|
}));
|
|
149
|
+
// Input events can arrive faster than React re-renders. Every state change
|
|
150
|
+
// goes through dispatch, which keeps this ref in sync with the exact action
|
|
151
|
+
// fold, so handlers always read and reduce the latest state instead of a
|
|
152
|
+
// stale render closure.
|
|
153
|
+
const stateRef = useRef(state);
|
|
154
|
+
const dispatch = (action: PickerAction) => {
|
|
155
|
+
stateRef.current = reduce(stateRef.current, action);
|
|
156
|
+
baseDispatch(action);
|
|
157
|
+
};
|
|
158
|
+
|
|
159
|
+
const query = state.mode === "search" ? state.editor.value : "";
|
|
160
|
+
const searching = normalizeSearchText(query) !== "";
|
|
161
|
+
const candidates = searching
|
|
162
|
+
? props.sessions.filter((session) => matchesSessionPreview(session, query))
|
|
163
|
+
: props.sessions;
|
|
164
|
+
const matchCount = candidates.length;
|
|
165
|
+
const displayedSessions = candidates.slice(0, MAX_DISPLAYED_SESSIONS);
|
|
166
|
+
const visibleItemCount = visibleCountFor(state.mode, displayedSessions.length);
|
|
167
|
+
const selectedIndex = clamp(
|
|
168
|
+
state.selectedIndex,
|
|
169
|
+
0,
|
|
170
|
+
Math.max(displayedSessions.length - 1, 0),
|
|
171
|
+
);
|
|
46
172
|
const windowStart = keepSelectionVisible(
|
|
47
|
-
|
|
48
|
-
|
|
173
|
+
state.windowStart,
|
|
174
|
+
selectedIndex,
|
|
49
175
|
visibleItemCount,
|
|
50
|
-
|
|
176
|
+
displayedSessions.length,
|
|
51
177
|
);
|
|
52
|
-
const windowEnd = Math.min(
|
|
53
|
-
const selectedSession =
|
|
178
|
+
const windowEnd = Math.min(displayedSessions.length, windowStart + visibleItemCount);
|
|
179
|
+
const selectedSession = displayedSessions[selectedIndex];
|
|
180
|
+
|
|
181
|
+
const selectCurrent = () => {
|
|
182
|
+
const current = stateRef.current;
|
|
183
|
+
const currentQuery = current.mode === "search" ? current.editor.value : "";
|
|
184
|
+
const displayed = displayedFor(currentQuery);
|
|
185
|
+
const session =
|
|
186
|
+
displayed[clamp(current.selectedIndex, 0, Math.max(displayed.length - 1, 0))];
|
|
187
|
+
if (session !== undefined && isSessionSelectable(session)) {
|
|
188
|
+
props.onSelect(session);
|
|
189
|
+
}
|
|
190
|
+
};
|
|
54
191
|
|
|
55
192
|
useInput(
|
|
56
193
|
(input, key) => {
|
|
194
|
+
if (stateRef.current.mode === "search") {
|
|
195
|
+
if (key.escape) {
|
|
196
|
+
dispatch({ type: "clear_search" });
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (key.return) {
|
|
200
|
+
selectCurrent();
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
if (key.upArrow) {
|
|
204
|
+
dispatch({ type: "move_selection", direction: -1 });
|
|
205
|
+
return;
|
|
206
|
+
}
|
|
207
|
+
if (key.downArrow) {
|
|
208
|
+
dispatch({ type: "move_selection", direction: 1 });
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (key.leftArrow) {
|
|
212
|
+
dispatch({ type: "move_editor_cursor", update: moveLeft });
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (key.rightArrow) {
|
|
216
|
+
dispatch({ type: "move_editor_cursor", update: moveRight });
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (key.backspace) {
|
|
220
|
+
dispatch({ type: "change_query", update: backspace });
|
|
221
|
+
return;
|
|
222
|
+
}
|
|
223
|
+
if (key.delete) {
|
|
224
|
+
dispatch({ type: "change_query", update: deleteForward });
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
if (key.ctrl) {
|
|
228
|
+
if (input === "a") {
|
|
229
|
+
dispatch({ type: "move_editor_cursor", update: moveToLineStart });
|
|
230
|
+
} else if (input === "e") {
|
|
231
|
+
dispatch({ type: "move_editor_cursor", update: moveToLineEnd });
|
|
232
|
+
} else if (input === "u") {
|
|
233
|
+
dispatch({ type: "change_query", update: deleteToLineStart });
|
|
234
|
+
} else if (input === "d") {
|
|
235
|
+
dispatch({ type: "change_query", update: deleteForward });
|
|
236
|
+
}
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
if (key.meta || key.pageUp || key.pageDown || input === "") {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
dispatch({
|
|
243
|
+
type: "change_query",
|
|
244
|
+
update: (editor) => insert(editor, normalizeQueryInput(input)),
|
|
245
|
+
});
|
|
246
|
+
return;
|
|
247
|
+
}
|
|
248
|
+
|
|
57
249
|
if (key.escape) {
|
|
58
250
|
props.onCancel();
|
|
59
251
|
return;
|
|
60
252
|
}
|
|
61
|
-
|
|
62
253
|
if (key.return) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
254
|
+
selectCurrent();
|
|
255
|
+
return;
|
|
256
|
+
}
|
|
257
|
+
if (input === "/" && !key.ctrl && !key.meta) {
|
|
258
|
+
dispatch({ type: "enter_search" });
|
|
66
259
|
return;
|
|
67
260
|
}
|
|
68
261
|
|
|
@@ -72,29 +265,21 @@ function ResumeSessionPickerContent(props: ResumeSessionPickerProps) {
|
|
|
72
265
|
: key.downArrow || (input === "j" && !key.ctrl && !key.meta)
|
|
73
266
|
? 1
|
|
74
267
|
: 0;
|
|
75
|
-
if (direction
|
|
76
|
-
|
|
268
|
+
if (direction !== 0) {
|
|
269
|
+
dispatch({ type: "move_selection", direction });
|
|
77
270
|
}
|
|
271
|
+
},
|
|
272
|
+
{ isActive: props.isResuming !== true },
|
|
273
|
+
);
|
|
78
274
|
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
selectedIndex,
|
|
88
|
-
visibleItemCount,
|
|
89
|
-
props.sessions.length,
|
|
90
|
-
);
|
|
91
|
-
if (
|
|
92
|
-
selectedIndex === current.selectedIndex &&
|
|
93
|
-
nextWindowStart === current.windowStart
|
|
94
|
-
) {
|
|
95
|
-
return current;
|
|
96
|
-
}
|
|
97
|
-
return { selectedIndex, windowStart: nextWindowStart };
|
|
275
|
+
usePaste(
|
|
276
|
+
(text) => {
|
|
277
|
+
if (stateRef.current.mode !== "search") {
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
dispatch({
|
|
281
|
+
type: "change_query",
|
|
282
|
+
update: (editor) => insert(editor, normalizeQueryInput(text)),
|
|
98
283
|
});
|
|
99
284
|
},
|
|
100
285
|
{ isActive: props.isResuming !== true },
|
|
@@ -104,16 +289,19 @@ function ResumeSessionPickerContent(props: ResumeSessionPickerProps) {
|
|
|
104
289
|
return (
|
|
105
290
|
<Box flexDirection="column">
|
|
106
291
|
<Text bold>Resume session</Text>
|
|
292
|
+
{state.mode === "search" ? <SearchLine editor={state.editor} /> : null}
|
|
107
293
|
<Text dimColor>
|
|
108
294
|
{props.isResuming === true
|
|
109
295
|
? `Resuming ${shortSessionId(selectedSession?.sessionId ?? "")}`
|
|
110
|
-
:
|
|
296
|
+
: state.mode === "search"
|
|
297
|
+
? "↑/↓ to move · Enter to resume · Esc to clear search"
|
|
298
|
+
: "↑/↓ or j/k to move · / to search · Enter to resume · Esc to cancel"}
|
|
111
299
|
</Text>
|
|
112
|
-
{
|
|
300
|
+
{displayedSessions.slice(windowStart, windowEnd).map((session, offset) => (
|
|
113
301
|
<SessionOption
|
|
114
302
|
key={session.sessionId}
|
|
115
303
|
session={session}
|
|
116
|
-
isSelected={windowStart + offset ===
|
|
304
|
+
isSelected={windowStart + offset === selectedIndex}
|
|
117
305
|
now={now}
|
|
118
306
|
/>
|
|
119
307
|
))}
|
|
@@ -123,13 +311,31 @@ function ResumeSessionPickerContent(props: ResumeSessionPickerProps) {
|
|
|
123
311
|
wrap="truncate-end"
|
|
124
312
|
>
|
|
125
313
|
{props.error === undefined
|
|
126
|
-
?
|
|
314
|
+
? formatFooter({
|
|
315
|
+
searching,
|
|
316
|
+
query,
|
|
317
|
+
matchCount,
|
|
318
|
+
windowStart,
|
|
319
|
+
windowEnd,
|
|
320
|
+
totalCount: props.sessions.length,
|
|
321
|
+
})
|
|
127
322
|
: `Resume failed: ${singleLine(props.error)}`}
|
|
128
323
|
</Text>
|
|
129
324
|
</Box>
|
|
130
325
|
);
|
|
131
326
|
}
|
|
132
327
|
|
|
328
|
+
function SearchLine(props: { editor: LineEditorState }) {
|
|
329
|
+
const { before, at, after } = splitAtCursor(props.editor);
|
|
330
|
+
return (
|
|
331
|
+
<Text wrap="truncate-end">
|
|
332
|
+
Search: {before}
|
|
333
|
+
<Text inverse>{at}</Text>
|
|
334
|
+
{after}
|
|
335
|
+
</Text>
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
|
|
133
339
|
export function ResumeSessionPickerLoading(props: { onCancel: () => void }) {
|
|
134
340
|
useInput((_input, key) => {
|
|
135
341
|
if (key.escape) {
|
|
@@ -145,6 +351,19 @@ export function ResumeSessionPickerLoading(props: { onCancel: () => void }) {
|
|
|
145
351
|
);
|
|
146
352
|
}
|
|
147
353
|
|
|
354
|
+
export function normalizeSearchText(value: string): string {
|
|
355
|
+
return value.normalize("NFKC").replace(/\s+/g, " ").trim().toLowerCase();
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function matchesSessionPreview(session: SessionSummary, query: string): boolean {
|
|
359
|
+
const terms = normalizeSearchText(query).split(" ").filter(Boolean);
|
|
360
|
+
if (terms.length === 0) {
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
const preview = normalizeSearchText(session.firstUserPromptPreview ?? "");
|
|
364
|
+
return preview !== "" && terms.every((term) => preview.includes(term));
|
|
365
|
+
}
|
|
366
|
+
|
|
148
367
|
function SessionOption(props: {
|
|
149
368
|
session: SessionSummary;
|
|
150
369
|
isSelected: boolean;
|
|
@@ -255,6 +474,29 @@ function shortSessionId(sessionId: string): string {
|
|
|
255
474
|
return `${sessionId.slice(0, 8)}…`;
|
|
256
475
|
}
|
|
257
476
|
|
|
477
|
+
function formatFooter(input: {
|
|
478
|
+
searching: boolean;
|
|
479
|
+
query: string;
|
|
480
|
+
matchCount: number;
|
|
481
|
+
windowStart: number;
|
|
482
|
+
windowEnd: number;
|
|
483
|
+
totalCount: number;
|
|
484
|
+
}): string {
|
|
485
|
+
if (input.searching) {
|
|
486
|
+
if (input.matchCount === 0) {
|
|
487
|
+
return `No sessions match "${singleLine(input.query)}" · Esc to clear search`;
|
|
488
|
+
}
|
|
489
|
+
if (input.matchCount > MAX_DISPLAYED_SESSIONS) {
|
|
490
|
+
return `Showing ${input.windowStart + 1}–${input.windowEnd} / ${MAX_DISPLAYED_SESSIONS} results · ${input.matchCount} matches total`;
|
|
491
|
+
}
|
|
492
|
+
return `${input.matchCount} ${input.matchCount === 1 ? "match" : "matches"}`;
|
|
493
|
+
}
|
|
494
|
+
if (input.totalCount > MAX_DISPLAYED_SESSIONS) {
|
|
495
|
+
return `Showing ${input.windowStart + 1}–${input.windowEnd} / ${MAX_DISPLAYED_SESSIONS} recent · ${input.totalCount} sessions total`;
|
|
496
|
+
}
|
|
497
|
+
return formatWindowStatus(input.windowStart, input.windowEnd, input.totalCount);
|
|
498
|
+
}
|
|
499
|
+
|
|
258
500
|
function formatWindowStatus(start: number, end: number, total: number): string {
|
|
259
501
|
if (start === 0 && end === total) {
|
|
260
502
|
return `${total} ${total === 1 ? "session" : "sessions"}`;
|
|
@@ -262,6 +504,10 @@ function formatWindowStatus(start: number, end: number, total: number): string {
|
|
|
262
504
|
return `Showing ${start + 1}–${end} / ${total}${start > 0 ? " · ↑ more above" : ""}${end < total ? " · ↓ more below" : ""}`;
|
|
263
505
|
}
|
|
264
506
|
|
|
507
|
+
function normalizeQueryInput(value: string): string {
|
|
508
|
+
return value.replace(/\s+/g, " ");
|
|
509
|
+
}
|
|
510
|
+
|
|
265
511
|
function clamp(value: number, minimum: number, maximum: number): number {
|
|
266
512
|
return Math.min(Math.max(value, minimum), maximum);
|
|
267
513
|
}
|
package/src/tui/event-store.ts
CHANGED
|
@@ -771,7 +771,11 @@ function toolCallSummary(input: { name: string; args: unknown }): string {
|
|
|
771
771
|
if (input.name === "WebFetch") {
|
|
772
772
|
return `WebFetch ${toolUrl(input.args) ?? ""}`.trim();
|
|
773
773
|
}
|
|
774
|
-
if (
|
|
774
|
+
if (
|
|
775
|
+
input.name === "TaskOutput" ||
|
|
776
|
+
input.name === "TaskInput" ||
|
|
777
|
+
input.name === "TaskStop"
|
|
778
|
+
) {
|
|
775
779
|
return `${input.name} ${toolTaskId(input.args) ?? ""}`.trim();
|
|
776
780
|
}
|
|
777
781
|
if (input.name === "TaskList") {
|
|
@@ -866,6 +870,10 @@ function toolRawResultSummary(name: string, args: unknown, raw: ToolRawResult):
|
|
|
866
870
|
return `${base} -> ${raw.status}, ${raw.outputLines} line${raw.outputLines === 1 ? "" : "s"}`;
|
|
867
871
|
}
|
|
868
872
|
return base;
|
|
873
|
+
case "task_input":
|
|
874
|
+
return raw.ok
|
|
875
|
+
? `${base} -> ${raw.status}, wrote ${raw.writtenBytes} byte${raw.writtenBytes === 1 ? "" : "s"}, ${raw.screenColumns}x${raw.screenRows}`
|
|
876
|
+
: base;
|
|
869
877
|
case "task_stop": {
|
|
870
878
|
if (raw.status === undefined) {
|
|
871
879
|
return base;
|
|
@@ -911,6 +919,10 @@ function toolRawResultBashDetail(raw: ToolRawResult): Pick<TimelineItem, "bash">
|
|
|
911
919
|
const detail = bashResultDetail(raw);
|
|
912
920
|
return detail === undefined ? {} : { bash: detail };
|
|
913
921
|
}
|
|
922
|
+
case "task_input": {
|
|
923
|
+
const detail = bashResultDetail(raw);
|
|
924
|
+
return detail === undefined ? {} : { bash: detail };
|
|
925
|
+
}
|
|
914
926
|
case "read":
|
|
915
927
|
case "write":
|
|
916
928
|
case "edit":
|
|
@@ -954,6 +966,7 @@ function toolRawResultDiff(
|
|
|
954
966
|
case "bash":
|
|
955
967
|
case "task_list":
|
|
956
968
|
case "task_output":
|
|
969
|
+
case "task_input":
|
|
957
970
|
case "task_stop":
|
|
958
971
|
case "web_search":
|
|
959
972
|
case "web_fetch":
|
|
@@ -96,7 +96,7 @@ export class DefaultTuiSessionController implements TuiSessionController {
|
|
|
96
96
|
};
|
|
97
97
|
|
|
98
98
|
listSessions(): Promise<readonly SessionSummary[]> {
|
|
99
|
-
return this.catalog.
|
|
99
|
+
return this.catalog.listAll(this.binding.sessionId);
|
|
100
100
|
}
|
|
101
101
|
|
|
102
102
|
compact(): Promise<ContextCompactionResult> {
|