tinker-agent 2.9.0 → 2.10.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 +41 -1
- package/README.md +17 -1
- package/package.json +2 -1
- package/src/agent/runtime-hosted-session.ts +443 -0
- package/src/cli/command-line.ts +26 -2
- package/src/cli/connect-runner.tsx +26 -0
- package/src/cli/main.ts +26 -0
- package/src/cli/output.ts +1 -1
- package/src/cli/public-cli-contract.ts +18 -0
- package/src/cli/public-config-contract.ts +1 -1
- package/src/cli/serve-runner.ts +45 -0
- package/src/cli/serve-runtime.ts +100 -0
- package/src/context/context-swap-renderer.ts +14 -0
- package/src/observation/observation-builder.ts +87 -37
- package/src/remote/client.ts +350 -0
- package/src/remote/config.ts +95 -0
- package/src/remote/http-server.ts +240 -0
- package/src/remote/protocol.ts +228 -0
- package/src/remote/service-store.ts +175 -0
- package/src/remote/service.ts +219 -0
- package/src/remote/sync-hub.ts +95 -0
- package/src/session/remote-history-reader.ts +143 -0
- package/src/tools/bash-task.ts +26 -16
- package/src/tools/bash.ts +44 -2
- package/src/tools/glob.ts +107 -19
- package/src/tools/grep-output.ts +130 -0
- package/src/tools/grep-pagination.ts +73 -0
- package/src/tools/grep-path.ts +11 -0
- package/src/tools/grep-snippets.ts +111 -0
- package/src/tools/grep.ts +139 -154
- package/src/tools/read.ts +0 -9
- package/src/tools/ripgrep.ts +19 -26
- package/src/tools/shell-process.ts +30 -4
- package/src/tools/task-stop.ts +2 -1
- package/src/tools/terminal-screen.ts +11 -2
- package/src/tools/types.ts +30 -2
- package/src/tui/event-store.ts +15 -2
- package/src/tui/remote-app.tsx +210 -0
package/src/cli/output.ts
CHANGED
|
@@ -4,7 +4,7 @@ const TRUNCATION_MARKER = "...[truncated]";
|
|
|
4
4
|
const ESCAPE = String.fromCharCode(27);
|
|
5
5
|
const ANSI_CSI_PATTERN = new RegExp(`${ESCAPE}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
6
6
|
|
|
7
|
-
export type CliCommandScope = "root" | "run" | "update";
|
|
7
|
+
export type CliCommandScope = "root" | "run" | "update" | "serve" | "connect";
|
|
8
8
|
|
|
9
9
|
export interface CliOutputWriter {
|
|
10
10
|
write(chunk: string): boolean | void;
|
|
@@ -47,6 +47,24 @@ export const PUBLIC_CLI_CONTRACT = Object.freeze({
|
|
|
47
47
|
description: "Start the interactive terminal interface.",
|
|
48
48
|
profileOption: PROFILE_OPTION,
|
|
49
49
|
}),
|
|
50
|
+
serve: Object.freeze({
|
|
51
|
+
command: "serve",
|
|
52
|
+
description: "Run the local daemon for paired remote clients.",
|
|
53
|
+
configOption: Object.freeze({
|
|
54
|
+
flags: "--config <path>",
|
|
55
|
+
description: "Read the service JSON configuration.",
|
|
56
|
+
valueName: "path",
|
|
57
|
+
} satisfies PublicCliOption),
|
|
58
|
+
}),
|
|
59
|
+
connect: Object.freeze({
|
|
60
|
+
command: "connect",
|
|
61
|
+
description: "Attach a terminal client to a service; exiting detaches only.",
|
|
62
|
+
configOption: Object.freeze({
|
|
63
|
+
flags: "--config <path>",
|
|
64
|
+
description: "Read the paired client JSON configuration.",
|
|
65
|
+
valueName: "path",
|
|
66
|
+
} satisfies PublicCliOption),
|
|
67
|
+
}),
|
|
50
68
|
run: Object.freeze({
|
|
51
69
|
command: "run [prompt]",
|
|
52
70
|
description: "Run one prompt non-interactively.",
|
|
@@ -174,7 +174,7 @@ export const PUBLIC_CONFIG_FIELDS = Object.freeze([
|
|
|
174
174
|
valueKind: "positive-integer",
|
|
175
175
|
requiredIn: "never",
|
|
176
176
|
appliesIn: "always",
|
|
177
|
-
defaultValue:
|
|
177
|
+
defaultValue: 60_000,
|
|
178
178
|
secret: false,
|
|
179
179
|
section: "tooling",
|
|
180
180
|
description: "Default Bash foreground timeout in milliseconds.",
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { loadRemoteConfig } from "../remote/config";
|
|
2
|
+
import { startRemoteHttpServer } from "../remote/http-server";
|
|
3
|
+
import { RemoteService } from "../remote/service";
|
|
4
|
+
import { RemoteServiceStore } from "../remote/service-store";
|
|
5
|
+
import { defaultHomeRoot } from "../session/workspace-storage";
|
|
6
|
+
import { createHostedRuntimeFactory } from "./serve-runtime";
|
|
7
|
+
import { writeCliOutput, type CliOutputWriter } from "./output";
|
|
8
|
+
|
|
9
|
+
export async function runServe(input: {
|
|
10
|
+
configPath: string;
|
|
11
|
+
env: NodeJS.ProcessEnv;
|
|
12
|
+
stdout: CliOutputWriter;
|
|
13
|
+
}): Promise<number> {
|
|
14
|
+
const config = await loadRemoteConfig(input.configPath);
|
|
15
|
+
const homeRoot = defaultHomeRoot(input.env);
|
|
16
|
+
const store = await RemoteServiceStore.open(config.stateDirectory);
|
|
17
|
+
const service = new RemoteService(
|
|
18
|
+
store,
|
|
19
|
+
config.workspaces,
|
|
20
|
+
createHostedRuntimeFactory(config.workspaces, input.env, homeRoot),
|
|
21
|
+
homeRoot,
|
|
22
|
+
);
|
|
23
|
+
let transport: ReturnType<typeof startRemoteHttpServer> | undefined;
|
|
24
|
+
try {
|
|
25
|
+
await service.initialize();
|
|
26
|
+
transport = startRemoteHttpServer(service, config);
|
|
27
|
+
await writeCliOutput(
|
|
28
|
+
input.stdout,
|
|
29
|
+
`Tinker service listening on https://${config.hostname}:${transport.port}; ${config.workspaces.length} workspace(s).\nClient disconnects detach only. Stop the process to shut down hosted sessions.\n`,
|
|
30
|
+
);
|
|
31
|
+
await new Promise<void>((resolve) => {
|
|
32
|
+
const stop = () => {
|
|
33
|
+
process.off("SIGINT", stop);
|
|
34
|
+
process.off("SIGTERM", stop);
|
|
35
|
+
resolve();
|
|
36
|
+
};
|
|
37
|
+
process.once("SIGINT", stop);
|
|
38
|
+
process.once("SIGTERM", stop);
|
|
39
|
+
});
|
|
40
|
+
return 0;
|
|
41
|
+
} finally {
|
|
42
|
+
await transport?.stopTransport();
|
|
43
|
+
await service.close();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { createRuntimeSession } from "../agent/runtime-session";
|
|
2
|
+
import { parseSessionId } from "../ids/runtime-id";
|
|
3
|
+
import {
|
|
4
|
+
buildSystemPrompt,
|
|
5
|
+
loadProjectInstructions,
|
|
6
|
+
projectInstructionManifest,
|
|
7
|
+
} from "../instructions/project-instructions";
|
|
8
|
+
import { createReasoningEffortController } from "../model/reasoning-effort";
|
|
9
|
+
import { resolveSessionDatabasePath } from "../session/session-store";
|
|
10
|
+
import { SessionCatalog } from "../session/session-catalog";
|
|
11
|
+
import { loadSkillCatalog } from "../skills/skill-loader";
|
|
12
|
+
import type { RemoteWorkspaceConfig } from "../remote/config";
|
|
13
|
+
import type { HostedRuntimeFactory } from "../agent/runtime-hosted-session";
|
|
14
|
+
import { deriveRunnerConfig, resolvePublicConfig } from "./config";
|
|
15
|
+
import { resolveSessionProfileName } from "./model-profiles";
|
|
16
|
+
import {
|
|
17
|
+
createRunnerModelClient,
|
|
18
|
+
createWebFetchRefiner,
|
|
19
|
+
RUNTIME_INSTRUCTIONS,
|
|
20
|
+
} from "./runner-dependencies";
|
|
21
|
+
|
|
22
|
+
/** Service composition reuses the existing configuration/provider/runtime contracts. */
|
|
23
|
+
export function createHostedRuntimeFactory(
|
|
24
|
+
workspaces: readonly RemoteWorkspaceConfig[],
|
|
25
|
+
env: NodeJS.ProcessEnv,
|
|
26
|
+
homeRoot?: string,
|
|
27
|
+
): HostedRuntimeFactory {
|
|
28
|
+
return async ({ record, sink }) => {
|
|
29
|
+
const workspace = workspaces.find((entry) => entry.id === record.workspaceId);
|
|
30
|
+
if (!workspace || workspace.path !== record.workspacePath)
|
|
31
|
+
throw new Error("Managed workspace configuration changed.");
|
|
32
|
+
const sessionId = parseSessionId(record.id);
|
|
33
|
+
const publicConfig = await resolvePublicConfig({
|
|
34
|
+
env: { ...env, TINKER_WORKSPACE: workspace.path },
|
|
35
|
+
cwd: workspace.path,
|
|
36
|
+
});
|
|
37
|
+
let profileName = workspace.profile;
|
|
38
|
+
if (record.initialized && publicConfig.mode === "profile") {
|
|
39
|
+
const summary = await new SessionCatalog({
|
|
40
|
+
workspaceRoot: workspace.path,
|
|
41
|
+
homeRoot,
|
|
42
|
+
}).get(sessionId);
|
|
43
|
+
profileName = resolveSessionProfileName(publicConfig.profiles, summary);
|
|
44
|
+
}
|
|
45
|
+
const config = deriveRunnerConfig(publicConfig, {
|
|
46
|
+
sessionId,
|
|
47
|
+
...(profileName ? { profileName } : {}),
|
|
48
|
+
});
|
|
49
|
+
const reasoning = createReasoningEffortController(config.reasoning);
|
|
50
|
+
const projectInstructions = await loadProjectInstructions(workspace.path);
|
|
51
|
+
const runtime = await createRuntimeSession({
|
|
52
|
+
workspaceRoot: workspace.path,
|
|
53
|
+
...(homeRoot === undefined ? {} : { homeRoot }),
|
|
54
|
+
...(record.initialized
|
|
55
|
+
? { selection: { mode: "resume" as const, sessionId } }
|
|
56
|
+
: { selection: { mode: "new" as const, sessionId } }),
|
|
57
|
+
modelName: config.modelName,
|
|
58
|
+
profileName: config.profileName,
|
|
59
|
+
maxIterations: config.maxIterations,
|
|
60
|
+
includeReasoningContent: config.includeReasoningContent,
|
|
61
|
+
contextProfile: config.contextProfile,
|
|
62
|
+
contextBudget: config.contextBudget,
|
|
63
|
+
modelClient: createRunnerModelClient(config, undefined, env, reasoning),
|
|
64
|
+
systemPrompt: buildSystemPrompt({
|
|
65
|
+
workspaceRoot: workspace.path,
|
|
66
|
+
runtimeInstructions: RUNTIME_INSTRUCTIONS(workspace.path),
|
|
67
|
+
projectInstructions,
|
|
68
|
+
}),
|
|
69
|
+
projectInstruction: projectInstructionManifest(projectInstructions),
|
|
70
|
+
skillCatalog: await loadSkillCatalog({ workspaceRoot: workspace.path }),
|
|
71
|
+
presentationSinks: [sink],
|
|
72
|
+
assistantTextDeltaSink: sink,
|
|
73
|
+
toolingConfig: publicConfig.tooling,
|
|
74
|
+
webFetchRefiner: createWebFetchRefiner(config, env, reasoning),
|
|
75
|
+
enableAskUser: true,
|
|
76
|
+
bashGuard: {
|
|
77
|
+
mode: config.bashGuardMode,
|
|
78
|
+
source: config.bashGuardSource,
|
|
79
|
+
surface: "tui",
|
|
80
|
+
},
|
|
81
|
+
});
|
|
82
|
+
try {
|
|
83
|
+
return {
|
|
84
|
+
runtime,
|
|
85
|
+
databasePath: await resolveSessionDatabasePath(
|
|
86
|
+
workspace.path,
|
|
87
|
+
sessionId,
|
|
88
|
+
homeRoot,
|
|
89
|
+
),
|
|
90
|
+
modelName: config.modelName,
|
|
91
|
+
};
|
|
92
|
+
} catch (error) {
|
|
93
|
+
await runtime.dispose({
|
|
94
|
+
type: "initialization_failed",
|
|
95
|
+
error: "Cannot open remote history reader.",
|
|
96
|
+
});
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
};
|
|
100
|
+
}
|
|
@@ -193,6 +193,11 @@ function metadataEntries(
|
|
|
193
193
|
["pattern", raw.pattern],
|
|
194
194
|
["searchPath", raw.searchPath],
|
|
195
195
|
["matchCount", raw.matchCount],
|
|
196
|
+
["totalMatches", raw.totalMatches],
|
|
197
|
+
["returnedCount", raw.returnedCount],
|
|
198
|
+
["appliedOffset", raw.appliedOffset],
|
|
199
|
+
["hasMore", raw.hasMore],
|
|
200
|
+
["nextOffset", raw.nextOffset],
|
|
196
201
|
];
|
|
197
202
|
case "grep":
|
|
198
203
|
return [
|
|
@@ -200,6 +205,15 @@ function metadataEntries(
|
|
|
200
205
|
["searchPath", raw.searchPath],
|
|
201
206
|
["mode", raw.mode],
|
|
202
207
|
["numMatches", raw.numMatches],
|
|
208
|
+
["numLines", raw.numLines],
|
|
209
|
+
["paginationUnit", raw.paginationUnit],
|
|
210
|
+
["totalResults", raw.totalResults],
|
|
211
|
+
["returnedResults", raw.returnedResults],
|
|
212
|
+
["appliedOffset", raw.appliedOffset],
|
|
213
|
+
["hasMore", raw.hasMore],
|
|
214
|
+
["nextOffset", raw.nextOffset],
|
|
215
|
+
["searchIncomplete", raw.searchIncomplete],
|
|
216
|
+
["contextMayBeIncomplete", raw.contextMayBeIncomplete],
|
|
203
217
|
["truncated", raw.truncated],
|
|
204
218
|
];
|
|
205
219
|
case "bash":
|
|
@@ -35,6 +35,7 @@ import type {
|
|
|
35
35
|
WriteFileRawResult,
|
|
36
36
|
} from "../tools/types";
|
|
37
37
|
import { MAX_MEMORY_TEXT_BYTES, truncateUtf8 } from "../memory/contracts";
|
|
38
|
+
import { formatGrepPath } from "../tools/grep-path";
|
|
38
39
|
|
|
39
40
|
export type ToolObservation = {
|
|
40
41
|
readonly content: readonly ToolResultContent[];
|
|
@@ -181,18 +182,30 @@ function assertNever(value: never): never {
|
|
|
181
182
|
|
|
182
183
|
function renderGlobObservation(raw: GlobRawResult): string {
|
|
183
184
|
if (!raw.ok) {
|
|
184
|
-
|
|
185
|
+
const pattern =
|
|
186
|
+
raw.pattern === undefined ? "(missing or invalid)" : JSON.stringify(raw.pattern);
|
|
187
|
+
return `Glob failed for pattern=${pattern}, searchPath=${JSON.stringify(raw.searchPath)}: ${raw.error ?? "Unknown error."}`;
|
|
185
188
|
}
|
|
186
189
|
|
|
187
190
|
const matches = raw.matches ?? [];
|
|
191
|
+
const totalMatches = raw.totalMatches ?? raw.matchCount ?? matches.length;
|
|
188
192
|
|
|
189
193
|
return [
|
|
190
194
|
`Glob succeeded for pattern=${JSON.stringify(raw.pattern)}.`,
|
|
191
195
|
`searchPath=${raw.searchPath}`,
|
|
192
|
-
`
|
|
196
|
+
`totalMatches=${totalMatches}`,
|
|
197
|
+
`returnedCount=${raw.returnedCount ?? matches.length}`,
|
|
198
|
+
`hasMore=${raw.hasMore ?? false}`,
|
|
199
|
+
...(raw.hasMore && raw.nextOffset !== undefined
|
|
200
|
+
? [`nextOffset=${raw.nextOffset}`]
|
|
201
|
+
: []),
|
|
193
202
|
`ignored=${(raw.ignored ?? []).join(",")}`,
|
|
194
203
|
"matches:",
|
|
195
|
-
matches.length
|
|
204
|
+
matches.length > 0
|
|
205
|
+
? matches.join("\n")
|
|
206
|
+
: totalMatches === 0
|
|
207
|
+
? "(no matches)"
|
|
208
|
+
: `(no results on this page at offset ${raw.appliedOffset ?? 0})`,
|
|
196
209
|
].join("\n");
|
|
197
210
|
}
|
|
198
211
|
|
|
@@ -202,31 +215,51 @@ function renderGrepObservation(raw: GrepRawResult): string {
|
|
|
202
215
|
}
|
|
203
216
|
|
|
204
217
|
const sections: string[] = [];
|
|
218
|
+
const paginated = raw.appliedLimit !== undefined || (raw.appliedOffset ?? 0) > 0;
|
|
219
|
+
const incomplete = grepSearchIncomplete(raw);
|
|
220
|
+
const empty = raw.mode === "content" ? !raw.content : raw.numFiles === 0;
|
|
205
221
|
|
|
206
|
-
if (
|
|
207
|
-
|
|
208
|
-
raw.numFiles === 0
|
|
209
|
-
? "No files found"
|
|
210
|
-
: [
|
|
211
|
-
`Found ${raw.numFiles} file${raw.numFiles === 1 ? "" : "s"}`,
|
|
212
|
-
...raw.filenames,
|
|
213
|
-
].join("\n"),
|
|
214
|
-
);
|
|
215
|
-
} else if (raw.mode === "count") {
|
|
216
|
-
if (raw.numFiles === 0) {
|
|
222
|
+
if (empty) {
|
|
223
|
+
if (raw.totalResults === 0 && !incomplete) {
|
|
217
224
|
sections.push("No matches found");
|
|
225
|
+
} else if ((raw.appliedOffset ?? 0) > 0) {
|
|
226
|
+
sections.push(`No results on this page at offset ${raw.appliedOffset}.`);
|
|
218
227
|
} else {
|
|
219
|
-
sections.push(raw.content ?? "");
|
|
220
228
|
sections.push(
|
|
221
|
-
|
|
229
|
+
incomplete
|
|
230
|
+
? "No results available in this partial output."
|
|
231
|
+
: "No matches found",
|
|
222
232
|
);
|
|
223
233
|
}
|
|
224
|
-
} else {
|
|
234
|
+
} else if (raw.mode === "files_with_matches") {
|
|
225
235
|
sections.push(
|
|
226
|
-
|
|
227
|
-
? "
|
|
228
|
-
|
|
236
|
+
[
|
|
237
|
+
`${paginated || incomplete ? "Showing" : "Found"} ${raw.numFiles} matching file${raw.numFiles === 1 ? "" : "s"}${paginated ? " on this page" : ""}`,
|
|
238
|
+
...raw.filenames.map(formatGrepPath),
|
|
239
|
+
].join("\n"),
|
|
229
240
|
);
|
|
241
|
+
} else if (raw.mode === "count" || raw.mode === "count-matches") {
|
|
242
|
+
const mode = raw.mode;
|
|
243
|
+
sections.push(
|
|
244
|
+
raw.counts !== undefined
|
|
245
|
+
? raw.counts
|
|
246
|
+
.map(
|
|
247
|
+
(entry) =>
|
|
248
|
+
`${formatGrepPath(entry.filePath)}: ${grepCountLabel(mode, entry.count)}`,
|
|
249
|
+
)
|
|
250
|
+
.join("\n")
|
|
251
|
+
: // Legacy stored results have only display text. Never use it to compute totals.
|
|
252
|
+
(raw.content ?? "").replace(
|
|
253
|
+
/:(\d+)$/gm,
|
|
254
|
+
(_suffix, count: string) => `: ${grepCountLabel(mode, Number(count))}`,
|
|
255
|
+
),
|
|
256
|
+
);
|
|
257
|
+
const scope = paginated ? "This page" : incomplete ? "Results shown" : "Total";
|
|
258
|
+
sections.push(
|
|
259
|
+
`${scope}: ${grepCountLabel(mode, raw.numMatches ?? 0)} across ${raw.numFiles} matching file${raw.numFiles === 1 ? "" : "s"}.`,
|
|
260
|
+
);
|
|
261
|
+
} else {
|
|
262
|
+
sections.push(raw.content ?? "");
|
|
230
263
|
}
|
|
231
264
|
|
|
232
265
|
const pagination = renderGrepPagination(raw);
|
|
@@ -234,27 +267,49 @@ function renderGrepObservation(raw: GrepRawResult): string {
|
|
|
234
267
|
sections.push(pagination);
|
|
235
268
|
}
|
|
236
269
|
|
|
237
|
-
if (
|
|
238
|
-
sections.push(
|
|
270
|
+
if (incomplete) {
|
|
271
|
+
sections.push(
|
|
272
|
+
`Warning: results are incomplete. ${raw.error ?? "Search did not finish."}`,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
if (raw.contextMayBeIncomplete === true) {
|
|
276
|
+
sections.push(
|
|
277
|
+
"Warning: requested context may be incomplete because the search stopped early. Narrow the search and retry.",
|
|
278
|
+
);
|
|
239
279
|
}
|
|
240
280
|
|
|
241
281
|
return sections.join("\n\n");
|
|
242
282
|
}
|
|
243
283
|
|
|
244
|
-
function
|
|
245
|
-
|
|
284
|
+
function grepCountLabel(mode: "count" | "count-matches", count: number): string {
|
|
285
|
+
return mode === "count"
|
|
286
|
+
? `${count} matching line${count === 1 ? "" : "s"}`
|
|
287
|
+
: `${count} match${count === 1 ? "" : "es"}`;
|
|
288
|
+
}
|
|
246
289
|
|
|
247
|
-
|
|
248
|
-
|
|
290
|
+
function renderGrepPagination(raw: GrepRawResult): string | undefined {
|
|
291
|
+
const incomplete = grepSearchIncomplete(raw);
|
|
292
|
+
const hasMore = raw.hasMore ?? raw.appliedLimit !== undefined;
|
|
293
|
+
const nextOffset =
|
|
294
|
+
raw.nextOffset ??
|
|
295
|
+
(raw.appliedLimit === undefined
|
|
296
|
+
? undefined
|
|
297
|
+
: (raw.appliedOffset ?? 0) + raw.appliedLimit);
|
|
298
|
+
if (hasMore && nextOffset !== undefined) {
|
|
299
|
+
return `More ${incomplete ? "collected " : ""}results available; nextOffset=${nextOffset}.`;
|
|
249
300
|
}
|
|
250
301
|
|
|
251
|
-
if (raw.appliedOffset !==
|
|
252
|
-
|
|
302
|
+
if ((raw.appliedOffset ?? 0) > 0 && raw.totalResults !== 0) {
|
|
303
|
+
return incomplete
|
|
304
|
+
? "End of collected results; search is incomplete."
|
|
305
|
+
: "End of results.";
|
|
253
306
|
}
|
|
254
307
|
|
|
255
|
-
return
|
|
256
|
-
|
|
257
|
-
|
|
308
|
+
return undefined;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
function grepSearchIncomplete(raw: GrepRawResult): boolean {
|
|
312
|
+
return raw.searchIncomplete ?? (raw.truncated === true && raw.error !== undefined);
|
|
258
313
|
}
|
|
259
314
|
|
|
260
315
|
function renderReadObservation(raw: ReadFileRawResult): string {
|
|
@@ -269,7 +324,6 @@ function renderReadObservation(raw: ReadFileRawResult): string {
|
|
|
269
324
|
|
|
270
325
|
return [
|
|
271
326
|
`Read succeeded for ${raw.filePath}.`,
|
|
272
|
-
`sha256=${raw.sha256}`,
|
|
273
327
|
`sizeBytes=${raw.sizeBytes ?? 0}`,
|
|
274
328
|
`contentBytes=${raw.contentBytes ?? 0}`,
|
|
275
329
|
`totalLines=${raw.totalLines ?? 0}`,
|
|
@@ -489,8 +543,6 @@ function renderWriteObservation(raw: WriteFileRawResult): string {
|
|
|
489
543
|
return [
|
|
490
544
|
`Write succeeded for ${raw.filePath}.`,
|
|
491
545
|
`bytesWritten=${raw.bytesWritten ?? 0}`,
|
|
492
|
-
`oldSha256=${raw.oldSha256 ?? "null"}`,
|
|
493
|
-
`newSha256=${raw.newSha256}`,
|
|
494
546
|
].join("\n");
|
|
495
547
|
}
|
|
496
548
|
|
|
@@ -508,8 +560,6 @@ function renderEditObservation(raw: EditFileRawResult): string {
|
|
|
508
560
|
`replacementCount=${raw.replacementCount ?? 0}`,
|
|
509
561
|
`replaceAll=${raw.replaceAll ?? false}`,
|
|
510
562
|
`created=${raw.created ?? false}`,
|
|
511
|
-
`oldSha256=${raw.oldSha256 ?? "null"}`,
|
|
512
|
-
`newSha256=${raw.newSha256}`,
|
|
513
563
|
].join("\n");
|
|
514
564
|
}
|
|
515
565
|
|
|
@@ -644,7 +694,7 @@ function renderTaskInputObservation(raw: TaskInputRawResult): string {
|
|
|
644
694
|
}
|
|
645
695
|
|
|
646
696
|
return [
|
|
647
|
-
"Terminal input sent.",
|
|
697
|
+
raw.writtenBytes === 0 ? "Terminal screen polled." : "Terminal input sent.",
|
|
648
698
|
`taskId=${raw.taskId}`,
|
|
649
699
|
`status=${raw.status}`,
|
|
650
700
|
`writtenBytes=${raw.writtenBytes}`,
|