@oh-my-pi/pi-coding-agent 14.7.2 → 14.7.4
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 +21 -0
- package/package.json +7 -7
- package/src/cli/read-cli.ts +1 -2
- package/src/commands/read.ts +2 -7
- package/src/config/settings-schema.ts +0 -5
- package/src/edit/modes/hashline.ts +40 -19
- package/src/edit/modes/patch.ts +7 -5
- package/src/edit/modes/replace.ts +6 -2
- package/src/edit/notebook.ts +222 -0
- package/src/edit/read-file.ts +7 -0
- package/src/edit/renderer.ts +4 -3
- package/src/edit/streaming.ts +49 -7
- package/src/modes/components/diff.ts +54 -7
- package/src/modes/components/tool-execution.ts +3 -29
- package/src/prompts/agents/designer.md +1 -2
- package/src/prompts/agents/explore.md +2 -5
- package/src/prompts/agents/init.md +1 -4
- package/src/prompts/agents/librarian.md +1 -3
- package/src/prompts/agents/plan.md +7 -8
- package/src/prompts/agents/reviewer.md +1 -2
- package/src/prompts/ci-green-request.md +10 -10
- package/src/prompts/commands/orchestrate.md +48 -0
- package/src/prompts/memories/consolidation.md +10 -10
- package/src/prompts/memories/read-path.md +6 -6
- package/src/prompts/system/agent-creation-architect.md +54 -44
- package/src/prompts/system/custom-system-prompt.md +3 -5
- package/src/prompts/system/eager-todo.md +4 -4
- package/src/prompts/system/handoff-document.md +7 -4
- package/src/prompts/system/plan-mode-active.md +7 -3
- package/src/prompts/system/plan-mode-approved.md +5 -5
- package/src/prompts/system/summarization-system.md +2 -2
- package/src/prompts/system/system-prompt.md +53 -65
- package/src/prompts/system/title-system.md +2 -2
- package/src/prompts/system/web-search.md +16 -19
- package/src/prompts/tools/bash.md +8 -8
- package/src/prompts/tools/browser.md +4 -4
- package/src/prompts/tools/debug.md +3 -1
- package/src/prompts/tools/eval.md +13 -9
- package/src/prompts/tools/hashline.md +4 -2
- package/src/prompts/tools/image-gen.md +1 -1
- package/src/prompts/tools/read.md +1 -2
- package/src/prompts/tools/reflect.md +3 -3
- package/src/prompts/tools/render-mermaid.md +2 -2
- package/src/prompts/tools/resolve.md +2 -2
- package/src/prompts/tools/retain.md +3 -2
- package/src/prompts/tools/rewind.md +2 -2
- package/src/prompts/tools/search-tool-bm25.md +3 -4
- package/src/prompts/tools/task.md +1 -1
- package/src/prompts/tools/todo-write.md +2 -2
- package/src/task/commands.ts +5 -1
- package/src/tools/fetch.ts +6 -7
- package/src/tools/index.ts +0 -4
- package/src/tools/read.ts +18 -7
- package/src/tools/renderers.ts +0 -2
- package/src/tools/write.ts +41 -26
- package/src/tools/notebook.ts +0 -286
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
|
|
1
|
+
End an active checkpoint. Rewind context to it, replacing intermediate exploration with your report.
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Call immediately after `checkpoint`-started investigative work.
|
|
4
4
|
|
|
5
5
|
Requirements:
|
|
6
6
|
- `report` is **REQUIRED** and must be concise, factual, and actionable.
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
Search hidden tool metadata to discover and activate tools.
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Activate hidden tools (MCP and built-in) when you need a capability not in your active tool set.
|
|
5
4
|
{{#if hasDiscoverableMCPServers}}Discoverable MCP servers in this session: {{#list discoverableMCPServerSummaries join=", "}}{{this}}{{/list}}.{{/if}}
|
|
6
5
|
{{#if discoverableMCPToolCount}}Total discoverable tools available: {{discoverableMCPToolCount}}.{{/if}}
|
|
7
6
|
Input:
|
|
@@ -16,7 +15,7 @@ Behavior:
|
|
|
16
15
|
- Newly activated tools become available before the next model call in the same overall turn
|
|
17
16
|
|
|
18
17
|
Notes:
|
|
19
|
-
|
|
18
|
+
Start with `limit` 5–10 if unsure.
|
|
20
19
|
- `query` is matched against tool metadata fields:
|
|
21
20
|
- `name`
|
|
22
21
|
- `label`
|
|
@@ -25,7 +24,7 @@ Notes:
|
|
|
25
24
|
- `description` / `summary`
|
|
26
25
|
- input schema property keys (`schema_keys`)
|
|
27
26
|
|
|
28
|
-
|
|
27
|
+
Not for repository/file/code search. Tool discovery only.
|
|
29
28
|
|
|
30
29
|
Returns JSON with:
|
|
31
30
|
- `query`
|
|
@@ -5,7 +5,7 @@ Launches subagents to parallelize workflows.
|
|
|
5
5
|
- Use `job` (with `poll`) to wait. **MUST NOT** poll `read jobs://` in a loop.
|
|
6
6
|
{{/if}}
|
|
7
7
|
|
|
8
|
-
Subagents have no
|
|
8
|
+
Subagents have no conversation history. Every fact, file path, and decision they need **MUST** be explicit in {{#if contextEnabled}}`context` or `assignment`{{else}}each `assignment`{{/if}}.
|
|
9
9
|
|
|
10
10
|
<parameters>
|
|
11
11
|
- `agent`: agent type for all tasks
|
|
@@ -6,12 +6,12 @@ Allowed `op` values are only `init`, `start`, `done`, `drop`, `rm`, `append`, an
|
|
|
6
6
|
|
|
7
7
|
|`op`|Required fields|Effect|
|
|
8
8
|
|---|---|---|
|
|
9
|
-
|`init`|`list`|Initialize the full list|
|
|
9
|
+
|`init`|`list: [{phase, items: string[]}]`|Initialize the full list (replaces any existing list)|
|
|
10
10
|
|`start`|`task`|Mark in progress|
|
|
11
11
|
|`done`|`task` or `phase`|Mark completed|
|
|
12
12
|
|`drop`|`task` or `phase`|Mark abandoned|
|
|
13
13
|
|`rm`|`task` or `phase`|Remove|
|
|
14
|
-
|`append`|`phase`, `items: string[]`|Append tasks
|
|
14
|
+
|`append`|`phase`, `items: string[]`|Append tasks to `phase`; lazily creates phase|
|
|
15
15
|
|`note`|`task`, `text`|Append a note to a task. Reminders for future-you only.|
|
|
16
16
|
|
|
17
17
|
## Anatomy
|
package/src/task/commands.ts
CHANGED
|
@@ -9,8 +9,12 @@ import { type SlashCommand, slashCommandCapability } from "../capability/slash-c
|
|
|
9
9
|
import { loadCapability } from "../discovery";
|
|
10
10
|
// Embed command markdown files at build time
|
|
11
11
|
import initMd from "../prompts/agents/init.md" with { type: "text" };
|
|
12
|
+
import orchestrateMd from "../prompts/commands/orchestrate.md" with { type: "text" };
|
|
12
13
|
|
|
13
|
-
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
14
|
+
const EMBEDDED_COMMANDS: { name: string; content: string }[] = [
|
|
15
|
+
{ name: "init.md", content: prompt.render(initMd) },
|
|
16
|
+
{ name: "orchestrate.md", content: prompt.render(orchestrateMd) },
|
|
17
|
+
];
|
|
14
18
|
|
|
15
19
|
export const EMBEDDED_COMMAND_TEMPLATES: ReadonlyArray<{ name: string; content: string }> = EMBEDDED_COMMANDS;
|
|
16
20
|
|
package/src/tools/fetch.ts
CHANGED
|
@@ -1220,13 +1220,13 @@ function cacheReadUrlEntry(session: ToolSession, requestedUrl: string, raw: bool
|
|
|
1220
1220
|
|
|
1221
1221
|
async function buildReadUrlCacheEntry(
|
|
1222
1222
|
session: ToolSession,
|
|
1223
|
-
params: { path: string;
|
|
1223
|
+
params: { path: string; raw?: boolean },
|
|
1224
1224
|
signal?: AbortSignal,
|
|
1225
1225
|
options?: { ensureArtifact?: boolean },
|
|
1226
1226
|
): Promise<ReadUrlCacheEntry> {
|
|
1227
|
-
const { path: url,
|
|
1227
|
+
const { path: url, raw = false } = params;
|
|
1228
1228
|
|
|
1229
|
-
const effectiveTimeout = clampTimeout("fetch",
|
|
1229
|
+
const effectiveTimeout = clampTimeout("fetch", 30);
|
|
1230
1230
|
|
|
1231
1231
|
if (signal?.aborted) {
|
|
1232
1232
|
throw new ToolAbortError();
|
|
@@ -1254,7 +1254,7 @@ async function buildReadUrlCacheEntry(
|
|
|
1254
1254
|
|
|
1255
1255
|
export async function loadReadUrlCacheEntry(
|
|
1256
1256
|
session: ToolSession,
|
|
1257
|
-
params: { path: string;
|
|
1257
|
+
params: { path: string; raw?: boolean },
|
|
1258
1258
|
signal?: AbortSignal,
|
|
1259
1259
|
options?: { ensureArtifact?: boolean; preferCached?: boolean },
|
|
1260
1260
|
): Promise<ReadUrlCacheEntry> {
|
|
@@ -1291,7 +1291,7 @@ function buildUrlReadOutput(result: FetchRenderResult, content: string): string
|
|
|
1291
1291
|
|
|
1292
1292
|
export async function executeReadUrl(
|
|
1293
1293
|
session: ToolSession,
|
|
1294
|
-
params: { path: string;
|
|
1294
|
+
params: { path: string; raw?: boolean },
|
|
1295
1295
|
signal?: AbortSignal,
|
|
1296
1296
|
): Promise<AgentToolResult<ReadUrlToolDetails>> {
|
|
1297
1297
|
let cacheEntry = await loadReadUrlCacheEntry(session, params, signal, { preferCached: true });
|
|
@@ -1345,7 +1345,7 @@ function countNonEmptyLines(text: string): number {
|
|
|
1345
1345
|
|
|
1346
1346
|
/** Render URL read call (URL preview) */
|
|
1347
1347
|
export function renderReadUrlCall(
|
|
1348
|
-
args: { path?: string; url?: string;
|
|
1348
|
+
args: { path?: string; url?: string; raw?: boolean },
|
|
1349
1349
|
_options: RenderResultOptions,
|
|
1350
1350
|
uiTheme: Theme = theme,
|
|
1351
1351
|
): Component {
|
|
@@ -1355,7 +1355,6 @@ export function renderReadUrlCall(
|
|
|
1355
1355
|
const description = `${domain}${path ? ` ${path}` : ""}`.trim();
|
|
1356
1356
|
const meta: string[] = [];
|
|
1357
1357
|
if (args.raw) meta.push("raw");
|
|
1358
|
-
if (args.timeout !== undefined) meta.push(`timeout:${args.timeout}s`);
|
|
1359
1358
|
const text = renderStatusLine({ icon: "pending", title: "Read", description, meta }, uiTheme);
|
|
1360
1359
|
return new Text(text, 0, 0);
|
|
1361
1360
|
}
|
package/src/tools/index.ts
CHANGED
|
@@ -37,7 +37,6 @@ import { HindsightRetainTool } from "./hindsight-retain";
|
|
|
37
37
|
import { InspectImageTool } from "./inspect-image";
|
|
38
38
|
import { IrcTool } from "./irc";
|
|
39
39
|
import { JobTool } from "./job";
|
|
40
|
-
import { NotebookTool } from "./notebook";
|
|
41
40
|
import { wrapToolWithMetaNotice } from "./output-meta";
|
|
42
41
|
import { ReadTool } from "./read";
|
|
43
42
|
import { RecipeTool } from "./recipe";
|
|
@@ -80,7 +79,6 @@ export * from "./image-gen";
|
|
|
80
79
|
export * from "./inspect-image";
|
|
81
80
|
export * from "./irc";
|
|
82
81
|
export * from "./job";
|
|
83
|
-
export * from "./notebook";
|
|
84
82
|
export * from "./read";
|
|
85
83
|
export * from "./recipe";
|
|
86
84
|
export * from "./render-mermaid";
|
|
@@ -271,7 +269,6 @@ export const BUILTIN_TOOLS: Record<string, ToolFactory> = {
|
|
|
271
269
|
find: s => new FindTool(s),
|
|
272
270
|
search: s => new SearchTool(s),
|
|
273
271
|
lsp: LspTool.createIf,
|
|
274
|
-
notebook: s => new NotebookTool(s),
|
|
275
272
|
inspect_image: s => new InspectImageTool(s),
|
|
276
273
|
browser: s => new BrowserTool(s),
|
|
277
274
|
checkpoint: CheckpointTool.createIf,
|
|
@@ -430,7 +427,6 @@ export async function createTools(session: ToolSession, toolNames?: string[]): P
|
|
|
430
427
|
if (name === "ast_grep") return session.settings.get("astGrep.enabled");
|
|
431
428
|
if (name === "ast_edit") return session.settings.get("astEdit.enabled");
|
|
432
429
|
if (name === "render_mermaid") return session.settings.get("renderMermaid.enabled");
|
|
433
|
-
if (name === "notebook") return session.settings.get("notebook.enabled");
|
|
434
430
|
if (name === "inspect_image") return session.settings.get("inspect_image.enabled");
|
|
435
431
|
if (name === "web_search") return session.settings.get("web_search.enabled");
|
|
436
432
|
// search_tool_bm25 is allowed when either legacy mcp.discoveryMode or new tools.discoveryMode is active.
|
package/src/tools/read.ts
CHANGED
|
@@ -9,6 +9,7 @@ import { Text } from "@oh-my-pi/pi-tui";
|
|
|
9
9
|
import { getRemoteDir, prompt, readImageMetadata, untilAborted } from "@oh-my-pi/pi-utils";
|
|
10
10
|
import { type Static, Type } from "@sinclair/typebox";
|
|
11
11
|
import { formatHashLine, formatHashLines, formatLineHash, HL_BODY_SEP } from "../edit/line-hash";
|
|
12
|
+
import { isNotebookPath, readEditableNotebookText } from "../edit/notebook";
|
|
12
13
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
13
14
|
import { parseInternalUrl } from "../internal-urls/parse";
|
|
14
15
|
import type { InternalUrl } from "../internal-urls/types";
|
|
@@ -418,7 +419,6 @@ const readSchema = Type.Object({
|
|
|
418
419
|
description: 'path or url; append :<sel> for line ranges or raw mode (e.g. "src/foo.ts:50-100")',
|
|
419
420
|
examples: ["src/foo.ts", "src/foo.ts:50-100", "https://example.com:L1-L40"],
|
|
420
421
|
}),
|
|
421
|
-
timeout: Type.Optional(Type.Number({ description: "timeout in seconds", default: 20 })),
|
|
422
422
|
});
|
|
423
423
|
|
|
424
424
|
export type ReadToolInput = Static<typeof readSchema>;
|
|
@@ -1084,7 +1084,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1084
1084
|
_onUpdate?: AgentToolUpdateCallback<ReadToolDetails>,
|
|
1085
1085
|
_toolContext?: AgentToolContext,
|
|
1086
1086
|
): Promise<AgentToolResult<ReadToolDetails>> {
|
|
1087
|
-
let { path: readPath
|
|
1087
|
+
let { path: readPath } = params;
|
|
1088
1088
|
if (readPath.startsWith("file://")) {
|
|
1089
1089
|
readPath = expandPath(readPath);
|
|
1090
1090
|
}
|
|
@@ -1098,7 +1098,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1098
1098
|
if (parsedUrlTarget.offset !== undefined || parsedUrlTarget.limit !== undefined) {
|
|
1099
1099
|
const cached = await loadReadUrlCacheEntry(
|
|
1100
1100
|
this.session,
|
|
1101
|
-
{ path: parsedUrlTarget.path,
|
|
1101
|
+
{ path: parsedUrlTarget.path, raw: parsedUrlTarget.raw },
|
|
1102
1102
|
signal,
|
|
1103
1103
|
{
|
|
1104
1104
|
ensureArtifact: true,
|
|
@@ -1111,7 +1111,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1111
1111
|
entityLabel: "URL output",
|
|
1112
1112
|
});
|
|
1113
1113
|
}
|
|
1114
|
-
return executeReadUrl(this.session, { path: parsedUrlTarget.path,
|
|
1114
|
+
return executeReadUrl(this.session, { path: parsedUrlTarget.path, raw: parsedUrlTarget.raw }, signal);
|
|
1115
1115
|
}
|
|
1116
1116
|
|
|
1117
1117
|
// Handle internal URLs (agent://, artifact://, memory://, skill://, rule://, local://, mcp://)
|
|
@@ -1196,7 +1196,7 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1196
1196
|
const ext = path.extname(absolutePath).toLowerCase();
|
|
1197
1197
|
const _hasEditTool = this.session.hasEditTool ?? true;
|
|
1198
1198
|
const _language = getLanguageFromPath(absolutePath);
|
|
1199
|
-
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext)
|
|
1199
|
+
const shouldConvertWithMarkit = CONVERTIBLE_EXTENSIONS.has(ext);
|
|
1200
1200
|
// Read the file based on type
|
|
1201
1201
|
let content: Array<TextContent | ImageContent> | undefined;
|
|
1202
1202
|
let details: ReadToolDetails = {};
|
|
@@ -1263,8 +1263,20 @@ export class ReadTool implements AgentTool<typeof readSchema, ReadToolDetails> {
|
|
|
1263
1263
|
throw error;
|
|
1264
1264
|
}
|
|
1265
1265
|
}
|
|
1266
|
+
} else if (isNotebookPath(absolutePath) && parsed.kind !== "raw") {
|
|
1267
|
+
const { offset, limit } = selToOffsetLimit(parsed);
|
|
1268
|
+
return this.#buildInMemoryTextResult(
|
|
1269
|
+
await readEditableNotebookText(absolutePath, localReadPath),
|
|
1270
|
+
offset,
|
|
1271
|
+
limit,
|
|
1272
|
+
{
|
|
1273
|
+
details: { resolvedPath: absolutePath },
|
|
1274
|
+
sourcePath: absolutePath,
|
|
1275
|
+
entityLabel: "notebook",
|
|
1276
|
+
},
|
|
1277
|
+
);
|
|
1266
1278
|
} else if (shouldConvertWithMarkit) {
|
|
1267
|
-
// Convert document
|
|
1279
|
+
// Convert document via markit.
|
|
1268
1280
|
const result = await convertFileWithMarkit(absolutePath, signal);
|
|
1269
1281
|
if (result.ok) {
|
|
1270
1282
|
// Apply truncation to converted content
|
|
@@ -1556,7 +1568,6 @@ interface ReadRenderArgs {
|
|
|
1556
1568
|
path?: string;
|
|
1557
1569
|
file_path?: string;
|
|
1558
1570
|
sel?: string;
|
|
1559
|
-
timeout?: number;
|
|
1560
1571
|
// Legacy fields from old schema — tolerated for in-flight tool calls during transition
|
|
1561
1572
|
offset?: number;
|
|
1562
1573
|
limit?: number;
|
package/src/tools/renderers.ts
CHANGED
|
@@ -22,7 +22,6 @@ import { findToolRenderer } from "./find";
|
|
|
22
22
|
import { githubToolRenderer } from "./gh-renderer";
|
|
23
23
|
import { inspectImageToolRenderer } from "./inspect-image-renderer";
|
|
24
24
|
import { jobToolRenderer } from "./job";
|
|
25
|
-
import { notebookToolRenderer } from "./notebook";
|
|
26
25
|
import { readToolRenderer } from "./read";
|
|
27
26
|
import { recipeToolRenderer } from "./recipe/render";
|
|
28
27
|
import { resolveToolRenderer } from "./resolve";
|
|
@@ -60,7 +59,6 @@ export const toolRenderers: Record<string, ToolRenderer> = {
|
|
|
60
59
|
find: findToolRenderer as ToolRenderer,
|
|
61
60
|
search: searchToolRenderer as ToolRenderer,
|
|
62
61
|
lsp: lspToolRenderer as ToolRenderer,
|
|
63
|
-
notebook: notebookToolRenderer as ToolRenderer,
|
|
64
62
|
inspect_image: inspectImageToolRenderer as ToolRenderer,
|
|
65
63
|
read: readToolRenderer as ToolRenderer,
|
|
66
64
|
job: jobToolRenderer as ToolRenderer,
|
package/src/tools/write.ts
CHANGED
|
@@ -9,7 +9,7 @@ import { type Static, Type } from "@sinclair/typebox";
|
|
|
9
9
|
import { stripHashlinePrefixes } from "../edit";
|
|
10
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
11
11
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
12
|
-
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
12
|
+
import { getLanguageFromPath, highlightCode, type Theme } from "../modes/theme/theme";
|
|
13
13
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
14
14
|
import type { ToolSession } from "../sdk";
|
|
15
15
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
@@ -168,6 +168,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
168
168
|
readonly concurrency = "exclusive";
|
|
169
169
|
readonly loadMode = "discoverable";
|
|
170
170
|
readonly summary = "Write content to a file (creates or overwrites)";
|
|
171
|
+
readonly intent = (args: Partial<WriteToolInput>) => (args.path ? `writing ${args.path}` : "writing");
|
|
171
172
|
|
|
172
173
|
readonly #writethrough: WritethroughCallback;
|
|
173
174
|
|
|
@@ -523,52 +524,67 @@ function countLines(text: string): number {
|
|
|
523
524
|
return text.split("\n").length;
|
|
524
525
|
}
|
|
525
526
|
|
|
526
|
-
function
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
return uiTheme.fg("dim", `${icon} ${lineCount} lines`);
|
|
530
|
-
}
|
|
531
|
-
return uiTheme.fg("dim", `${icon}`);
|
|
527
|
+
function formatLineCountSuffix(lineCount: number, uiTheme: Theme): string {
|
|
528
|
+
if (lineCount <= 0) return "";
|
|
529
|
+
return uiTheme.fg("dim", ` · ${lineCount} line${lineCount === 1 ? "" : "s"}`);
|
|
532
530
|
}
|
|
533
531
|
|
|
534
532
|
function normalizeDisplayText(text: string): string {
|
|
535
533
|
return text.replace(/\r/g, "");
|
|
536
534
|
}
|
|
537
535
|
|
|
538
|
-
function formatStreamingContent(content: string, uiTheme: Theme): string {
|
|
536
|
+
function formatStreamingContent(content: string, language: string | undefined, uiTheme: Theme): string {
|
|
539
537
|
if (!content) return "";
|
|
540
538
|
const lines = normalizeDisplayText(content).split("\n");
|
|
541
|
-
const
|
|
542
|
-
const
|
|
539
|
+
const totalLines = lines.length;
|
|
540
|
+
const startIndex = Math.max(0, totalLines - WRITE_STREAMING_PREVIEW_LINES);
|
|
541
|
+
const visibleLines = lines.slice(startIndex);
|
|
542
|
+
const hidden = startIndex;
|
|
543
|
+
const highlighted = highlightCode(visibleLines.join("\n"), language);
|
|
544
|
+
const lineNumberWidth = String(totalLines).length;
|
|
543
545
|
|
|
544
546
|
let text = "\n\n";
|
|
545
547
|
if (hidden > 0) {
|
|
546
|
-
text += uiTheme.fg("dim", `… (${hidden} earlier
|
|
548
|
+
text += `${uiTheme.fg("dim", `… (${hidden} earlier line${hidden === 1 ? "" : "s"})`)}\n`;
|
|
547
549
|
}
|
|
548
|
-
for (
|
|
549
|
-
|
|
550
|
+
for (let i = 0; i < highlighted.length; i++) {
|
|
551
|
+
const lineNum = startIndex + i + 1;
|
|
552
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}│`);
|
|
553
|
+
const body = replaceTabs(highlighted[i] ?? "");
|
|
554
|
+
text += ` ${gutter}${body}\n`;
|
|
550
555
|
}
|
|
551
556
|
text += uiTheme.fg("dim", `… (streaming)`);
|
|
552
557
|
return text;
|
|
553
558
|
}
|
|
554
559
|
|
|
555
|
-
function renderContentPreview(
|
|
560
|
+
function renderContentPreview(
|
|
561
|
+
content: string,
|
|
562
|
+
expanded: boolean,
|
|
563
|
+
language: string | undefined,
|
|
564
|
+
uiTheme: Theme,
|
|
565
|
+
): string {
|
|
556
566
|
if (!content) return "";
|
|
557
|
-
const
|
|
558
|
-
const
|
|
559
|
-
const
|
|
560
|
-
const
|
|
567
|
+
const rawLines = normalizeDisplayText(content).split("\n");
|
|
568
|
+
const totalLines = rawLines.length;
|
|
569
|
+
const maxLines = expanded ? totalLines : Math.min(totalLines, WRITE_PREVIEW_LINES);
|
|
570
|
+
const visibleLines = rawLines.slice(0, maxLines);
|
|
571
|
+
const highlighted = highlightCode(visibleLines.join("\n"), language);
|
|
572
|
+
const lineNumberWidth = String(maxLines).length;
|
|
573
|
+
const hidden = totalLines - maxLines;
|
|
561
574
|
|
|
562
575
|
let text = "\n\n";
|
|
563
|
-
for (
|
|
564
|
-
|
|
576
|
+
for (let i = 0; i < highlighted.length; i++) {
|
|
577
|
+
const lineNum = i + 1;
|
|
578
|
+
const gutter = uiTheme.fg("dim", `${String(lineNum).padStart(lineNumberWidth, " ")}│`);
|
|
579
|
+
const body = replaceTabs(highlighted[i] ?? "");
|
|
580
|
+
text += ` ${gutter}${body}\n`;
|
|
565
581
|
}
|
|
566
582
|
if (!expanded && hidden > 0) {
|
|
567
583
|
const hint = formatExpandHint(uiTheme, expanded, hidden > 0);
|
|
568
584
|
const moreLine = `${formatMoreItems(hidden, "line")}${hint ? ` ${hint}` : ""}`;
|
|
569
585
|
text += uiTheme.fg("dim", moreLine);
|
|
570
586
|
}
|
|
571
|
-
return text;
|
|
587
|
+
return text.trimEnd();
|
|
572
588
|
}
|
|
573
589
|
|
|
574
590
|
export const writeToolRenderer = {
|
|
@@ -588,7 +604,7 @@ export const writeToolRenderer = {
|
|
|
588
604
|
}
|
|
589
605
|
|
|
590
606
|
// Show streaming preview of content (tail)
|
|
591
|
-
text += formatStreamingContent(args.content, uiTheme);
|
|
607
|
+
text += formatStreamingContent(args.content, lang, uiTheme);
|
|
592
608
|
|
|
593
609
|
return new Text(text, 0, 0);
|
|
594
610
|
},
|
|
@@ -606,17 +622,17 @@ export const writeToolRenderer = {
|
|
|
606
622
|
const langIcon = uiTheme.fg("muted", uiTheme.getLangIcon(lang));
|
|
607
623
|
const pathDisplay = filePath ? uiTheme.fg("accent", filePath) : uiTheme.fg("toolOutput", "…");
|
|
608
624
|
const lineCount = countLines(fileContent);
|
|
625
|
+
const lineSuffix = formatLineCountSuffix(lineCount, uiTheme);
|
|
609
626
|
|
|
610
627
|
// Build header with status icon
|
|
611
628
|
const header = renderStatusLine(
|
|
612
629
|
{
|
|
613
630
|
icon: "success",
|
|
614
631
|
title: "Write",
|
|
615
|
-
description: `${langIcon} ${pathDisplay}`,
|
|
632
|
+
description: `${langIcon} ${pathDisplay}${lineSuffix}`,
|
|
616
633
|
},
|
|
617
634
|
uiTheme,
|
|
618
635
|
);
|
|
619
|
-
const metadataLine = formatMetadataLine(lineCount, lang ?? "text", uiTheme);
|
|
620
636
|
const diagnostics = result.details?.diagnostics;
|
|
621
637
|
|
|
622
638
|
let cached: RenderCache | undefined;
|
|
@@ -628,8 +644,7 @@ export const writeToolRenderer = {
|
|
|
628
644
|
if (cached?.key === key) return cached.lines;
|
|
629
645
|
|
|
630
646
|
let text = header;
|
|
631
|
-
text +=
|
|
632
|
-
text += renderContentPreview(fileContent, expanded, uiTheme);
|
|
647
|
+
text += renderContentPreview(fileContent, expanded, lang, uiTheme);
|
|
633
648
|
|
|
634
649
|
if (diagnostics) {
|
|
635
650
|
const diagText = formatDiagnostics(diagnostics, expanded, uiTheme, fp =>
|