@oh-my-pi/pi-coding-agent 11.2.3 → 11.3.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 +100 -0
- package/examples/extensions/plan-mode.ts +1 -1
- package/examples/hooks/qna.ts +1 -1
- package/examples/hooks/status-line.ts +1 -1
- package/examples/sdk/11-sessions.ts +1 -1
- package/package.json +8 -8
- package/src/cli/args.ts +9 -6
- package/src/cli/update-cli.ts +2 -2
- package/src/commands/index/index.ts +2 -5
- package/src/commit/agentic/agent.ts +1 -1
- package/src/commit/changelog/index.ts +2 -2
- package/src/config/keybindings.ts +16 -1
- package/src/config/model-registry.ts +25 -20
- package/src/config/model-resolver.ts +8 -8
- package/src/config/resolve-config-value.ts +92 -0
- package/src/config/settings-schema.ts +9 -0
- package/src/config.ts +14 -1
- package/src/export/html/template.css +7 -0
- package/src/export/html/template.generated.ts +1 -1
- package/src/export/html/template.js +33 -16
- package/src/extensibility/custom-commands/bundled/review/index.ts +1 -1
- package/src/extensibility/extensions/index.ts +18 -0
- package/src/extensibility/extensions/loader.ts +15 -0
- package/src/extensibility/extensions/runner.ts +78 -1
- package/src/extensibility/extensions/types.ts +131 -5
- package/src/extensibility/extensions/wrapper.ts +1 -1
- package/src/extensibility/plugins/git-url.ts +270 -0
- package/src/extensibility/plugins/index.ts +2 -0
- package/src/extensibility/slash-commands.ts +45 -0
- package/src/index.ts +7 -0
- package/src/lsp/render.ts +50 -43
- package/src/lsp/utils.ts +2 -2
- package/src/main.ts +11 -10
- package/src/mcp/transports/stdio.ts +3 -5
- package/src/modes/components/custom-message.ts +0 -8
- package/src/modes/components/diff.ts +1 -7
- package/src/modes/components/footer.ts +4 -4
- package/src/modes/components/model-selector.ts +4 -0
- package/src/modes/components/todo-display.ts +13 -3
- package/src/modes/components/tool-execution.ts +30 -16
- package/src/modes/components/tree-selector.ts +50 -19
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +34 -2
- package/src/modes/controllers/input-controller.ts +47 -33
- package/src/modes/controllers/selector-controller.ts +10 -15
- package/src/modes/interactive-mode.ts +50 -38
- package/src/modes/print-mode.ts +6 -0
- package/src/modes/rpc/rpc-client.ts +4 -4
- package/src/modes/rpc/rpc-mode.ts +17 -2
- package/src/modes/rpc/rpc-types.ts +2 -2
- package/src/modes/types.ts +1 -0
- package/src/modes/utils/ui-helpers.ts +3 -1
- package/src/patch/applicator.ts +2 -3
- package/src/patch/fuzzy.ts +1 -1
- package/src/patch/shared.ts +74 -61
- package/src/prompts/system/system-prompt.md +1 -0
- package/src/prompts/tools/task.md +6 -0
- package/src/sdk.ts +15 -11
- package/src/session/agent-session.ts +72 -23
- package/src/session/auth-storage.ts +2 -1
- package/src/session/blob-store.ts +105 -0
- package/src/session/session-manager.ts +107 -44
- package/src/task/executor.ts +19 -9
- package/src/task/render.ts +80 -58
- package/src/tools/ask.ts +28 -5
- package/src/tools/bash.ts +47 -39
- package/src/tools/browser.ts +248 -26
- package/src/tools/calculator.ts +42 -23
- package/src/tools/fetch.ts +33 -16
- package/src/tools/find.ts +57 -22
- package/src/tools/grep.ts +54 -25
- package/src/tools/index.ts +5 -5
- package/src/tools/notebook.ts +19 -6
- package/src/tools/path-utils.ts +26 -1
- package/src/tools/python.ts +20 -14
- package/src/tools/read.ts +21 -8
- package/src/tools/render-utils.ts +5 -45
- package/src/tools/ssh.ts +59 -53
- package/src/tools/submit-result.ts +2 -2
- package/src/tools/todo-write.ts +32 -14
- package/src/tools/truncate.ts +1 -1
- package/src/tools/write.ts +39 -24
- package/src/tui/output-block.ts +61 -3
- package/src/tui/tree-list.ts +4 -4
- package/src/tui/utils.ts +71 -1
- package/src/utils/frontmatter.ts +1 -1
- package/src/utils/title-generator.ts +1 -1
- package/src/utils/tools-manager.ts +18 -2
- package/src/web/scrapers/osv.ts +4 -1
- package/src/web/scrapers/youtube.ts +1 -1
- package/src/web/search/index.ts +1 -1
- package/src/web/search/render.ts +96 -90
package/src/task/render.ts
CHANGED
|
@@ -24,7 +24,7 @@ import {
|
|
|
24
24
|
type ReportFindingDetails,
|
|
25
25
|
type SubmitReviewDetails,
|
|
26
26
|
} from "../tools/review";
|
|
27
|
-
import { renderStatusLine } from "../tui";
|
|
27
|
+
import { Ellipsis, Hasher, type RenderCache, renderStatusLine } from "../tui";
|
|
28
28
|
import { subprocessToolRegistry } from "./subprocess-tool-registry";
|
|
29
29
|
import type { AgentProgress, SingleResult, TaskParams, TaskToolDetails } from "./types";
|
|
30
30
|
|
|
@@ -382,7 +382,13 @@ function formatScalarInline(value: unknown, maxLen: number, _theme: Theme): stri
|
|
|
382
382
|
if (value === undefined) return "undefined";
|
|
383
383
|
if (typeof value === "boolean") return String(value);
|
|
384
384
|
if (typeof value === "number") return String(value);
|
|
385
|
-
if (typeof value === "string")
|
|
385
|
+
if (typeof value === "string") {
|
|
386
|
+
const firstLine = value.split("\n")[0].trim();
|
|
387
|
+
if (firstLine.length === 0) return `"" (${value.split("\n").length} lines)`;
|
|
388
|
+
const preview = truncateToWidth(firstLine, maxLen);
|
|
389
|
+
if (value.includes("\n")) return `"${preview}…" (${value.split("\n").length} lines)`;
|
|
390
|
+
return `"${preview}"`;
|
|
391
|
+
}
|
|
386
392
|
if (Array.isArray(value)) return `[${value.length} items]`;
|
|
387
393
|
if (typeof value === "object") {
|
|
388
394
|
const keys = Object.keys(value);
|
|
@@ -845,74 +851,90 @@ export function renderResult(
|
|
|
845
851
|
options: RenderResultOptions,
|
|
846
852
|
theme: Theme,
|
|
847
853
|
): Component {
|
|
848
|
-
const { expanded, isPartial, spinnerFrame } = options;
|
|
849
854
|
const fallbackText = result.content.find(c => c.type === "text")?.text ?? "";
|
|
850
855
|
const details = result.details;
|
|
851
856
|
|
|
852
857
|
if (!details) {
|
|
853
|
-
// Fallback to simple text
|
|
854
858
|
const text = result.content.find(c => c.type === "text")?.text || "";
|
|
855
859
|
return new Text(theme.fg("dim", truncateToWidth(text, 100)), 0, 0);
|
|
856
860
|
}
|
|
857
861
|
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
const
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
877
|
-
|
|
878
|
-
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
862
|
+
let cached: RenderCache | undefined;
|
|
863
|
+
|
|
864
|
+
return {
|
|
865
|
+
render(width) {
|
|
866
|
+
const { expanded, isPartial, spinnerFrame } = options;
|
|
867
|
+
const key = new Hasher()
|
|
868
|
+
.bool(expanded)
|
|
869
|
+
.bool(isPartial)
|
|
870
|
+
.u32(spinnerFrame ?? 0)
|
|
871
|
+
.u32(width)
|
|
872
|
+
.digest();
|
|
873
|
+
if (cached?.key === key) return cached.lines;
|
|
874
|
+
|
|
875
|
+
const lines: string[] = [];
|
|
876
|
+
|
|
877
|
+
if (isPartial && details.progress) {
|
|
878
|
+
details.progress.forEach((progress, i) => {
|
|
879
|
+
const isLast = i === details.progress!.length - 1;
|
|
880
|
+
lines.push(...renderAgentProgress(progress, isLast, expanded, theme, spinnerFrame));
|
|
881
|
+
});
|
|
882
|
+
} else if (details.results.length > 0) {
|
|
883
|
+
details.results.forEach((res, i) => {
|
|
884
|
+
const isLast = i === details.results.length - 1;
|
|
885
|
+
lines.push(...renderAgentResult(res, isLast, expanded, theme));
|
|
886
|
+
});
|
|
887
|
+
|
|
888
|
+
const abortedCount = details.results.filter(r => r.aborted).length;
|
|
889
|
+
const successCount = details.results.filter(r => !r.aborted && r.exitCode === 0).length;
|
|
890
|
+
const failCount = details.results.length - successCount - abortedCount;
|
|
891
|
+
let summary = `${theme.fg("dim", "Total:")} `;
|
|
892
|
+
if (abortedCount > 0) {
|
|
893
|
+
summary += theme.fg("error", `${abortedCount} aborted`);
|
|
894
|
+
if (successCount > 0 || failCount > 0) summary += theme.sep.dot;
|
|
895
|
+
}
|
|
896
|
+
if (successCount > 0) {
|
|
897
|
+
summary += theme.fg("success", `${successCount} succeeded`);
|
|
898
|
+
if (failCount > 0) summary += theme.sep.dot;
|
|
899
|
+
}
|
|
900
|
+
if (failCount > 0) {
|
|
901
|
+
summary += theme.fg("error", `${failCount} failed`);
|
|
902
|
+
}
|
|
903
|
+
summary += `${theme.sep.dot}${theme.fg("dim", formatDuration(details.totalDurationMs))}`;
|
|
904
|
+
lines.push(summary);
|
|
905
|
+
}
|
|
894
906
|
|
|
895
|
-
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
907
|
+
if (lines.length === 0) {
|
|
908
|
+
const text = fallbackText.trim() ? fallbackText : "No results";
|
|
909
|
+
const result = [theme.fg("dim", truncateToWidth(text, width))];
|
|
910
|
+
cached = { key, lines: result };
|
|
911
|
+
return result;
|
|
912
|
+
}
|
|
899
913
|
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
914
|
+
if (fallbackText.trim()) {
|
|
915
|
+
const summaryLines = fallbackText.split("\n");
|
|
916
|
+
const markerIndex = summaryLines.findIndex(
|
|
917
|
+
line => line.includes("<system-notification>") || line.startsWith("Applied patches:"),
|
|
918
|
+
);
|
|
919
|
+
if (markerIndex >= 0) {
|
|
920
|
+
const extra = summaryLines.slice(markerIndex);
|
|
921
|
+
for (const line of extra) {
|
|
922
|
+
if (!line.trim()) continue;
|
|
923
|
+
lines.push(theme.fg("dim", line));
|
|
924
|
+
}
|
|
925
|
+
}
|
|
910
926
|
}
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
927
|
|
|
914
|
-
|
|
915
|
-
|
|
928
|
+
const indented = lines.map(line =>
|
|
929
|
+
line.length > 0 ? truncateToWidth(` ${line}`, width, Ellipsis.Omit) : "",
|
|
930
|
+
);
|
|
931
|
+
cached = { key, lines: indented };
|
|
932
|
+
return indented;
|
|
933
|
+
},
|
|
934
|
+
invalidate() {
|
|
935
|
+
cached = undefined;
|
|
936
|
+
},
|
|
937
|
+
};
|
|
916
938
|
}
|
|
917
939
|
|
|
918
940
|
function isTaskToolDetails(value: unknown): value is TaskToolDetails {
|
package/src/tools/ask.ts
CHANGED
|
@@ -446,7 +446,7 @@ export const askToolRenderer = {
|
|
|
446
446
|
|
|
447
447
|
renderResult(
|
|
448
448
|
result: { content: Array<{ type: string; text?: string }>; details?: AskToolDetails },
|
|
449
|
-
|
|
449
|
+
_options: RenderResultOptions,
|
|
450
450
|
uiTheme: Theme,
|
|
451
451
|
): Component {
|
|
452
452
|
const { details } = result;
|
|
@@ -454,7 +454,13 @@ export const askToolRenderer = {
|
|
|
454
454
|
const txt = result.content[0];
|
|
455
455
|
const fallback = txt?.type === "text" && txt.text ? txt.text : "";
|
|
456
456
|
const header = renderStatusLine({ icon: "warning", title: "Ask" }, uiTheme);
|
|
457
|
-
|
|
457
|
+
const renderedLines = [header, uiTheme.fg("dim", fallback)];
|
|
458
|
+
return {
|
|
459
|
+
render() {
|
|
460
|
+
return renderedLines;
|
|
461
|
+
},
|
|
462
|
+
invalidate() {},
|
|
463
|
+
};
|
|
458
464
|
}
|
|
459
465
|
|
|
460
466
|
// Multi-part results
|
|
@@ -506,13 +512,24 @@ export const askToolRenderer = {
|
|
|
506
512
|
}
|
|
507
513
|
}
|
|
508
514
|
|
|
509
|
-
return
|
|
515
|
+
return {
|
|
516
|
+
render() {
|
|
517
|
+
return lines;
|
|
518
|
+
},
|
|
519
|
+
invalidate() {},
|
|
520
|
+
};
|
|
510
521
|
}
|
|
511
522
|
|
|
512
523
|
// Single question result
|
|
513
524
|
if (!details.question) {
|
|
514
525
|
const txt = result.content[0];
|
|
515
|
-
|
|
526
|
+
const renderedLines = txt?.type === "text" && txt.text ? txt.text.split("\n") : [""];
|
|
527
|
+
return {
|
|
528
|
+
render() {
|
|
529
|
+
return renderedLines;
|
|
530
|
+
},
|
|
531
|
+
invalidate() {},
|
|
532
|
+
};
|
|
516
533
|
}
|
|
517
534
|
|
|
518
535
|
const hasSelection = details.customInput || (details.selectedOptions && details.selectedOptions.length > 0);
|
|
@@ -535,6 +552,12 @@ export const askToolRenderer = {
|
|
|
535
552
|
text += `\n ${uiTheme.fg("dim", uiTheme.tree.last)} ${uiTheme.styledSymbol("status.warning", "warning")} ${uiTheme.fg("warning", "Cancelled")}`;
|
|
536
553
|
}
|
|
537
554
|
|
|
538
|
-
|
|
555
|
+
const renderedLines = text.split("\n");
|
|
556
|
+
return {
|
|
557
|
+
render() {
|
|
558
|
+
return renderedLines;
|
|
559
|
+
},
|
|
560
|
+
invalidate() {},
|
|
561
|
+
};
|
|
539
562
|
},
|
|
540
563
|
};
|
package/src/tools/bash.ts
CHANGED
|
@@ -2,14 +2,15 @@ import * as path from "node:path";
|
|
|
2
2
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
|
-
import { Type } from "@sinclair/typebox";
|
|
5
|
+
import { type Static, Type } from "@sinclair/typebox";
|
|
6
6
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
7
|
import { type BashExecutorOptions, executeBash } from "../exec/bash-executor";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import { truncateToVisualLines } from "../modes/components/visual-truncate";
|
|
10
10
|
import type { Theme } from "../modes/theme/theme";
|
|
11
11
|
import bashDescription from "../prompts/tools/bash.md" with { type: "text" };
|
|
12
|
-
import {
|
|
12
|
+
import { renderStatusLine } from "../tui";
|
|
13
|
+
import { CachedOutputBlock } from "../tui/output-block";
|
|
13
14
|
import type { ToolSession } from ".";
|
|
14
15
|
import { checkBashInterception } from "./bash-interceptor";
|
|
15
16
|
import { applyHeadTail, normalizeBashCommand } from "./bash-normalize";
|
|
@@ -31,6 +32,8 @@ const bashSchema = Type.Object({
|
|
|
31
32
|
tail: Type.Optional(Type.Number({ description: "Return only last N lines of output" })),
|
|
32
33
|
});
|
|
33
34
|
|
|
35
|
+
export type BashToolInput = Static<typeof bashSchema>;
|
|
36
|
+
|
|
34
37
|
export interface BashToolDetails {
|
|
35
38
|
meta?: OutputMeta;
|
|
36
39
|
}
|
|
@@ -221,46 +224,49 @@ export const bashToolRenderer = {
|
|
|
221
224
|
const cmdText = args ? formatBashCommand(args, uiTheme) : undefined;
|
|
222
225
|
const isError = result.isError === true;
|
|
223
226
|
const header = renderStatusLine({ icon: isError ? "error" : "success", title: "Bash" }, uiTheme);
|
|
224
|
-
const { renderContext } = options;
|
|
225
227
|
const details = result.details;
|
|
226
|
-
const expanded = renderContext?.expanded ?? options.expanded;
|
|
227
|
-
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
228
|
-
|
|
229
|
-
// Get output from context (preferred) or fall back to result content
|
|
230
|
-
const output = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
231
|
-
const displayOutput = output.trimEnd();
|
|
232
|
-
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
233
|
-
|
|
234
|
-
// Build truncation warning lines (static, doesn't depend on width)
|
|
235
228
|
const truncation = details?.meta?.truncation;
|
|
236
|
-
const
|
|
237
|
-
const timeoutLine =
|
|
238
|
-
typeof timeoutSeconds === "number"
|
|
239
|
-
? uiTheme.fg(
|
|
240
|
-
"dim",
|
|
241
|
-
`${uiTheme.format.bracketLeft}Timeout: ${timeoutSeconds}s${uiTheme.format.bracketRight}`,
|
|
242
|
-
)
|
|
243
|
-
: undefined;
|
|
244
|
-
let warningLine: string | undefined;
|
|
245
|
-
if (truncation && !showingFullOutput) {
|
|
246
|
-
const warnings: string[] = [];
|
|
247
|
-
if (truncation?.artifactId) {
|
|
248
|
-
warnings.push(`Full output: artifact://${truncation.artifactId}`);
|
|
249
|
-
}
|
|
250
|
-
if (truncation.truncatedBy === "lines") {
|
|
251
|
-
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
252
|
-
} else {
|
|
253
|
-
warnings.push(
|
|
254
|
-
`Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.outputBytes)} limit)`,
|
|
255
|
-
);
|
|
256
|
-
}
|
|
257
|
-
if (warnings.length > 0) {
|
|
258
|
-
warningLine = uiTheme.fg("warning", wrapBrackets(warnings.join(". "), uiTheme));
|
|
259
|
-
}
|
|
260
|
-
}
|
|
229
|
+
const outputBlock = new CachedOutputBlock();
|
|
261
230
|
|
|
262
231
|
return {
|
|
263
232
|
render: (width: number): string[] => {
|
|
233
|
+
// REACTIVE: read mutable options at render time
|
|
234
|
+
const { renderContext } = options;
|
|
235
|
+
const expanded = renderContext?.expanded ?? options.expanded;
|
|
236
|
+
const previewLines = renderContext?.previewLines ?? BASH_DEFAULT_PREVIEW_LINES;
|
|
237
|
+
|
|
238
|
+
// Get output from context (preferred) or fall back to result content
|
|
239
|
+
const output = renderContext?.output ?? result.content?.find(c => c.type === "text")?.text ?? "";
|
|
240
|
+
const displayOutput = output.trimEnd();
|
|
241
|
+
const showingFullOutput = expanded && renderContext?.isFullOutput === true;
|
|
242
|
+
|
|
243
|
+
// Build truncation warning
|
|
244
|
+
const timeoutSeconds = renderContext?.timeout;
|
|
245
|
+
const timeoutLine =
|
|
246
|
+
typeof timeoutSeconds === "number"
|
|
247
|
+
? uiTheme.fg(
|
|
248
|
+
"dim",
|
|
249
|
+
`${uiTheme.format.bracketLeft}Timeout: ${timeoutSeconds}s${uiTheme.format.bracketRight}`,
|
|
250
|
+
)
|
|
251
|
+
: undefined;
|
|
252
|
+
let warningLine: string | undefined;
|
|
253
|
+
if (truncation && !showingFullOutput) {
|
|
254
|
+
const warnings: string[] = [];
|
|
255
|
+
if (truncation?.artifactId) {
|
|
256
|
+
warnings.push(`Full output: artifact://${truncation.artifactId}`);
|
|
257
|
+
}
|
|
258
|
+
if (truncation.truncatedBy === "lines") {
|
|
259
|
+
warnings.push(`Truncated: showing ${truncation.outputLines} of ${truncation.totalLines} lines`);
|
|
260
|
+
} else {
|
|
261
|
+
warnings.push(
|
|
262
|
+
`Truncated: ${truncation.outputLines} lines shown (${formatBytes(truncation.outputBytes)} limit)`,
|
|
263
|
+
);
|
|
264
|
+
}
|
|
265
|
+
if (warnings.length > 0) {
|
|
266
|
+
warningLine = uiTheme.fg("warning", wrapBrackets(warnings.join(". "), uiTheme));
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
264
270
|
const outputLines: string[] = [];
|
|
265
271
|
const hasOutput = displayOutput.trim().length > 0;
|
|
266
272
|
if (hasOutput) {
|
|
@@ -287,7 +293,7 @@ export const bashToolRenderer = {
|
|
|
287
293
|
if (timeoutLine) outputLines.push(timeoutLine);
|
|
288
294
|
if (warningLine) outputLines.push(warningLine);
|
|
289
295
|
|
|
290
|
-
return
|
|
296
|
+
return outputBlock.render(
|
|
291
297
|
{
|
|
292
298
|
header,
|
|
293
299
|
state: isError ? "error" : "success",
|
|
@@ -300,7 +306,9 @@ export const bashToolRenderer = {
|
|
|
300
306
|
uiTheme,
|
|
301
307
|
);
|
|
302
308
|
},
|
|
303
|
-
invalidate: () => {
|
|
309
|
+
invalidate: () => {
|
|
310
|
+
outputBlock.invalidate();
|
|
311
|
+
},
|
|
304
312
|
};
|
|
305
313
|
},
|
|
306
314
|
mergeCallAndResult: true,
|