@oh-my-pi/pi-coding-agent 13.19.0 → 14.0.2
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 +266 -1
- package/package.json +86 -20
- package/scripts/format-prompts.ts +2 -2
- package/src/autoresearch/apply-contract-to-state.ts +24 -0
- package/src/autoresearch/contract.ts +0 -44
- package/src/autoresearch/dashboard.ts +1 -2
- package/src/autoresearch/git.ts +91 -0
- package/src/autoresearch/helpers.ts +49 -0
- package/src/autoresearch/index.ts +28 -187
- package/src/autoresearch/prompt.md +26 -9
- package/src/autoresearch/state.ts +0 -6
- package/src/autoresearch/tools/init-experiment.ts +202 -117
- package/src/autoresearch/tools/log-experiment.ts +83 -125
- package/src/autoresearch/tools/run-experiment.ts +48 -10
- package/src/autoresearch/types.ts +2 -2
- package/src/capability/index.ts +4 -2
- package/src/cli/file-processor.ts +3 -3
- package/src/cli/grep-cli.ts +8 -8
- package/src/cli/grievances-cli.ts +78 -0
- package/src/cli/read-cli.ts +67 -0
- package/src/cli/setup-cli.ts +4 -4
- package/src/cli/update-cli.ts +3 -3
- package/src/cli.ts +2 -0
- package/src/commands/grep.ts +6 -1
- package/src/commands/grievances.ts +20 -0
- package/src/commands/read.ts +33 -0
- package/src/commit/agentic/agent.ts +5 -5
- package/src/commit/agentic/index.ts +3 -4
- package/src/commit/agentic/tools/analyze-file.ts +3 -3
- package/src/commit/agentic/validation.ts +1 -1
- package/src/commit/analysis/conventional.ts +4 -4
- package/src/commit/analysis/summary.ts +3 -3
- package/src/commit/changelog/generate.ts +4 -4
- package/src/commit/map-reduce/map-phase.ts +4 -4
- package/src/commit/map-reduce/reduce-phase.ts +4 -4
- package/src/commit/pipeline.ts +3 -4
- package/src/config/prompt-templates.ts +44 -226
- package/src/config/resolve-config-value.ts +4 -2
- package/src/config/settings-schema.ts +54 -2
- package/src/config/settings.ts +25 -26
- package/src/dap/client.ts +674 -0
- package/src/dap/config.ts +150 -0
- package/src/dap/defaults.json +211 -0
- package/src/dap/index.ts +4 -0
- package/src/dap/session.ts +1255 -0
- package/src/dap/types.ts +600 -0
- package/src/debug/log-viewer.ts +3 -2
- package/src/discovery/builtin.ts +1 -2
- package/src/discovery/codex.ts +2 -2
- package/src/discovery/github.ts +2 -1
- package/src/discovery/helpers.ts +2 -2
- package/src/discovery/opencode.ts +2 -2
- package/src/edit/diff.ts +818 -0
- package/src/edit/index.ts +309 -0
- package/src/edit/line-hash.ts +67 -0
- package/src/edit/modes/chunk.ts +454 -0
- package/src/{patch → edit/modes}/hashline.ts +741 -361
- package/src/{patch/applicator.ts → edit/modes/patch.ts} +420 -117
- package/src/{patch/fuzzy.ts → edit/modes/replace.ts} +519 -197
- package/src/{patch → edit}/normalize.ts +97 -76
- package/src/{patch/shared.ts → edit/renderer.ts} +181 -108
- package/src/exec/bash-executor.ts +4 -2
- package/src/exec/idle-timeout-watchdog.ts +126 -0
- package/src/exec/non-interactive-env.ts +5 -0
- package/src/extensibility/custom-commands/bundled/ci-green/index.ts +2 -2
- package/src/extensibility/custom-commands/bundled/review/index.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +1 -2
- package/src/extensibility/custom-tools/loader.ts +34 -11
- package/src/extensibility/extensions/loader.ts +9 -4
- package/src/extensibility/extensions/runner.ts +24 -1
- package/src/extensibility/extensions/types.ts +1 -1
- package/src/extensibility/hooks/loader.ts +5 -6
- package/src/extensibility/hooks/types.ts +1 -1
- package/src/extensibility/plugins/doctor.ts +2 -1
- package/src/extensibility/slash-commands.ts +3 -7
- package/src/index.ts +2 -1
- package/src/internal-urls/docs-index.generated.ts +11 -11
- package/src/ipy/executor.ts +58 -17
- package/src/ipy/gateway-coordinator.ts +6 -4
- package/src/ipy/kernel.ts +45 -22
- package/src/ipy/runtime.ts +2 -2
- package/src/lsp/client.ts +7 -4
- package/src/lsp/clients/lsp-linter-client.ts +4 -4
- package/src/lsp/config.ts +2 -2
- package/src/lsp/defaults.json +688 -154
- package/src/lsp/index.ts +234 -45
- package/src/lsp/lspmux.ts +2 -2
- package/src/lsp/startup-events.ts +13 -0
- package/src/lsp/types.ts +12 -1
- package/src/lsp/utils.ts +8 -1
- package/src/main.ts +102 -46
- package/src/memories/index.ts +4 -5
- package/src/modes/acp/acp-agent.ts +563 -163
- package/src/modes/acp/acp-event-mapper.ts +9 -1
- package/src/modes/acp/acp-mode.ts +4 -2
- package/src/modes/components/agent-dashboard.ts +3 -4
- package/src/modes/components/diff.ts +6 -7
- package/src/modes/components/read-tool-group.ts +6 -12
- package/src/modes/components/settings-defs.ts +5 -0
- package/src/modes/components/tool-execution.ts +1 -1
- package/src/modes/components/welcome.ts +1 -1
- package/src/modes/controllers/btw-controller.ts +2 -2
- package/src/modes/controllers/command-controller.ts +3 -2
- package/src/modes/controllers/input-controller.ts +12 -8
- package/src/modes/index.ts +20 -2
- package/src/modes/interactive-mode.ts +94 -37
- package/src/modes/rpc/host-tools.ts +186 -0
- package/src/modes/rpc/rpc-client.ts +178 -13
- package/src/modes/rpc/rpc-mode.ts +73 -3
- package/src/modes/rpc/rpc-types.ts +53 -1
- package/src/modes/theme/theme.ts +80 -8
- package/src/modes/types.ts +2 -2
- package/src/prompts/system/system-prompt.md +2 -1
- package/src/prompts/tools/chunk-edit.md +219 -0
- package/src/prompts/tools/debug.md +43 -0
- package/src/prompts/tools/grep.md +3 -0
- package/src/prompts/tools/lsp.md +5 -5
- package/src/prompts/tools/read-chunk.md +17 -0
- package/src/prompts/tools/read.md +19 -5
- package/src/sdk.ts +190 -154
- package/src/secrets/obfuscator.ts +1 -1
- package/src/session/agent-session.ts +306 -256
- package/src/session/agent-storage.ts +12 -12
- package/src/session/compaction/branch-summarization.ts +3 -3
- package/src/session/compaction/compaction.ts +5 -6
- package/src/session/compaction/utils.ts +3 -3
- package/src/session/history-storage.ts +62 -19
- package/src/session/messages.ts +3 -3
- package/src/session/session-dump-format.ts +203 -0
- package/src/session/session-storage.ts +4 -2
- package/src/session/streaming-output.ts +1 -1
- package/src/session/tool-choice-queue.ts +213 -0
- package/src/slash-commands/builtin-registry.ts +56 -8
- package/src/ssh/connection-manager.ts +2 -2
- package/src/ssh/sshfs-mount.ts +5 -5
- package/src/stt/downloader.ts +4 -4
- package/src/stt/recorder.ts +4 -4
- package/src/stt/transcriber.ts +2 -2
- package/src/system-prompt.ts +21 -13
- package/src/task/agents.ts +5 -6
- package/src/task/commands.ts +2 -5
- package/src/task/executor.ts +4 -4
- package/src/task/index.ts +3 -4
- package/src/task/template.ts +2 -2
- package/src/task/worktree.ts +4 -4
- package/src/tools/ask.ts +2 -3
- package/src/tools/ast-edit.ts +7 -7
- package/src/tools/ast-grep.ts +7 -7
- package/src/tools/auto-generated-guard.ts +36 -41
- package/src/tools/await-tool.ts +2 -2
- package/src/tools/bash.ts +5 -23
- package/src/tools/browser.ts +4 -5
- package/src/tools/calculator.ts +2 -3
- package/src/tools/cancel-job.ts +2 -2
- package/src/tools/checkpoint.ts +3 -3
- package/src/tools/debug.ts +1007 -0
- package/src/tools/exit-plan-mode.ts +2 -3
- package/src/tools/fetch.ts +67 -3
- package/src/tools/find.ts +4 -5
- package/src/tools/fs-cache-invalidation.ts +5 -0
- package/src/tools/gemini-image.ts +13 -5
- package/src/tools/gh.ts +10 -11
- package/src/tools/grep.ts +57 -9
- package/src/tools/index.ts +44 -22
- package/src/tools/inspect-image.ts +4 -4
- package/src/tools/output-meta.ts +1 -1
- package/src/tools/python.ts +19 -6
- package/src/tools/read.ts +198 -67
- package/src/tools/render-mermaid.ts +2 -3
- package/src/tools/render-utils.ts +20 -6
- package/src/tools/renderers.ts +3 -1
- package/src/tools/report-tool-issue.ts +80 -0
- package/src/tools/resolve.ts +70 -39
- package/src/tools/search-tool-bm25.ts +2 -2
- package/src/tools/ssh.ts +2 -2
- package/src/tools/todo-write.ts +2 -2
- package/src/tools/tool-timeouts.ts +1 -0
- package/src/tools/write.ts +5 -6
- package/src/tui/tree-list.ts +3 -1
- package/src/utils/clipboard.ts +80 -0
- package/src/utils/commit-message-generator.ts +2 -3
- package/src/utils/edit-mode.ts +49 -0
- package/src/utils/file-display-mode.ts +6 -5
- package/src/utils/file-mentions.ts +8 -7
- package/src/utils/git.ts +4 -4
- package/src/utils/image-loading.ts +98 -0
- package/src/utils/title-generator.ts +2 -3
- package/src/utils/tools-manager.ts +6 -6
- package/src/web/scrapers/choosealicense.ts +1 -1
- package/src/web/search/index.ts +3 -3
- package/src/autoresearch/command-initialize.md +0 -34
- package/src/patch/diff.ts +0 -433
- package/src/patch/index.ts +0 -888
- package/src/patch/parser.ts +0 -532
- package/src/patch/types.ts +0 -292
- package/src/prompts/agents/oracle.md +0 -77
- package/src/tools/pending-action.ts +0 -49
- package/src/utils/child-process.ts +0 -88
- package/src/utils/frontmatter.ts +0 -117
- package/src/utils/image-input.ts +0 -274
- package/src/utils/mime.ts +0 -53
- package/src/utils/prompt-format.ts +0 -170
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* report_tool_issue — automated QA tool for tracking unexpected tool behavior.
|
|
3
|
+
*
|
|
4
|
+
* Enabled when PI_AUTO_QA=1 or the dev.autoqa setting is on.
|
|
5
|
+
* Always injected into every agent (including subagents) regardless of tool selection.
|
|
6
|
+
* Records grievances to a local SQLite database; never throws.
|
|
7
|
+
*/
|
|
8
|
+
import { Database } from "bun:sqlite";
|
|
9
|
+
import path from "node:path";
|
|
10
|
+
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
11
|
+
import { $env, getAgentDir, logger, VERSION } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { Type } from "@sinclair/typebox";
|
|
13
|
+
import type { Settings } from "..";
|
|
14
|
+
import type { ToolSession } from "./index";
|
|
15
|
+
|
|
16
|
+
const ReportToolIssueParams = Type.Object({
|
|
17
|
+
tool: Type.String({ description: "Name of the tool that behaved unexpectedly" }),
|
|
18
|
+
report: Type.String({ description: "Description of what was unexpected about the tool's behavior" }),
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
export function isAutoQaEnabled(settings?: Settings): boolean {
|
|
22
|
+
return $env.PI_AUTO_QA === "1" || !!settings?.get("dev.autoqa");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function getAutoQaDbPath(): string {
|
|
26
|
+
return path.join(getAgentDir(), "autoqa.db");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let cachedDb: Database | null = null;
|
|
30
|
+
|
|
31
|
+
function openDb(): Database | null {
|
|
32
|
+
if (cachedDb) return cachedDb;
|
|
33
|
+
try {
|
|
34
|
+
const db = new Database(getAutoQaDbPath());
|
|
35
|
+
db.run(`
|
|
36
|
+
PRAGMA journal_mode=WAL;
|
|
37
|
+
PRAGMA synchronous=NORMAL;
|
|
38
|
+
PRAGMA busy_timeout=5000;
|
|
39
|
+
CREATE TABLE IF NOT EXISTS grievances (
|
|
40
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
41
|
+
model TEXT NOT NULL,
|
|
42
|
+
version TEXT NOT NULL,
|
|
43
|
+
tool TEXT NOT NULL,
|
|
44
|
+
report TEXT NOT NULL
|
|
45
|
+
);
|
|
46
|
+
`);
|
|
47
|
+
cachedDb = db;
|
|
48
|
+
return db;
|
|
49
|
+
} catch {
|
|
50
|
+
return null;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function createReportToolIssueTool(session: ToolSession): AgentTool {
|
|
55
|
+
const getModel = () => session.getActiveModelString?.() ?? "unknown";
|
|
56
|
+
|
|
57
|
+
return {
|
|
58
|
+
name: "report_tool_issue",
|
|
59
|
+
label: "Report Tool Issue",
|
|
60
|
+
description: "Report unexpected tool behavior for automated QA tracking.",
|
|
61
|
+
parameters: ReportToolIssueParams,
|
|
62
|
+
async execute(_toolCallId, rawParams) {
|
|
63
|
+
try {
|
|
64
|
+
const params = rawParams as { tool: string; report: string };
|
|
65
|
+
const db = openDb();
|
|
66
|
+
db?.prepare("INSERT INTO grievances (model, version, tool, report) VALUES (?, ?, ?, ?)").run(
|
|
67
|
+
getModel(),
|
|
68
|
+
VERSION,
|
|
69
|
+
params.tool,
|
|
70
|
+
params.report,
|
|
71
|
+
);
|
|
72
|
+
} catch (error) {
|
|
73
|
+
logger.error("Failed to record tool issue", { error });
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
content: [{ type: "text", text: "Noted, thanks!" }],
|
|
77
|
+
};
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
}
|
package/src/tools/resolve.ts
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
|
-
import { untilAborted } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import { type Static, Type } from "@sinclair/typebox";
|
|
6
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
7
6
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
8
7
|
import type { Theme } from "../modes/theme/theme";
|
|
9
8
|
import resolveDescription from "../prompts/tools/resolve.md" with { type: "text" };
|
|
@@ -11,7 +10,6 @@ import { Ellipsis, padToWidth, renderStatusLine, truncateToWidth } from "../tui"
|
|
|
11
10
|
import type { ToolSession } from ".";
|
|
12
11
|
import { replaceTabs } from "./render-utils";
|
|
13
12
|
import { ToolError } from "./tool-errors";
|
|
14
|
-
import { toolResult } from "./tool-result";
|
|
15
13
|
|
|
16
14
|
const resolveSchema = Type.Object({
|
|
17
15
|
action: Type.Union([Type.Literal("apply"), Type.Literal("discard")]),
|
|
@@ -33,6 +31,70 @@ function resolveReasonPreview(reason?: string): string | undefined {
|
|
|
33
31
|
return truncateToWidth(trimmed, 72, Ellipsis.Omit);
|
|
34
32
|
}
|
|
35
33
|
|
|
34
|
+
/**
|
|
35
|
+
* Queue a resolve-protocol handler on the tool-choice queue. Forces the next
|
|
36
|
+
* LLM call to invoke the hidden `resolve` tool, wraps the caller's apply/reject
|
|
37
|
+
* callbacks into an onInvoked closure that matches the resolve schema, and
|
|
38
|
+
* steers a preview reminder so the model understands why.
|
|
39
|
+
*
|
|
40
|
+
* This is the canonical entry point for any tool that wants preview/apply
|
|
41
|
+
* semantics. No session-level abstraction is needed: callers pass their
|
|
42
|
+
* apply/reject functions directly.
|
|
43
|
+
*/
|
|
44
|
+
export function queueResolveHandler(
|
|
45
|
+
session: ToolSession,
|
|
46
|
+
options: {
|
|
47
|
+
label: string;
|
|
48
|
+
sourceToolName: string;
|
|
49
|
+
apply(reason: string): Promise<AgentToolResult<unknown>>;
|
|
50
|
+
reject?(reason: string): Promise<AgentToolResult<unknown> | undefined>;
|
|
51
|
+
},
|
|
52
|
+
): void {
|
|
53
|
+
const queue = session.getToolChoiceQueue?.();
|
|
54
|
+
const forced = session.buildToolChoice?.("resolve");
|
|
55
|
+
if (!queue || !forced || typeof forced === "string") return;
|
|
56
|
+
|
|
57
|
+
const detailsFor = (params: ResolveParams): ResolveToolDetails => ({
|
|
58
|
+
action: params.action,
|
|
59
|
+
reason: params.reason,
|
|
60
|
+
sourceToolName: options.sourceToolName,
|
|
61
|
+
label: options.label,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
queue.pushOnce(forced, {
|
|
65
|
+
label: `pending-action:${options.sourceToolName}`,
|
|
66
|
+
now: true,
|
|
67
|
+
onRejected: () => "requeue",
|
|
68
|
+
onInvoked: async (input: unknown) => {
|
|
69
|
+
const params = input as ResolveParams;
|
|
70
|
+
if (params.action === "apply") {
|
|
71
|
+
const result = await options.apply(params.reason);
|
|
72
|
+
return { ...result, details: detailsFor(params) };
|
|
73
|
+
}
|
|
74
|
+
if (params.action === "discard" && options.reject != null) {
|
|
75
|
+
const result = await options.reject(params.reason);
|
|
76
|
+
if (result != null) {
|
|
77
|
+
return { ...result, details: detailsFor(params) };
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: "text" as const, text: `Discarded: ${options.label}. Reason: ${params.reason}` }],
|
|
82
|
+
details: detailsFor(params),
|
|
83
|
+
};
|
|
84
|
+
},
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
session.steer?.({
|
|
88
|
+
customType: "resolve-reminder",
|
|
89
|
+
content: [
|
|
90
|
+
"<system-reminder>",
|
|
91
|
+
"This is a preview. Call the `resolve` tool to apply or discard these changes.",
|
|
92
|
+
"</system-reminder>",
|
|
93
|
+
].join("\n"),
|
|
94
|
+
details: { toolName: options.sourceToolName },
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
36
98
|
export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolDetails> {
|
|
37
99
|
readonly name = "resolve";
|
|
38
100
|
readonly label = "Resolve";
|
|
@@ -42,7 +104,7 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
|
|
|
42
104
|
readonly strict = true;
|
|
43
105
|
|
|
44
106
|
constructor(private readonly session: ToolSession) {
|
|
45
|
-
this.description =
|
|
107
|
+
this.description = prompt.render(resolveDescription);
|
|
46
108
|
}
|
|
47
109
|
|
|
48
110
|
async execute(
|
|
@@ -53,43 +115,12 @@ export class ResolveTool implements AgentTool<typeof resolveSchema, ResolveToolD
|
|
|
53
115
|
_context?: AgentToolContext,
|
|
54
116
|
): Promise<AgentToolResult<ResolveToolDetails>> {
|
|
55
117
|
return untilAborted(signal, async () => {
|
|
56
|
-
const
|
|
57
|
-
if (!
|
|
118
|
+
const invoker = this.session.peekQueueInvoker?.();
|
|
119
|
+
if (!invoker) {
|
|
58
120
|
throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
|
|
59
121
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
if (!pendingAction) {
|
|
63
|
-
throw new ToolError("No pending action to resolve. Nothing to apply or discard.");
|
|
64
|
-
}
|
|
65
|
-
const resolveDetails: ResolveToolDetails = {
|
|
66
|
-
action: params.action,
|
|
67
|
-
reason: params.reason,
|
|
68
|
-
sourceToolName: pendingAction.sourceToolName,
|
|
69
|
-
label: pendingAction.label,
|
|
70
|
-
};
|
|
71
|
-
|
|
72
|
-
if (params.action === "apply") {
|
|
73
|
-
const applyResult = await pendingAction.apply(params.reason);
|
|
74
|
-
const appliedText = applyResult.content
|
|
75
|
-
.filter(part => part.type === "text")
|
|
76
|
-
.map(part => part.text)
|
|
77
|
-
.filter(text => text != null && text.length > 0)
|
|
78
|
-
.join("\n");
|
|
79
|
-
const baseResult = toolResult()
|
|
80
|
-
.text(appliedText || `Applied: ${pendingAction.label}.`)
|
|
81
|
-
.done();
|
|
82
|
-
return { ...baseResult, details: resolveDetails };
|
|
83
|
-
}
|
|
84
|
-
|
|
85
|
-
if (params.action === "discard" && pendingAction.reject != null) {
|
|
86
|
-
const discardResult = await pendingAction.reject(params.reason);
|
|
87
|
-
if (discardResult != null) {
|
|
88
|
-
return { ...discardResult, details: resolveDetails };
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
const discardResult = toolResult().text(`Discarded: ${pendingAction.label}. Reason: ${params.reason}`).done();
|
|
92
|
-
return { ...discardResult, details: resolveDetails };
|
|
122
|
+
const result = (await invoker(params)) as AgentToolResult<ResolveToolDetails>;
|
|
123
|
+
return result;
|
|
93
124
|
});
|
|
94
125
|
}
|
|
95
126
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
3
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import { type Static, Type } from "@sinclair/typebox";
|
|
4
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
5
5
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
6
6
|
import {
|
|
7
7
|
buildDiscoverableMCPSearchIndex,
|
|
@@ -105,7 +105,7 @@ function supportsMCPToolDiscoveryExecution(session: ToolSession): session is MCP
|
|
|
105
105
|
|
|
106
106
|
export function renderSearchToolBm25Description(discoverableTools: DiscoverableMCPTool[] = []): string {
|
|
107
107
|
const summary = summarizeDiscoverableMCPTools(discoverableTools);
|
|
108
|
-
return
|
|
108
|
+
return prompt.render(searchToolBm25Description, {
|
|
109
109
|
discoverableMCPToolCount: summary.toolCount,
|
|
110
110
|
discoverableMCPServerSummaries: summary.servers.map(formatDiscoverableMCPToolServerSummary),
|
|
111
111
|
hasDiscoverableMCPServers: summary.servers.length > 0,
|
package/src/tools/ssh.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
3
3
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
4
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
5
|
import { type Static, Type } from "@sinclair/typebox";
|
|
5
6
|
import type { SSHHost } from "../capability/ssh";
|
|
6
7
|
import { sshCapability } from "../capability/ssh";
|
|
7
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
8
8
|
import { loadCapability } from "../discovery";
|
|
9
9
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
10
10
|
import type { Theme } from "../modes/theme/theme";
|
|
@@ -59,7 +59,7 @@ async function formatHostEntry(host: SSHHost): Promise<string> {
|
|
|
59
59
|
}
|
|
60
60
|
|
|
61
61
|
async function formatDescription(hosts: SSHHost[]): Promise<string> {
|
|
62
|
-
const baseDescription =
|
|
62
|
+
const baseDescription = prompt.render(sshDescriptionBase);
|
|
63
63
|
if (hosts.length === 0) {
|
|
64
64
|
return baseDescription;
|
|
65
65
|
}
|
package/src/tools/todo-write.ts
CHANGED
|
@@ -2,9 +2,9 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
2
2
|
import { StringEnum } from "@oh-my-pi/pi-ai";
|
|
3
3
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
4
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
5
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
5
6
|
import { type Static, Type } from "@sinclair/typebox";
|
|
6
7
|
import chalk from "chalk";
|
|
7
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
8
8
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
9
9
|
import type { Theme } from "../modes/theme/theme";
|
|
10
10
|
import todoWriteDescription from "../prompts/tools/todo-write.md" with { type: "text" };
|
|
@@ -347,7 +347,7 @@ export class TodoWriteTool implements AgentTool<typeof todoWriteSchema, TodoWrit
|
|
|
347
347
|
readonly strict = true;
|
|
348
348
|
|
|
349
349
|
constructor(private readonly session: ToolSession) {
|
|
350
|
-
this.description =
|
|
350
|
+
this.description = prompt.render(todoWriteDescription);
|
|
351
351
|
}
|
|
352
352
|
|
|
353
353
|
async execute(
|
|
@@ -14,6 +14,7 @@ export const TOOL_TIMEOUTS = {
|
|
|
14
14
|
ssh: { default: 60, min: 1, max: 3600 },
|
|
15
15
|
fetch: { default: 20, min: 1, max: 45 },
|
|
16
16
|
lsp: { default: 20, min: 5, max: 60 },
|
|
17
|
+
debug: { default: 30, min: 5, max: 300 },
|
|
17
18
|
} as const satisfies Record<string, ToolTimeoutConfig>;
|
|
18
19
|
|
|
19
20
|
export type ToolWithTimeout = keyof typeof TOOL_TIMEOUTS;
|
package/src/tools/write.ts
CHANGED
|
@@ -9,20 +9,19 @@ import type {
|
|
|
9
9
|
} from "@oh-my-pi/pi-agent-core";
|
|
10
10
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
11
11
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
12
|
-
import { isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
|
|
12
|
+
import { isEnoent, prompt, untilAborted } from "@oh-my-pi/pi-utils";
|
|
13
13
|
import { type Static, Type } from "@sinclair/typebox";
|
|
14
14
|
import { unzipSync, zipSync } from "fflate";
|
|
15
|
-
import {
|
|
15
|
+
import { stripHashlinePrefixes } from "../edit";
|
|
16
16
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
17
17
|
import { createLspWritethrough, type FileDiagnosticsResult, type WritethroughCallback, writethroughNoop } from "../lsp";
|
|
18
18
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
19
|
-
import { stripHashlinePrefixes } from "../patch";
|
|
20
19
|
import writeDescription from "../prompts/tools/write.md" with { type: "text" };
|
|
21
20
|
import type { ToolSession } from "../sdk";
|
|
22
21
|
import { Ellipsis, Hasher, type RenderCache, renderStatusLine, truncateToWidth } from "../tui";
|
|
23
22
|
import { resolveFileDisplayMode } from "../utils/file-display-mode";
|
|
24
23
|
import { parseArchivePathCandidates } from "./archive-reader";
|
|
25
|
-
import {
|
|
24
|
+
import { assertEditableFile } from "./auto-generated-guard";
|
|
26
25
|
import { invalidateFsScanAfterWrite } from "./fs-cache-invalidation";
|
|
27
26
|
import { type OutputMeta, outputMeta } from "./output-meta";
|
|
28
27
|
import { enforcePlanModeWrite, resolvePlanPath } from "./plan-mode-guard";
|
|
@@ -149,7 +148,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
149
148
|
this.#writethrough = enableLsp
|
|
150
149
|
? createLspWritethrough(session.cwd, { enableFormat, enableDiagnostics })
|
|
151
150
|
: writethroughNoop;
|
|
152
|
-
this.description =
|
|
151
|
+
this.description = prompt.render(writeDescription);
|
|
153
152
|
}
|
|
154
153
|
|
|
155
154
|
async #resolveArchiveWritePath(writePath: string): Promise<ResolvedArchiveWritePath | null> {
|
|
@@ -298,7 +297,7 @@ export class WriteTool implements AgentTool<typeof writeSchema, WriteToolDetails
|
|
|
298
297
|
|
|
299
298
|
// Check if file exists and is auto-generated before overwriting
|
|
300
299
|
if (await fs.exists(absolutePath)) {
|
|
301
|
-
await
|
|
300
|
+
await assertEditableFile(absolutePath, path);
|
|
302
301
|
}
|
|
303
302
|
|
|
304
303
|
const diagnostics = await this.#writethrough(absolutePath, cleanContent, signal, undefined, batchRequest);
|
package/src/tui/tree-list.ts
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hierarchical tree list rendering helper.
|
|
3
3
|
*/
|
|
4
|
+
|
|
5
|
+
import { replaceTabs } from "@oh-my-pi/pi-tui";
|
|
4
6
|
import type { Theme } from "../modes/theme/theme";
|
|
5
|
-
import { formatMoreItems
|
|
7
|
+
import { formatMoreItems } from "../tools/render-utils";
|
|
6
8
|
import type { TreeContext } from "./types";
|
|
7
9
|
import { getTreeBranch, getTreeContinuePrefix } from "./utils";
|
|
8
10
|
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
import { execSync } from "node:child_process";
|
|
2
|
+
import type { ClipboardImage } from "@oh-my-pi/pi-natives";
|
|
3
|
+
import * as native from "@oh-my-pi/pi-natives";
|
|
4
|
+
|
|
5
|
+
const hasDisplay = process.platform !== "linux" || Boolean(process.env.DISPLAY || process.env.WAYLAND_DISPLAY);
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Copy text to the system clipboard.
|
|
9
|
+
*
|
|
10
|
+
* Emits OSC 52 first when running in a real terminal (works over SSH/mosh),
|
|
11
|
+
* then attempts native clipboard copy as best-effort for local sessions.
|
|
12
|
+
* On Termux, tries `termux-clipboard-set` before native.
|
|
13
|
+
*
|
|
14
|
+
* @param text - UTF-8 text to place on the clipboard.
|
|
15
|
+
*/
|
|
16
|
+
export async function copyToClipboard(text: string): Promise<void> {
|
|
17
|
+
if (process.stdout.isTTY) {
|
|
18
|
+
const onError = (err: unknown) => {
|
|
19
|
+
process.stdout.off("error", onError);
|
|
20
|
+
// Prevent unhandled 'error' from crashing the process when stdout is a closed pipe.
|
|
21
|
+
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
|
|
22
|
+
return;
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
try {
|
|
26
|
+
const encoded = Buffer.from(text).toString("base64");
|
|
27
|
+
const osc52 = `\x1b]52;c;${encoded}\x07`;
|
|
28
|
+
process.stdout.on("error", onError);
|
|
29
|
+
process.stdout.write(osc52, err => {
|
|
30
|
+
process.stdout.off("error", onError);
|
|
31
|
+
// If stdout is closed (e.g. piped to a process that exits early),
|
|
32
|
+
// ignore EPIPE and proceed with native clipboard best-effort.
|
|
33
|
+
if ((err as NodeJS.ErrnoException | null | undefined)?.code === "EPIPE") {
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
} catch (err) {
|
|
38
|
+
process.stdout.off("error", onError);
|
|
39
|
+
if ((err as NodeJS.ErrnoException | null | undefined)?.code !== "EPIPE") {
|
|
40
|
+
// Ignore all write failures (OSC 52 is best-effort).
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Also try native tools (best effort for local sessions)
|
|
46
|
+
try {
|
|
47
|
+
if (process.env.TERMUX_VERSION) {
|
|
48
|
+
try {
|
|
49
|
+
execSync("termux-clipboard-set", { input: text, timeout: 5000 });
|
|
50
|
+
return;
|
|
51
|
+
} catch {
|
|
52
|
+
// Fall through to native
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
await native.copyToClipboard(text);
|
|
57
|
+
} catch {
|
|
58
|
+
// Ignore — clipboard copy is best-effort
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Read an image from the system clipboard.
|
|
64
|
+
*
|
|
65
|
+
* Returns null on Termux (no image clipboard support) or when no display
|
|
66
|
+
* server is available (headless/SSH without forwarding).
|
|
67
|
+
*
|
|
68
|
+
* @returns PNG payload or null when no image is available.
|
|
69
|
+
*/
|
|
70
|
+
export async function readImageFromClipboard(): Promise<ClipboardImage | null> {
|
|
71
|
+
if (process.env.TERMUX_VERSION) {
|
|
72
|
+
return null;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!hasDisplay) {
|
|
76
|
+
return null;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
return (await native.readImageFromClipboard()) ?? null;
|
|
80
|
+
}
|
|
@@ -5,16 +5,15 @@
|
|
|
5
5
|
import type { ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
6
6
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
7
7
|
import { completeSimple } from "@oh-my-pi/pi-ai";
|
|
8
|
-
import { logger } from "@oh-my-pi/pi-utils";
|
|
8
|
+
import { logger, prompt } from "@oh-my-pi/pi-utils";
|
|
9
9
|
import type { ModelRegistry } from "../config/model-registry";
|
|
10
10
|
import { resolveModelRoleValue } from "../config/model-resolver";
|
|
11
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
12
11
|
import type { Settings } from "../config/settings";
|
|
13
12
|
import MODEL_PRIO from "../priority.json" with { type: "json" };
|
|
14
13
|
import commitSystemPrompt from "../prompts/system/commit-message-system.md" with { type: "text" };
|
|
15
14
|
import { toReasoningEffort } from "../thinking";
|
|
16
15
|
|
|
17
|
-
const COMMIT_SYSTEM_PROMPT =
|
|
16
|
+
const COMMIT_SYSTEM_PROMPT = prompt.render(commitSystemPrompt);
|
|
18
17
|
const MAX_DIFF_CHARS = 4000;
|
|
19
18
|
|
|
20
19
|
/** File patterns that should be excluded from commit message generation diffs. */
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { $env } from "@oh-my-pi/pi-utils";
|
|
2
|
+
|
|
3
|
+
export type EditMode = "replace" | "patch" | "hashline" | "chunk";
|
|
4
|
+
|
|
5
|
+
export const DEFAULT_EDIT_MODE: EditMode = "hashline";
|
|
6
|
+
|
|
7
|
+
const EDIT_MODE_IDS = {
|
|
8
|
+
chunk: "chunk",
|
|
9
|
+
hashline: "hashline",
|
|
10
|
+
patch: "patch",
|
|
11
|
+
replace: "replace",
|
|
12
|
+
} as const satisfies Record<string, EditMode>;
|
|
13
|
+
|
|
14
|
+
export const EDIT_MODES = Object.keys(EDIT_MODE_IDS) as EditMode[];
|
|
15
|
+
|
|
16
|
+
export function normalizeEditMode(mode?: string | null): EditMode | undefined {
|
|
17
|
+
if (!mode) return undefined;
|
|
18
|
+
return EDIT_MODE_IDS[mode as keyof typeof EDIT_MODE_IDS];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface EditModeSettingsLike {
|
|
22
|
+
get(key: "edit.mode"): unknown;
|
|
23
|
+
getEditVariantForModel?(model: string | undefined): EditMode | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface EditModeSessionLike {
|
|
27
|
+
settings: EditModeSettingsLike;
|
|
28
|
+
getActiveModelString?: () => string | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export function resolveEditMode(session: EditModeSessionLike): EditMode {
|
|
32
|
+
const activeModel = session.getActiveModelString?.();
|
|
33
|
+
const modelVariant = session.settings.getEditVariantForModel?.(activeModel);
|
|
34
|
+
if (modelVariant) return modelVariant;
|
|
35
|
+
|
|
36
|
+
const envMode = normalizeEditMode($env.PI_EDIT_VARIANT);
|
|
37
|
+
if (envMode) return envMode;
|
|
38
|
+
|
|
39
|
+
if ($env.PI_STRICT_EDIT_MODE === "1") {
|
|
40
|
+
if (activeModel?.includes("spark")) return "replace";
|
|
41
|
+
if (activeModel?.includes("nano")) return "replace";
|
|
42
|
+
if (activeModel?.includes("mini")) return "replace";
|
|
43
|
+
if (activeModel?.includes("haiku")) return "replace";
|
|
44
|
+
if (activeModel?.includes("flash")) return "replace";
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const settingsMode = normalizeEditMode(String(session.settings.get("edit.mode") ?? ""));
|
|
48
|
+
return settingsMode ?? DEFAULT_EDIT_MODE;
|
|
49
|
+
}
|
|
@@ -2,9 +2,12 @@
|
|
|
2
2
|
* Resolve line-display mode for file-like outputs (read, grep, @file mentions).
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { resolveEditMode } from "./edit-mode";
|
|
6
|
+
|
|
5
7
|
export interface FileDisplayMode {
|
|
6
8
|
lineNumbers: boolean;
|
|
7
9
|
hashLines: boolean;
|
|
10
|
+
chunked: boolean;
|
|
8
11
|
}
|
|
9
12
|
|
|
10
13
|
/** Session-like object providing settings and tool availability for display mode resolution. */
|
|
@@ -24,13 +27,11 @@ export interface FileDisplayModeSession {
|
|
|
24
27
|
export function resolveFileDisplayMode(session: FileDisplayModeSession): FileDisplayMode {
|
|
25
28
|
const { settings } = session;
|
|
26
29
|
const hasEditTool = session.hasEditTool ?? true;
|
|
27
|
-
const hashLines =
|
|
28
|
-
|
|
29
|
-
(settings.get("readHashLines") === true ||
|
|
30
|
-
settings.get("edit.mode") === "hashline" ||
|
|
31
|
-
Bun.env.PI_EDIT_VARIANT === "hashline");
|
|
30
|
+
const hashLines = hasEditTool && resolveEditMode(session) === "hashline" && settings.get("readHashLines") !== false;
|
|
31
|
+
const chunked = hasEditTool && resolveEditMode(session) === "chunk";
|
|
32
32
|
return {
|
|
33
33
|
hashLines,
|
|
34
34
|
lineNumbers: hashLines || settings.get("readLineNumbers") === true,
|
|
35
|
+
chunked,
|
|
35
36
|
};
|
|
36
37
|
}
|
|
@@ -8,8 +8,10 @@
|
|
|
8
8
|
import * as fs from "node:fs/promises";
|
|
9
9
|
import path from "node:path";
|
|
10
10
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
11
|
+
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
11
12
|
import { glob } from "@oh-my-pi/pi-natives";
|
|
12
|
-
import {
|
|
13
|
+
import { formatAge, formatBytes, readImageMetadata } from "@oh-my-pi/pi-utils";
|
|
14
|
+
import { formatHashLines } from "../edit/line-hash";
|
|
13
15
|
import type { FileMentionMessage } from "../session/messages";
|
|
14
16
|
import {
|
|
15
17
|
DEFAULT_MAX_BYTES,
|
|
@@ -18,10 +20,8 @@ import {
|
|
|
18
20
|
truncateHeadBytes,
|
|
19
21
|
} from "../session/streaming-output";
|
|
20
22
|
import { resolveReadPath } from "../tools/path-utils";
|
|
21
|
-
import { formatAge, formatBytes } from "../tools/render-utils";
|
|
22
23
|
import { fuzzyMatch } from "./fuzzy";
|
|
23
24
|
import { formatDimensionNote, resizeImage } from "./image-resize";
|
|
24
|
-
import { detectSupportedImageMimeTypeFromFile } from "./mime";
|
|
25
25
|
|
|
26
26
|
/** Regex to match @filepath patterns in text */
|
|
27
27
|
const FILE_MENTION_REGEX = /@([^\s@]+)/g;
|
|
@@ -304,7 +304,8 @@ export async function generateFileMentionMessages(
|
|
|
304
304
|
continue;
|
|
305
305
|
}
|
|
306
306
|
|
|
307
|
-
const
|
|
307
|
+
const imageMetadata = await readImageMetadata(absolutePath);
|
|
308
|
+
const mimeType = imageMetadata?.mimeType;
|
|
308
309
|
if (mimeType) {
|
|
309
310
|
if (stat.size > MAX_AUTO_READ_IMAGE_BYTES) {
|
|
310
311
|
files.push({
|
|
@@ -321,7 +322,7 @@ export async function generateFileMentionMessages(
|
|
|
321
322
|
}
|
|
322
323
|
|
|
323
324
|
const base64Content = buffer.toBase64();
|
|
324
|
-
let image = { type: "image"
|
|
325
|
+
let image: ImageContent = { type: "image", mimeType, data: base64Content };
|
|
325
326
|
let dimensionNote: string | undefined;
|
|
326
327
|
|
|
327
328
|
if (autoResizeImages) {
|
|
@@ -329,12 +330,12 @@ export async function generateFileMentionMessages(
|
|
|
329
330
|
const resized = await resizeImage({ type: "image", data: base64Content, mimeType });
|
|
330
331
|
dimensionNote = formatDimensionNote(resized);
|
|
331
332
|
image = {
|
|
332
|
-
type: "image"
|
|
333
|
+
type: "image",
|
|
333
334
|
mimeType: resized.mimeType,
|
|
334
335
|
data: resized.data,
|
|
335
336
|
};
|
|
336
337
|
} catch {
|
|
337
|
-
image = { type: "image"
|
|
338
|
+
image = { type: "image", mimeType, data: base64Content };
|
|
338
339
|
}
|
|
339
340
|
}
|
|
340
341
|
|
package/src/utils/git.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { isEnoent, Snowflake } from "@oh-my-pi/pi-utils";
|
|
4
|
+
import { $which, isEnoent, Snowflake } from "@oh-my-pi/pi-utils";
|
|
5
5
|
import {
|
|
6
6
|
parseDiffHunks as parseCommitDiffHunks,
|
|
7
7
|
parseFileDiffs,
|
|
@@ -162,7 +162,7 @@ function normalizeStdin(input: CommandOptions["stdin"]): "ignore" | Uint8Array {
|
|
|
162
162
|
}
|
|
163
163
|
|
|
164
164
|
function ensureAvailable(): void {
|
|
165
|
-
if (
|
|
165
|
+
if (!$which("git")) {
|
|
166
166
|
throw new Error("git is not installed.");
|
|
167
167
|
}
|
|
168
168
|
}
|
|
@@ -1334,13 +1334,13 @@ function formatGhFailure(args: readonly string[], stdout: string, stderr: string
|
|
|
1334
1334
|
export const github = {
|
|
1335
1335
|
/** Check if `gh` CLI is installed. */
|
|
1336
1336
|
available(): boolean {
|
|
1337
|
-
return Boolean(
|
|
1337
|
+
return Boolean($which("gh"));
|
|
1338
1338
|
},
|
|
1339
1339
|
|
|
1340
1340
|
/** Run a raw `gh` CLI command. Does not throw on non-zero exit. */
|
|
1341
1341
|
async run(cwd: string, args: string[], signal?: AbortSignal, options?: GhCommandOptions): Promise<GhCommandResult> {
|
|
1342
1342
|
throwIfAborted(signal);
|
|
1343
|
-
if (
|
|
1343
|
+
if (!$which("gh")) {
|
|
1344
1344
|
throw new ToolError("GitHub CLI (gh) is not installed. Install it from https://cli.github.com/.");
|
|
1345
1345
|
}
|
|
1346
1346
|
try {
|