@oh-my-pi/pi-coding-agent 14.2.1 → 14.4.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 +143 -1
- package/package.json +19 -19
- package/src/autoresearch/prompt.md +1 -1
- package/src/cli/args.ts +10 -1
- package/src/cli/shell-cli.ts +15 -3
- package/src/commit/agentic/prompts/analyze-file.md +1 -1
- package/src/config/model-registry.ts +67 -15
- package/src/config/prompt-templates.ts +5 -5
- package/src/config/settings-schema.ts +63 -4
- package/src/cursor.ts +3 -8
- package/src/debug/system-info.ts +6 -2
- package/src/discovery/claude.ts +58 -36
- package/src/discovery/helpers.ts +3 -3
- package/src/discovery/opencode.ts +20 -2
- package/src/edit/diff.ts +50 -47
- package/src/edit/index.ts +87 -57
- package/src/edit/line-hash.ts +735 -19
- package/src/edit/modes/apply-patch.ts +0 -9
- package/src/edit/modes/atom.ts +658 -0
- package/src/edit/modes/chunk.ts +144 -78
- package/src/edit/modes/hashline.ts +223 -146
- package/src/edit/modes/patch.ts +5 -9
- package/src/edit/modes/replace.ts +6 -11
- package/src/edit/renderer.ts +112 -143
- package/src/edit/streaming.ts +385 -0
- package/src/exec/bash-executor.ts +58 -5
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +4 -12
- package/src/extensibility/custom-tools/types.ts +2 -0
- package/src/extensibility/custom-tools/wrapper.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +7 -7
- package/src/internal-urls/pi-protocol.ts +0 -2
- package/src/lsp/client.ts +8 -1
- package/src/lsp/defaults.json +2 -1
- package/src/lsp/index.ts +1 -1
- package/src/mcp/render.ts +1 -8
- package/src/modes/acp/acp-agent.ts +76 -2
- package/src/modes/components/assistant-message.ts +5 -34
- package/src/modes/components/diff.ts +23 -14
- package/src/modes/components/footer.ts +21 -16
- package/src/modes/components/hook-editor.ts +1 -1
- package/src/modes/components/settings-defs.ts +6 -1
- package/src/modes/components/todo-reminder.ts +1 -8
- package/src/modes/components/tool-execution.ts +112 -105
- package/src/modes/controllers/input-controller.ts +1 -1
- package/src/modes/controllers/selector-controller.ts +1 -1
- package/src/modes/interactive-mode.ts +0 -2
- package/src/modes/print-mode.ts +8 -0
- package/src/modes/theme/mermaid-cache.ts +13 -52
- package/src/modes/theme/theme.ts +2 -2
- package/src/prompts/agents/librarian.md +1 -1
- package/src/prompts/agents/reviewer.md +4 -4
- package/src/prompts/ci-green-request.md +1 -1
- package/src/prompts/review-request.md +1 -1
- package/src/prompts/system/subagent-system-prompt.md +3 -3
- package/src/prompts/system/subagent-yield-reminder.md +11 -0
- package/src/prompts/system/system-prompt.md +4 -1
- package/src/prompts/tools/ask.md +3 -2
- package/src/prompts/tools/ast-edit.md +15 -19
- package/src/prompts/tools/ast-grep.md +18 -24
- package/src/prompts/tools/atom.md +96 -0
- package/src/prompts/tools/browser.md +1 -0
- package/src/prompts/tools/chunk-edit.md +58 -179
- package/src/prompts/tools/debug.md +4 -5
- package/src/prompts/tools/exit-plan-mode.md +4 -5
- package/src/prompts/tools/find.md +4 -8
- package/src/prompts/tools/github.md +18 -0
- package/src/prompts/tools/grep.md +8 -8
- package/src/prompts/tools/hashline.md +22 -89
- package/src/prompts/tools/{gemini-image.md → image-gen.md} +1 -1
- package/src/prompts/tools/inspect-image.md +6 -6
- package/src/prompts/tools/lsp.md +6 -0
- package/src/prompts/tools/patch.md +12 -19
- package/src/prompts/tools/python.md +3 -2
- package/src/prompts/tools/read-chunk.md +46 -8
- package/src/prompts/tools/read.md +9 -6
- package/src/prompts/tools/ssh.md +8 -17
- package/src/prompts/tools/todo-write.md +54 -41
- package/src/sdk.ts +22 -14
- package/src/session/agent-session.ts +61 -22
- package/src/session/session-manager.ts +228 -57
- package/src/session/streaming-output.ts +11 -0
- package/src/system-prompt.ts +7 -2
- package/src/task/executor.ts +44 -48
- package/src/task/render.ts +11 -13
- package/src/tools/ask.ts +7 -7
- package/src/tools/ast-edit.ts +45 -41
- package/src/tools/ast-grep.ts +77 -85
- package/src/tools/bash.ts +21 -9
- package/src/tools/browser.ts +32 -30
- package/src/tools/calculator.ts +4 -4
- package/src/tools/cancel-job.ts +1 -1
- package/src/tools/checkpoint.ts +2 -2
- package/src/tools/debug.ts +41 -37
- package/src/tools/exit-plan-mode.ts +1 -1
- package/src/tools/find.ts +4 -4
- package/src/tools/gh-renderer.ts +12 -4
- package/src/tools/gh.ts +514 -712
- package/src/tools/grep.ts +115 -130
- package/src/tools/{gemini-image.ts → image-gen.ts} +459 -60
- package/src/tools/index.ts +14 -32
- package/src/tools/inspect-image.ts +3 -3
- package/src/tools/json-tree.ts +114 -114
- package/src/tools/match-line-format.ts +9 -8
- package/src/tools/notebook.ts +8 -7
- package/src/tools/poll-tool.ts +2 -1
- package/src/tools/python.ts +9 -23
- package/src/tools/read.ts +32 -21
- package/src/tools/render-mermaid.ts +1 -1
- package/src/tools/render-utils.ts +18 -0
- package/src/tools/renderers.ts +2 -2
- package/src/tools/report-tool-issue.ts +3 -2
- package/src/tools/resolve.ts +1 -1
- package/src/tools/review.ts +12 -10
- package/src/tools/search-tool-bm25.ts +2 -4
- package/src/tools/sqlite-reader.ts +116 -3
- package/src/tools/ssh.ts +4 -4
- package/src/tools/todo-write.ts +172 -147
- package/src/tools/vim.ts +14 -15
- package/src/tools/write.ts +4 -4
- package/src/tools/{submit-result.ts → yield.ts} +11 -13
- package/src/utils/edit-mode.ts +2 -1
- package/src/utils/file-display-mode.ts +10 -5
- package/src/utils/git.ts +9 -5
- package/src/utils/shell-snapshot.ts +2 -3
- package/src/vim/render.ts +4 -4
- package/src/web/search/providers/codex.ts +129 -6
- package/src/prompts/system/subagent-submit-reminder.md +0 -11
- package/src/prompts/tools/gh-issue-view.md +0 -11
- package/src/prompts/tools/gh-pr-checkout.md +0 -12
- package/src/prompts/tools/gh-pr-diff.md +0 -12
- package/src/prompts/tools/gh-pr-push.md +0 -11
- package/src/prompts/tools/gh-pr-view.md +0 -11
- package/src/prompts/tools/gh-repo-view.md +0 -11
- package/src/prompts/tools/gh-run-watch.md +0 -12
- package/src/prompts/tools/gh-search-issues.md +0 -11
- package/src/prompts/tools/gh-search-prs.md +0 -11
|
@@ -45,7 +45,6 @@ export class PiProtocolHandler implements ProtocolHandler {
|
|
|
45
45
|
content,
|
|
46
46
|
contentType: "text/markdown",
|
|
47
47
|
size: Buffer.byteLength(content, "utf-8"),
|
|
48
|
-
sourcePath: "pi://",
|
|
49
48
|
};
|
|
50
49
|
}
|
|
51
50
|
|
|
@@ -78,7 +77,6 @@ export class PiProtocolHandler implements ProtocolHandler {
|
|
|
78
77
|
content,
|
|
79
78
|
contentType: "text/markdown",
|
|
80
79
|
size: Buffer.byteLength(content, "utf-8"),
|
|
81
|
-
sourcePath: `pi://${normalized}`,
|
|
82
80
|
};
|
|
83
81
|
}
|
|
84
82
|
}
|
package/src/lsp/client.ts
CHANGED
|
@@ -484,7 +484,14 @@ export async function getOrCreateClient(config: ServerConfig, cwd: string, initT
|
|
|
484
484
|
|
|
485
485
|
// Reject any pending requests — the server is gone, they will never complete.
|
|
486
486
|
if (client.pendingRequests.size > 0) {
|
|
487
|
-
|
|
487
|
+
// Strip informational log lines (e.g. marksman's [INF]/[DBG] prefix)
|
|
488
|
+
// — they are startup noise, not actionable errors.
|
|
489
|
+
const rawStderr = proc.peekStderr().trim();
|
|
490
|
+
const stderr = rawStderr
|
|
491
|
+
.split("\n")
|
|
492
|
+
.filter(line => !/^\[\d{2}:\d{2}:\d{2} (?:INF|DBG|VRB)\]/.test(line))
|
|
493
|
+
.join("\n")
|
|
494
|
+
.trim();
|
|
488
495
|
const code = proc.exitCode;
|
|
489
496
|
const err = new Error(
|
|
490
497
|
stderr ? `LSP server exited (code ${code}): ${stderr}` : `LSP server exited unexpectedly (code ${code})`,
|
package/src/lsp/defaults.json
CHANGED
package/src/lsp/index.ts
CHANGED
|
@@ -1115,11 +1115,11 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1115
1115
|
readonly label = "LSP";
|
|
1116
1116
|
readonly description: string;
|
|
1117
1117
|
readonly parameters = lspSchema;
|
|
1118
|
-
readonly strict = true;
|
|
1119
1118
|
readonly renderCall = renderCall;
|
|
1120
1119
|
readonly renderResult = renderResult;
|
|
1121
1120
|
readonly mergeCallAndResult = true;
|
|
1122
1121
|
readonly inline = true;
|
|
1122
|
+
readonly strict = true;
|
|
1123
1123
|
|
|
1124
1124
|
constructor(private readonly session: ToolSession) {
|
|
1125
1125
|
this.description = prompt.render(lspDescription);
|
package/src/mcp/render.ts
CHANGED
|
@@ -17,7 +17,6 @@ import {
|
|
|
17
17
|
JSON_TREE_SCALAR_LEN_COLLAPSED,
|
|
18
18
|
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
19
19
|
renderJsonTreeLines,
|
|
20
|
-
stripInternalArgs,
|
|
21
20
|
} from "../tools/json-tree";
|
|
22
21
|
import { formatExpandHint, truncateToWidth } from "../tools/render-utils";
|
|
23
22
|
import { renderStatusLine } from "../tui";
|
|
@@ -58,13 +57,7 @@ export function renderMCPResult(
|
|
|
58
57
|
lines.push(`${theme.fg("dim", "Args")}`);
|
|
59
58
|
const maxDepth = JSON_TREE_MAX_DEPTH_EXPANDED;
|
|
60
59
|
const maxLines = JSON_TREE_MAX_LINES_EXPANDED;
|
|
61
|
-
const tree = renderJsonTreeLines(
|
|
62
|
-
stripInternalArgs(args),
|
|
63
|
-
theme,
|
|
64
|
-
maxDepth,
|
|
65
|
-
maxLines,
|
|
66
|
-
JSON_TREE_SCALAR_LEN_EXPANDED,
|
|
67
|
-
);
|
|
60
|
+
const tree = renderJsonTreeLines(args, theme, maxDepth, maxLines, JSON_TREE_SCALAR_LEN_EXPANDED);
|
|
68
61
|
for (const line of tree.lines) {
|
|
69
62
|
lines.push(line);
|
|
70
63
|
}
|
|
@@ -39,11 +39,14 @@ import {
|
|
|
39
39
|
} from "@agentclientprotocol/sdk";
|
|
40
40
|
import type { Model } from "@oh-my-pi/pi-ai";
|
|
41
41
|
import { logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
42
|
+
import { disableProvider, enableProvider } from "../../capability";
|
|
43
|
+
import { Settings } from "../../config/settings";
|
|
42
44
|
import type { ExtensionUIContext } from "../../extensibility/extensions";
|
|
43
45
|
import { runExtensionCompact } from "../../extensibility/extensions/compact-handler";
|
|
44
46
|
import { loadSlashCommands } from "../../extensibility/slash-commands";
|
|
45
47
|
import { MCPManager } from "../../mcp/manager";
|
|
46
48
|
import type { MCPServerConfig } from "../../mcp/types";
|
|
49
|
+
import { loadAllExtensions } from "../../modes/components/extensions/state-manager";
|
|
47
50
|
import { theme } from "../../modes/theme/theme";
|
|
48
51
|
import type { AgentSession, AgentSessionEvent } from "../../session/agent-session";
|
|
49
52
|
import {
|
|
@@ -379,8 +382,79 @@ export class AcpAgent implements Agent {
|
|
|
379
382
|
}
|
|
380
383
|
}
|
|
381
384
|
|
|
382
|
-
async extMethod(
|
|
383
|
-
|
|
385
|
+
async extMethod(method: string, params: { [key: string]: unknown }): Promise<{ [key: string]: unknown }> {
|
|
386
|
+
switch (method) {
|
|
387
|
+
case "omp/sessions/listAll": {
|
|
388
|
+
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(5000, params.limit as number)) : 1000;
|
|
389
|
+
const sessions = await SessionManager.listAll();
|
|
390
|
+
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
391
|
+
return {
|
|
392
|
+
sessions: sorted.map(s => this.#toSessionInfo(s)),
|
|
393
|
+
total: sessions.length,
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
case "omp/projects/list": {
|
|
397
|
+
const sessions = await SessionManager.listAll();
|
|
398
|
+
const buckets = new Map<
|
|
399
|
+
string,
|
|
400
|
+
{ cwd: string; sessionCount: number; lastActivityAt: number; lastTitle: string }
|
|
401
|
+
>();
|
|
402
|
+
for (const s of sessions) {
|
|
403
|
+
if (!s.cwd) continue;
|
|
404
|
+
const ts = s.modified.getTime();
|
|
405
|
+
const existing = buckets.get(s.cwd);
|
|
406
|
+
if (existing) {
|
|
407
|
+
existing.sessionCount += 1;
|
|
408
|
+
if (ts > existing.lastActivityAt) {
|
|
409
|
+
existing.lastActivityAt = ts;
|
|
410
|
+
existing.lastTitle = s.title ?? "";
|
|
411
|
+
}
|
|
412
|
+
} else {
|
|
413
|
+
buckets.set(s.cwd, {
|
|
414
|
+
cwd: s.cwd,
|
|
415
|
+
sessionCount: 1,
|
|
416
|
+
lastActivityAt: ts,
|
|
417
|
+
lastTitle: s.title ?? "",
|
|
418
|
+
});
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
const projects = Array.from(buckets.values()).sort((a, b) => b.lastActivityAt - a.lastActivityAt);
|
|
422
|
+
return { projects, totalSessions: sessions.length };
|
|
423
|
+
}
|
|
424
|
+
case "omp/chats/byCwd": {
|
|
425
|
+
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
426
|
+
if (!cwd) throw new Error("cwd required");
|
|
427
|
+
const limit = typeof params.limit === "number" ? Math.max(1, Math.min(500, params.limit as number)) : 100;
|
|
428
|
+
const sessions = await SessionManager.list(cwd);
|
|
429
|
+
const sorted = sessions.sort((l, r) => r.modified.getTime() - l.modified.getTime()).slice(0, limit);
|
|
430
|
+
return { sessions: sorted.map(s => this.#toSessionInfo(s)) };
|
|
431
|
+
}
|
|
432
|
+
case "omp/usage": {
|
|
433
|
+
const [firstRecord] = this.#sessions.values();
|
|
434
|
+
const target = firstRecord?.session ?? this.#initialSession;
|
|
435
|
+
const reports = await target.fetchUsageReports();
|
|
436
|
+
return { reports: reports ?? [] };
|
|
437
|
+
}
|
|
438
|
+
case "omp/extensions": {
|
|
439
|
+
const cwd = typeof params.cwd === "string" ? (params.cwd as string) : undefined;
|
|
440
|
+
const sm = await Settings.init();
|
|
441
|
+
const disabledIds = (sm.get("disabledExtensions") as string[] | undefined) ?? [];
|
|
442
|
+
const extensions = await loadAllExtensions(cwd, disabledIds);
|
|
443
|
+
return { extensions: extensions as unknown as Array<{ [key: string]: unknown }> };
|
|
444
|
+
}
|
|
445
|
+
case "omp/extensions/toggle": {
|
|
446
|
+
const providerId = params.providerId;
|
|
447
|
+
if (typeof providerId !== "string") throw new Error("providerId required");
|
|
448
|
+
if (params.enabled === false) {
|
|
449
|
+
disableProvider(providerId);
|
|
450
|
+
return { enabled: false };
|
|
451
|
+
}
|
|
452
|
+
enableProvider(providerId);
|
|
453
|
+
return { enabled: true };
|
|
454
|
+
}
|
|
455
|
+
default:
|
|
456
|
+
throw new Error(`Unknown ACP ext method: ${method}`);
|
|
457
|
+
}
|
|
384
458
|
}
|
|
385
459
|
|
|
386
460
|
async extNotification(_method: string, _params: { [key: string]: unknown }): Promise<void> {}
|
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import type { AssistantMessage, ImageContent, Usage } from "@oh-my-pi/pi-ai";
|
|
2
2
|
import { Container, Image, ImageProtocol, Markdown, Spacer, TERMINAL, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
-
import { formatNumber
|
|
3
|
+
import { formatNumber } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { settings } from "../../config/settings";
|
|
5
|
-
import { hasPendingMermaid, prerenderMermaid } from "../../modes/theme/mermaid-cache";
|
|
6
5
|
import { getMarkdownTheme, theme } from "../../modes/theme/theme";
|
|
7
6
|
import { resolveImageOptions } from "../../tools/render-utils";
|
|
8
7
|
|
|
@@ -12,7 +11,6 @@ import { resolveImageOptions } from "../../tools/render-utils";
|
|
|
12
11
|
export class AssistantMessageComponent extends Container {
|
|
13
12
|
#contentContainer: Container;
|
|
14
13
|
#lastMessage?: AssistantMessage;
|
|
15
|
-
#prerenderInFlight = false;
|
|
16
14
|
#toolImagesByCallId = new Map<string, ImageContent[]>();
|
|
17
15
|
#usageInfo?: Usage;
|
|
18
16
|
|
|
@@ -85,34 +83,6 @@ export class AssistantMessageComponent extends Container {
|
|
|
85
83
|
this.#contentContainer.addChild(new Text(theme.fg("toolOutput", `[Image: ${image.mimeType}]`), 1, 0));
|
|
86
84
|
}
|
|
87
85
|
}
|
|
88
|
-
#triggerMermaidPrerender(message: AssistantMessage): void {
|
|
89
|
-
if (!TERMINAL.imageProtocol || this.#prerenderInFlight) return;
|
|
90
|
-
|
|
91
|
-
// Check if any text content has pending mermaid blocks
|
|
92
|
-
const hasPending = message.content.some(c => c.type === "text" && c.text.trim() && hasPendingMermaid(c.text));
|
|
93
|
-
if (!hasPending) return;
|
|
94
|
-
|
|
95
|
-
this.#prerenderInFlight = true;
|
|
96
|
-
|
|
97
|
-
// Fire off background prerender
|
|
98
|
-
void (async () => {
|
|
99
|
-
try {
|
|
100
|
-
for (const content of message.content) {
|
|
101
|
-
if (content.type === "text" && content.text.trim() && hasPendingMermaid(content.text)) {
|
|
102
|
-
prerenderMermaid(content.text);
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
} catch (error) {
|
|
106
|
-
logger.warn("Background mermaid prerender failed", {
|
|
107
|
-
error: error instanceof Error ? error.message : String(error),
|
|
108
|
-
});
|
|
109
|
-
} finally {
|
|
110
|
-
this.#prerenderInFlight = false;
|
|
111
|
-
// Invalidate to re-render with cached images
|
|
112
|
-
this.invalidate();
|
|
113
|
-
}
|
|
114
|
-
})();
|
|
115
|
-
}
|
|
116
86
|
|
|
117
87
|
updateContent(message: AssistantMessage): void {
|
|
118
88
|
this.#lastMessage = message;
|
|
@@ -120,9 +90,6 @@ export class AssistantMessageComponent extends Container {
|
|
|
120
90
|
// Clear content container
|
|
121
91
|
this.#contentContainer.clear();
|
|
122
92
|
|
|
123
|
-
// Trigger background mermaid pre-rendering if needed
|
|
124
|
-
this.#triggerMermaidPrerender(message);
|
|
125
|
-
|
|
126
93
|
const hasVisibleContent = message.content.some(
|
|
127
94
|
c => (c.type === "text" && c.text.trim()) || (c.type === "thinking" && c.thinking.trim()),
|
|
128
95
|
);
|
|
@@ -188,6 +155,10 @@ export class AssistantMessageComponent extends Container {
|
|
|
188
155
|
this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${errorMsg}`), 1, 0));
|
|
189
156
|
}
|
|
190
157
|
}
|
|
158
|
+
if (message.errorMessage && message.stopReason !== "aborted" && message.stopReason !== "error") {
|
|
159
|
+
this.#contentContainer.addChild(new Spacer(1));
|
|
160
|
+
this.#contentContainer.addChild(new Text(theme.fg("error", `Error: ${message.errorMessage}`), 1, 0));
|
|
161
|
+
}
|
|
191
162
|
|
|
192
163
|
// Token usage metadata
|
|
193
164
|
if (settings.get("display.showTokenUsage") && this.#usageInfo) {
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { getIndentation } from "@oh-my-pi/pi-utils";
|
|
2
2
|
import * as Diff from "diff";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
|
-
import { replaceTabs } from "../../tools/render-utils";
|
|
4
|
+
import { type CodeFrameMarker, formatCodeFrameLine, replaceTabs } from "../../tools/render-utils";
|
|
5
5
|
|
|
6
6
|
/** SGR dim on / normal intensity — additive, preserves fg/bg colors. */
|
|
7
7
|
const DIM = "\x1b[2m";
|
|
@@ -37,14 +37,14 @@ function visualizeIndent(text: string, filePath?: string): string {
|
|
|
37
37
|
* Parse diff line to extract prefix, line number, and content.
|
|
38
38
|
* Supported formats: "+123|content" (canonical) and "+123 content" (legacy).
|
|
39
39
|
*/
|
|
40
|
-
function parseDiffLine(line: string): { prefix:
|
|
40
|
+
function parseDiffLine(line: string): { prefix: CodeFrameMarker; lineNum: string; content: string } | null {
|
|
41
41
|
const canonical = line.match(/^([+-\s])(\s*\d+)\|(.*)$/);
|
|
42
42
|
if (canonical) {
|
|
43
|
-
return { prefix: canonical[1], lineNum: canonical[2], content: canonical[3] };
|
|
43
|
+
return { prefix: canonical[1] as CodeFrameMarker, lineNum: canonical[2] ?? "", content: canonical[3] ?? "" };
|
|
44
44
|
}
|
|
45
45
|
const legacy = line.match(/^([+-\s])(?:(\s*\d+)\s)?(.*)$/);
|
|
46
46
|
if (!legacy) return null;
|
|
47
|
-
return { prefix: legacy[1], lineNum: legacy[2] ?? "", content: legacy[3] };
|
|
47
|
+
return { prefix: legacy[1] as CodeFrameMarker, lineNum: legacy[2] ?? "", content: legacy[3] ?? "" };
|
|
48
48
|
}
|
|
49
49
|
|
|
50
50
|
/**
|
|
@@ -108,12 +108,27 @@ export interface RenderDiffOptions {
|
|
|
108
108
|
export function renderDiff(diffText: string, options: RenderDiffOptions = {}): string {
|
|
109
109
|
const lines = diffText.split("\n");
|
|
110
110
|
const result: string[] = [];
|
|
111
|
-
|
|
112
|
-
const
|
|
111
|
+
const parsedLines = lines.map(parseDiffLine);
|
|
112
|
+
const lineNumberWidth = parsedLines.reduce((width, parsed) => {
|
|
113
|
+
const lineNumber = parsed?.lineNum.trim() ?? "";
|
|
114
|
+
return Math.max(width, lineNumber.length);
|
|
115
|
+
}, 0);
|
|
116
|
+
|
|
117
|
+
// Track the line number rendered on the previous emitted line so we can
|
|
118
|
+
// blank out duplicate gutters. Two cases trigger this:
|
|
119
|
+
// 1. Single-line replacement (`-N` followed by `+N`) — the `+N` repeats `N`.
|
|
120
|
+
// 2. Insertion followed by context (`+N` then ` N` if producer used oldLine).
|
|
121
|
+
let prevLineNum = "";
|
|
122
|
+
|
|
123
|
+
const formatLine = (prefix: CodeFrameMarker, lineNum: string, content: string): string => {
|
|
113
124
|
if (lineNum.trim().length === 0) {
|
|
125
|
+
prevLineNum = "";
|
|
114
126
|
return `${prefix}${content}`;
|
|
115
127
|
}
|
|
116
|
-
|
|
128
|
+
const trimmed = lineNum.trim();
|
|
129
|
+
const displayNum = trimmed === prevLineNum ? "" : trimmed;
|
|
130
|
+
prevLineNum = trimmed;
|
|
131
|
+
return formatCodeFrameLine(prefix, displayNum, content, lineNumberWidth);
|
|
117
132
|
};
|
|
118
133
|
|
|
119
134
|
let i = 0;
|
|
@@ -122,13 +137,13 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
122
137
|
const parsed = parseDiffLine(line);
|
|
123
138
|
|
|
124
139
|
if (!parsed) {
|
|
140
|
+
prevLineNum = "";
|
|
125
141
|
result.push(theme.fg("toolDiffContext", line));
|
|
126
142
|
i++;
|
|
127
143
|
continue;
|
|
128
144
|
}
|
|
129
145
|
|
|
130
146
|
if (parsed.prefix === "-") {
|
|
131
|
-
// Collect consecutive removed lines
|
|
132
147
|
const removedLines: { lineNum: string; content: string }[] = [];
|
|
133
148
|
while (i < lines.length) {
|
|
134
149
|
const p = parseDiffLine(lines[i]);
|
|
@@ -137,7 +152,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
137
152
|
i++;
|
|
138
153
|
}
|
|
139
154
|
|
|
140
|
-
// Collect consecutive added lines
|
|
141
155
|
const addedLines: { lineNum: string; content: string }[] = [];
|
|
142
156
|
while (i < lines.length) {
|
|
143
157
|
const p = parseDiffLine(lines[i]);
|
|
@@ -146,8 +160,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
146
160
|
i++;
|
|
147
161
|
}
|
|
148
162
|
|
|
149
|
-
// Only do intra-line diffing when there's exactly one removed and one added line
|
|
150
|
-
// (indicating a single line modification). Otherwise, show lines as-is.
|
|
151
163
|
if (removedLines.length === 1 && addedLines.length === 1) {
|
|
152
164
|
const removed = removedLines[0];
|
|
153
165
|
const added = addedLines[0];
|
|
@@ -167,7 +179,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
167
179
|
theme.fg("toolDiffAdded", formatLine("+", added.lineNum, visualizeIndent(addedLine, options.filePath))),
|
|
168
180
|
);
|
|
169
181
|
} else {
|
|
170
|
-
// Show all removed lines first, then all added lines
|
|
171
182
|
for (const removed of removedLines) {
|
|
172
183
|
result.push(
|
|
173
184
|
theme.fg(
|
|
@@ -186,7 +197,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
186
197
|
}
|
|
187
198
|
}
|
|
188
199
|
} else if (parsed.prefix === "+") {
|
|
189
|
-
// Standalone added line
|
|
190
200
|
result.push(
|
|
191
201
|
theme.fg(
|
|
192
202
|
"toolDiffAdded",
|
|
@@ -195,7 +205,6 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
195
205
|
);
|
|
196
206
|
i++;
|
|
197
207
|
} else {
|
|
198
|
-
// Context line
|
|
199
208
|
result.push(
|
|
200
209
|
theme.fg(
|
|
201
210
|
"toolDiffContext",
|
|
@@ -56,22 +56,27 @@ export class FooterComponent implements Component {
|
|
|
56
56
|
this.#gitWatcher = null;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
|
-
git.head
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
this.#
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
59
|
+
void git.head
|
|
60
|
+
.resolve(getProjectDir())
|
|
61
|
+
.then(head => {
|
|
62
|
+
if (!head) {
|
|
63
|
+
return;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
try {
|
|
67
|
+
this.#gitWatcher = fs.watch(head.headPath, () => {
|
|
68
|
+
this.#cachedBranch = undefined; // Invalidate cache
|
|
69
|
+
if (this.#onBranchChange) {
|
|
70
|
+
this.#onBranchChange();
|
|
71
|
+
}
|
|
72
|
+
});
|
|
73
|
+
} catch {
|
|
74
|
+
// Silently fail if we can't watch
|
|
75
|
+
}
|
|
76
|
+
})
|
|
77
|
+
.catch(() => {
|
|
78
|
+
this.#cachedBranch = null;
|
|
79
|
+
});
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
/**
|
|
@@ -136,7 +136,7 @@ export class HookEditorComponent extends Container {
|
|
|
136
136
|
const editorCmd = getEditorCommand();
|
|
137
137
|
if (!editorCmd) return;
|
|
138
138
|
|
|
139
|
-
const currentText = this.#editor.
|
|
139
|
+
const currentText = this.#editor.getExpandedText();
|
|
140
140
|
try {
|
|
141
141
|
this.#tui.stop();
|
|
142
142
|
const result = await openInEditor(editorCmd, currentText);
|
|
@@ -355,7 +355,12 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
355
355
|
{ value: "parallel", label: "Parallel", description: "Requires PARALLEL_API_KEY" },
|
|
356
356
|
],
|
|
357
357
|
"providers.image": [
|
|
358
|
-
{
|
|
358
|
+
{
|
|
359
|
+
value: "auto",
|
|
360
|
+
label: "Auto",
|
|
361
|
+
description: "Priority: GPT model image tool > Antigravity > OpenRouter > Gemini",
|
|
362
|
+
},
|
|
363
|
+
{ value: "openai", label: "OpenAI", description: "Uses the active GPT Responses/Codex model" },
|
|
359
364
|
{ value: "gemini", label: "Gemini", description: "Requires GEMINI_API_KEY" },
|
|
360
365
|
{ value: "openrouter", label: "OpenRouter", description: "Requires OPENROUTER_API_KEY" },
|
|
361
366
|
],
|
|
@@ -34,14 +34,7 @@ export class TodoReminderComponent extends Container {
|
|
|
34
34
|
this.#box.addChild(new Text(header, 0, 0));
|
|
35
35
|
this.#box.addChild(new Spacer(1));
|
|
36
36
|
|
|
37
|
-
const todoList = this.todos
|
|
38
|
-
.map(t => {
|
|
39
|
-
const line = ` ${theme.checkbox.unchecked} ${t.content}`;
|
|
40
|
-
if (!t.details) return line;
|
|
41
|
-
const detailLines = t.details.split("\n").map(l => ` ${l}`);
|
|
42
|
-
return [line, ...detailLines].join("\n");
|
|
43
|
-
})
|
|
44
|
-
.join("\n");
|
|
37
|
+
const todoList = this.todos.map(todo => ` ${theme.checkbox.unchecked} ${todo.content}`).join("\n");
|
|
45
38
|
this.#box.addChild(new Text(theme.italic(todoList), 0, 0));
|
|
46
39
|
}
|
|
47
40
|
}
|