@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
|
@@ -8,6 +8,10 @@ import type {
|
|
|
8
8
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
9
9
|
import type { TodoStatus } from "../../tools/todo-write";
|
|
10
10
|
|
|
11
|
+
interface AcpEventMapperOptions {
|
|
12
|
+
getMessageId?: (message: unknown) => string | undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
11
15
|
interface ContentArrayContainer {
|
|
12
16
|
content?: unknown;
|
|
13
17
|
}
|
|
@@ -118,10 +122,11 @@ export function mapToolKind(toolName: string): ToolKind {
|
|
|
118
122
|
export function mapAgentSessionEventToAcpSessionUpdates(
|
|
119
123
|
event: AgentSessionEvent,
|
|
120
124
|
sessionId: string,
|
|
125
|
+
options: AcpEventMapperOptions = {},
|
|
121
126
|
): SessionNotification[] {
|
|
122
127
|
switch (event.type) {
|
|
123
128
|
case "message_update":
|
|
124
|
-
return mapAssistantMessageUpdate(event, sessionId);
|
|
129
|
+
return mapAssistantMessageUpdate(event, sessionId, options);
|
|
125
130
|
case "tool_execution_start": {
|
|
126
131
|
const update: SessionUpdate = {
|
|
127
132
|
sessionUpdate: "tool_call",
|
|
@@ -181,6 +186,7 @@ export function mapAgentSessionEventToAcpSessionUpdates(
|
|
|
181
186
|
function mapAssistantMessageUpdate(
|
|
182
187
|
event: Extract<AgentSessionEvent, { type: "message_update" }>,
|
|
183
188
|
sessionId: string,
|
|
189
|
+
options: AcpEventMapperOptions,
|
|
184
190
|
): SessionNotification[] {
|
|
185
191
|
if (!isAssistantMessage(event.message)) {
|
|
186
192
|
return [];
|
|
@@ -208,10 +214,12 @@ function mapAssistantMessageUpdate(
|
|
|
208
214
|
return [];
|
|
209
215
|
}
|
|
210
216
|
|
|
217
|
+
const messageId = options.getMessageId?.(event.message);
|
|
211
218
|
return [
|
|
212
219
|
toSessionNotification(sessionId, {
|
|
213
220
|
sessionUpdate,
|
|
214
221
|
content: { type: "text", text },
|
|
222
|
+
messageId,
|
|
215
223
|
}),
|
|
216
224
|
];
|
|
217
225
|
}
|
|
@@ -3,11 +3,13 @@ import { AgentSideConnection, ndJsonStream } from "@agentclientprotocol/sdk";
|
|
|
3
3
|
import type { AgentSession } from "../../session/agent-session";
|
|
4
4
|
import { AcpAgent } from "./acp-agent";
|
|
5
5
|
|
|
6
|
-
export
|
|
6
|
+
export type AcpSessionFactory = (cwd: string) => Promise<AgentSession>;
|
|
7
|
+
|
|
8
|
+
export async function runAcpMode(session: AgentSession, createSession: AcpSessionFactory): Promise<never> {
|
|
7
9
|
const input = stream.Writable.toWeb(process.stdout);
|
|
8
10
|
const output = stream.Readable.toWeb(process.stdin);
|
|
9
11
|
const transport = ndJsonStream(input, output);
|
|
10
|
-
const connection = new AgentSideConnection(conn => new AcpAgent(conn, session), transport);
|
|
12
|
+
const connection = new AgentSideConnection(conn => new AcpAgent(conn, session, createSession), transport);
|
|
11
13
|
await connection.closed;
|
|
12
14
|
process.exit(0);
|
|
13
15
|
}
|
|
@@ -31,7 +31,7 @@ import {
|
|
|
31
31
|
visibleWidth,
|
|
32
32
|
wrapTextWithAnsi,
|
|
33
33
|
} from "@oh-my-pi/pi-tui";
|
|
34
|
-
import { isEnoent } from "@oh-my-pi/pi-utils";
|
|
34
|
+
import { isEnoent, prompt } from "@oh-my-pi/pi-utils";
|
|
35
35
|
import { YAML } from "bun";
|
|
36
36
|
import { getConfigDirs } from "../../config";
|
|
37
37
|
import type { ModelRegistry } from "../../config/model-registry";
|
|
@@ -41,7 +41,6 @@ import {
|
|
|
41
41
|
resolveConfiguredModelPatterns,
|
|
42
42
|
resolveModelOverride,
|
|
43
43
|
} from "../../config/model-resolver";
|
|
44
|
-
import { renderPromptTemplate } from "../../config/prompt-templates";
|
|
45
44
|
import { Settings } from "../../config/settings";
|
|
46
45
|
import agentCreationArchitectPrompt from "../../prompts/system/agent-creation-architect.md" with { type: "text" };
|
|
47
46
|
import agentCreationUserPrompt from "../../prompts/system/agent-creation-user.md" with { type: "text" };
|
|
@@ -627,8 +626,8 @@ export class AgentDashboard extends Container {
|
|
|
627
626
|
throw new Error("No available model to generate agent specification.");
|
|
628
627
|
}
|
|
629
628
|
|
|
630
|
-
const systemPrompt =
|
|
631
|
-
const userPrompt =
|
|
629
|
+
const systemPrompt = prompt.render(agentCreationArchitectPrompt, { TASK_TOOL_NAME: "task" });
|
|
630
|
+
const userPrompt = prompt.render(agentCreationUserPrompt, { request: description });
|
|
632
631
|
|
|
633
632
|
const { session } = await createAgentSession({
|
|
634
633
|
cwd: this.cwd,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { getIndentation } from "@oh-my-pi/pi-
|
|
1
|
+
import { getIndentation } from "@oh-my-pi/pi-natives";
|
|
2
2
|
import * as Diff from "diff";
|
|
3
3
|
import { theme } from "../../modes/theme/theme";
|
|
4
4
|
import { replaceTabs } from "../../tools/render-utils";
|
|
@@ -15,11 +15,10 @@ const DIM_OFF = "\x1b[22m";
|
|
|
15
15
|
*/
|
|
16
16
|
function visualizeIndent(text: string, filePath?: string): string {
|
|
17
17
|
const match = text.match(/^([ \t]+)/);
|
|
18
|
-
if (!match) return replaceTabs(text
|
|
18
|
+
if (!match) return replaceTabs(text);
|
|
19
19
|
const indent = match[1];
|
|
20
20
|
const rest = text.slice(indent.length);
|
|
21
|
-
const
|
|
22
|
-
const tabWidth = indentation.length;
|
|
21
|
+
const tabWidth = getIndentation(filePath);
|
|
23
22
|
const leftPadding = Math.floor(tabWidth / 2);
|
|
24
23
|
const rightPadding = Math.max(0, tabWidth - leftPadding - 1);
|
|
25
24
|
const tabMarker = `${DIM}${" ".repeat(leftPadding)}→${" ".repeat(rightPadding)}${DIM_OFF}`;
|
|
@@ -31,7 +30,7 @@ function visualizeIndent(text: string, filePath?: string): string {
|
|
|
31
30
|
visible += `${DIM}·${DIM_OFF}`;
|
|
32
31
|
}
|
|
33
32
|
}
|
|
34
|
-
return `${visible}${replaceTabs(rest
|
|
33
|
+
return `${visible}${replaceTabs(rest)}`;
|
|
35
34
|
}
|
|
36
35
|
|
|
37
36
|
/**
|
|
@@ -154,8 +153,8 @@ export function renderDiff(diffText: string, options: RenderDiffOptions = {}): s
|
|
|
154
153
|
const added = addedLines[0];
|
|
155
154
|
|
|
156
155
|
const { removedLine, addedLine } = renderIntraLineDiff(
|
|
157
|
-
replaceTabs(removed.content
|
|
158
|
-
replaceTabs(added.content
|
|
156
|
+
replaceTabs(removed.content),
|
|
157
|
+
replaceTabs(added.content),
|
|
159
158
|
);
|
|
160
159
|
|
|
161
160
|
result.push(
|
|
@@ -7,8 +7,7 @@ import type { ToolExecutionHandle } from "./tool-execution";
|
|
|
7
7
|
type ReadRenderArgs = {
|
|
8
8
|
path?: string;
|
|
9
9
|
file_path?: string;
|
|
10
|
-
|
|
11
|
-
limit?: number;
|
|
10
|
+
sel?: string;
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
type ReadToolSuffixResolution = {
|
|
@@ -33,8 +32,7 @@ function getSuffixResolution(details: ReadToolResultDetails | undefined): ReadTo
|
|
|
33
32
|
type ReadEntry = {
|
|
34
33
|
toolCallId: string;
|
|
35
34
|
path: string;
|
|
36
|
-
|
|
37
|
-
limit?: number;
|
|
35
|
+
sel?: string;
|
|
38
36
|
status: "pending" | "success" | "warning" | "error";
|
|
39
37
|
correctedFrom?: string;
|
|
40
38
|
};
|
|
@@ -56,13 +54,11 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
56
54
|
const entry: ReadEntry = this.#entries.get(toolCallId) ?? {
|
|
57
55
|
toolCallId,
|
|
58
56
|
path: rawPath,
|
|
59
|
-
|
|
60
|
-
limit: args.limit,
|
|
57
|
+
sel: args.sel,
|
|
61
58
|
status: "pending",
|
|
62
59
|
};
|
|
63
60
|
entry.path = rawPath;
|
|
64
|
-
entry.
|
|
65
|
-
entry.limit = args.limit;
|
|
61
|
+
entry.sel = args.sel;
|
|
66
62
|
this.#entries.set(toolCallId, entry);
|
|
67
63
|
this.#updateDisplay();
|
|
68
64
|
}
|
|
@@ -132,10 +128,8 @@ export class ReadToolGroupComponent extends Container implements ToolExecutionHa
|
|
|
132
128
|
#formatPath(entry: ReadEntry): string {
|
|
133
129
|
const filePath = shortenPath(entry.path);
|
|
134
130
|
let pathDisplay = filePath ? theme.fg("accent", filePath) : theme.fg("toolOutput", "…");
|
|
135
|
-
if (entry.
|
|
136
|
-
|
|
137
|
-
const endLine = entry.limit !== undefined ? startLine + entry.limit - 1 : "";
|
|
138
|
-
pathDisplay += theme.fg("warning", `:${startLine}${endLine ? `-${endLine}` : ""}`);
|
|
131
|
+
if (entry.sel) {
|
|
132
|
+
pathDisplay += theme.fg("warning", `:${entry.sel}`);
|
|
139
133
|
}
|
|
140
134
|
if (entry.correctedFrom) {
|
|
141
135
|
pathDisplay += theme.fg("dim", ` (corrected from ${shortenPath(entry.correctedFrom)})`);
|
|
@@ -280,6 +280,11 @@ const OPTION_PROVIDERS: Partial<Record<SettingPath, OptionProvider>> = {
|
|
|
280
280
|
{ value: "1000", label: "1000 lines" },
|
|
281
281
|
{ value: "5000", label: "5000 lines" },
|
|
282
282
|
],
|
|
283
|
+
"read.anchorstyle": [
|
|
284
|
+
{ value: "full", label: "Full", description: "Show the kind prefix and identifier" },
|
|
285
|
+
{ value: "kind", label: "Kind", description: "Show only the kind prefix plus checksum" },
|
|
286
|
+
{ value: "bare", label: "Bare", description: "Show only the checksum" },
|
|
287
|
+
],
|
|
283
288
|
// Todo auto-clear delay
|
|
284
289
|
"tasks.todoClearDelay": [
|
|
285
290
|
{ value: "0", label: "Instant" },
|
|
@@ -14,9 +14,9 @@ import {
|
|
|
14
14
|
type TUI,
|
|
15
15
|
} from "@oh-my-pi/pi-tui";
|
|
16
16
|
import { getProjectDir, logger } from "@oh-my-pi/pi-utils";
|
|
17
|
+
import { computeEditDiff, computeHashlineDiff, computePatchDiff, type DiffError, type DiffResult } from "../../edit";
|
|
17
18
|
import type { Theme } from "../../modes/theme/theme";
|
|
18
19
|
import { theme } from "../../modes/theme/theme";
|
|
19
|
-
import { computeEditDiff, computeHashlineDiff, computePatchDiff, type DiffError, type DiffResult } from "../../patch";
|
|
20
20
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
21
21
|
import {
|
|
22
22
|
formatArgsInline,
|
|
@@ -111,7 +111,7 @@ export class WelcomeComponent implements Component {
|
|
|
111
111
|
server.status === "ready"
|
|
112
112
|
? theme.styledSymbol("status.success", "success")
|
|
113
113
|
: server.status === "connecting"
|
|
114
|
-
? theme.styledSymbol("status.
|
|
114
|
+
? theme.styledSymbol("status.pending", "muted")
|
|
115
115
|
: theme.styledSymbol("status.error", "error");
|
|
116
116
|
const exts = server.fileTypes.slice(0, 3).join(" ");
|
|
117
117
|
lspLines.push(` ${icon} ${theme.fg("muted", server.name)} ${theme.fg("dim", exts)}`);
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { type AssistantMessage, type Context, streamSimple } from "@oh-my-pi/pi-ai";
|
|
3
|
-
import {
|
|
3
|
+
import { prompt } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import btwUserPrompt from "../../prompts/system/btw-user.md" with { type: "text" };
|
|
5
5
|
import { toReasoningEffort } from "../../thinking";
|
|
6
6
|
import { BtwPanelComponent } from "../components/btw-panel";
|
|
@@ -137,7 +137,7 @@ export class BtwController {
|
|
|
137
137
|
content: [
|
|
138
138
|
{
|
|
139
139
|
type: "text",
|
|
140
|
-
text:
|
|
140
|
+
text: prompt.render(btwUserPrompt, { question }),
|
|
141
141
|
},
|
|
142
142
|
],
|
|
143
143
|
attribution: "user",
|
|
@@ -9,7 +9,6 @@ import {
|
|
|
9
9
|
type UsageLimit,
|
|
10
10
|
type UsageReport,
|
|
11
11
|
} from "@oh-my-pi/pi-ai";
|
|
12
|
-
import { copyToClipboard } from "@oh-my-pi/pi-natives";
|
|
13
12
|
import { Loader, Markdown, padding, Spacer, Text, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
14
13
|
import { formatDuration, Snowflake, setProjectDir } from "@oh-my-pi/pi-utils";
|
|
15
14
|
import { $ } from "bun";
|
|
@@ -33,6 +32,7 @@ import { outputMeta } from "../../tools/output-meta";
|
|
|
33
32
|
import { resolveToCwd, stripOuterDoubleQuotes } from "../../tools/path-utils";
|
|
34
33
|
import { replaceTabs } from "../../tools/render-utils";
|
|
35
34
|
import { getChangelogPath, parseChangelog } from "../../utils/changelog";
|
|
35
|
+
import { copyToClipboard } from "../../utils/clipboard";
|
|
36
36
|
import { openPath } from "../../utils/open";
|
|
37
37
|
import { setSessionTerminalTitle } from "../../utils/title-generator";
|
|
38
38
|
|
|
@@ -396,7 +396,8 @@ export class CommandController {
|
|
|
396
396
|
if (this.ctx.lspServers && this.ctx.lspServers.length > 0) {
|
|
397
397
|
info += `\n${theme.bold("LSP Servers")}\n`;
|
|
398
398
|
for (const server of this.ctx.lspServers) {
|
|
399
|
-
const statusColor =
|
|
399
|
+
const statusColor =
|
|
400
|
+
server.status === "ready" ? "success" : server.status === "connecting" ? "warning" : "error";
|
|
400
401
|
const statusText =
|
|
401
402
|
server.status === "error" && server.error ? `${server.status}: ${server.error}` : server.status;
|
|
402
403
|
info += `${theme.fg("dim", `${server.name}:`)} ${theme.fg(statusColor, statusText)} ${theme.fg("dim", `(${server.fileTypes.join(", ")})`)}\n`;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs/promises";
|
|
2
2
|
import { type AgentMessage, ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
3
|
-
import {
|
|
3
|
+
import { sanitizeText } from "@oh-my-pi/pi-natives";
|
|
4
4
|
import type { AutocompleteProvider, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { $env } from "@oh-my-pi/pi-utils";
|
|
6
6
|
import { settings } from "../../config/settings";
|
|
@@ -10,8 +10,9 @@ import type { InteractiveModeContext } from "../../modes/types";
|
|
|
10
10
|
import type { AgentSessionEvent } from "../../session/agent-session";
|
|
11
11
|
import { SKILL_PROMPT_MESSAGE_TYPE, type SkillPromptDetails } from "../../session/messages";
|
|
12
12
|
import { executeBuiltinSlashCommand } from "../../slash-commands/builtin-registry";
|
|
13
|
+
import { copyToClipboard, readImageFromClipboard } from "../../utils/clipboard";
|
|
13
14
|
import { getEditorCommand, openInEditor } from "../../utils/external-editor";
|
|
14
|
-
import { ensureSupportedImageInput } from "../../utils/image-
|
|
15
|
+
import { ensureSupportedImageInput } from "../../utils/image-loading";
|
|
15
16
|
import { resizeImage } from "../../utils/image-resize";
|
|
16
17
|
import { generateSessionTitle, setSessionTerminalTitle } from "../../utils/title-generator";
|
|
17
18
|
|
|
@@ -218,14 +219,17 @@ export class InputController {
|
|
|
218
219
|
if (!text) return;
|
|
219
220
|
|
|
220
221
|
// Handle built-in slash commands
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
) {
|
|
222
|
+
const slashResult = await executeBuiltinSlashCommand(text, {
|
|
223
|
+
ctx: this.ctx,
|
|
224
|
+
handleBackgroundCommand: () => this.handleBackgroundCommand(),
|
|
225
|
+
});
|
|
226
|
+
if (slashResult === true) {
|
|
227
227
|
return;
|
|
228
228
|
}
|
|
229
|
+
if (typeof slashResult === "string") {
|
|
230
|
+
// Command handled but returned remaining text to use as prompt
|
|
231
|
+
text = slashResult;
|
|
232
|
+
}
|
|
229
233
|
|
|
230
234
|
// Handle skill commands (/skill:name [args])
|
|
231
235
|
if (text.startsWith("/skill:")) {
|
package/src/modes/index.ts
CHANGED
|
@@ -7,9 +7,27 @@ import { postmortem } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
export { runAcpMode } from "./acp";
|
|
8
8
|
export { InteractiveMode, type InteractiveModeOptions } from "./interactive-mode";
|
|
9
9
|
export { type PrintModeOptions, runPrintMode } from "./print-mode";
|
|
10
|
-
export {
|
|
10
|
+
export {
|
|
11
|
+
defineRpcClientTool,
|
|
12
|
+
type ModelInfo,
|
|
13
|
+
RpcClient,
|
|
14
|
+
type RpcClientCustomTool,
|
|
15
|
+
type RpcClientOptions,
|
|
16
|
+
type RpcClientToolContext,
|
|
17
|
+
type RpcClientToolResult,
|
|
18
|
+
type RpcEventListener,
|
|
19
|
+
} from "./rpc/rpc-client";
|
|
11
20
|
export { runRpcMode } from "./rpc/rpc-mode";
|
|
12
|
-
export type {
|
|
21
|
+
export type {
|
|
22
|
+
RpcCommand,
|
|
23
|
+
RpcHostToolCallRequest,
|
|
24
|
+
RpcHostToolCancelRequest,
|
|
25
|
+
RpcHostToolDefinition,
|
|
26
|
+
RpcHostToolResult,
|
|
27
|
+
RpcHostToolUpdate,
|
|
28
|
+
RpcResponse,
|
|
29
|
+
RpcSessionState,
|
|
30
|
+
} from "./rpc/rpc-types";
|
|
13
31
|
|
|
14
32
|
postmortem.register("terminal-restore", () => {
|
|
15
33
|
emergencyTerminalRestore();
|
|
@@ -15,10 +15,9 @@ import {
|
|
|
15
15
|
} from "@oh-my-pi/pi-ai";
|
|
16
16
|
import type { Component, SlashCommand } from "@oh-my-pi/pi-tui";
|
|
17
17
|
import { Container, Loader, Markdown, ProcessTerminal, Spacer, Text, TUI, visibleWidth } from "@oh-my-pi/pi-tui";
|
|
18
|
-
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
18
|
+
import { APP_NAME, getProjectDir, hsvToRgb, isEnoent, logger, postmortem, prompt } from "@oh-my-pi/pi-utils";
|
|
19
19
|
import chalk from "chalk";
|
|
20
20
|
import { KeybindingsManager } from "../config/keybindings";
|
|
21
|
-
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
22
21
|
import { type Settings, settings } from "../config/settings";
|
|
23
22
|
import type {
|
|
24
23
|
ExtensionUIContext,
|
|
@@ -29,6 +28,7 @@ import type {
|
|
|
29
28
|
import type { CompactOptions } from "../extensibility/extensions/types";
|
|
30
29
|
import { BUILTIN_SLASH_COMMANDS, loadSlashCommands } from "../extensibility/slash-commands";
|
|
31
30
|
import { resolveLocalUrlToPath } from "../internal-urls";
|
|
31
|
+
import { LSP_STARTUP_EVENT_CHANNEL, type LspStartupEvent } from "../lsp/startup-events";
|
|
32
32
|
import { renameApprovedPlanFile } from "../plan-mode/approved-plan";
|
|
33
33
|
import planModeApprovedPrompt from "../prompts/system/plan-mode-approved.md" with { type: "text" };
|
|
34
34
|
import type { AgentSession, AgentSessionEvent } from "../session/agent-session";
|
|
@@ -36,7 +36,7 @@ import { HistoryStorage } from "../session/history-storage";
|
|
|
36
36
|
import type { SessionContext, SessionManager } from "../session/session-manager";
|
|
37
37
|
import { getRecentSessions } from "../session/session-manager";
|
|
38
38
|
import { STTController, type SttState } from "../stt";
|
|
39
|
-
import type { ExitPlanModeDetails } from "../tools";
|
|
39
|
+
import type { ExitPlanModeDetails, LspStartupServerInfo } from "../tools";
|
|
40
40
|
import type { EventBus } from "../utils/event-bus";
|
|
41
41
|
import { getEditorCommand, openInEditor } from "../utils/external-editor";
|
|
42
42
|
import { popTerminalTitle, pushTerminalTitle, setSessionTerminalTitle } from "../utils/title-generator";
|
|
@@ -50,7 +50,7 @@ import type { HookSelectorComponent } from "./components/hook-selector";
|
|
|
50
50
|
import type { PythonExecutionComponent } from "./components/python-execution";
|
|
51
51
|
import { StatusLineComponent } from "./components/status-line";
|
|
52
52
|
import type { ToolExecutionHandle } from "./components/tool-execution";
|
|
53
|
-
import { WelcomeComponent } from "./components/welcome";
|
|
53
|
+
import { WelcomeComponent, type LspServerInfo as WelcomeLspServerInfo } from "./components/welcome";
|
|
54
54
|
import { BtwController } from "./controllers/btw-controller";
|
|
55
55
|
import { CommandController } from "./controllers/command-controller";
|
|
56
56
|
import { EventController } from "./controllers/event-controller";
|
|
@@ -166,9 +166,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
166
166
|
#pendingModelSwitch: { model: Model; thinkingLevel?: ThinkingLevel } | undefined;
|
|
167
167
|
#planModeHasEntered = false;
|
|
168
168
|
#planReviewContainer: Container | undefined;
|
|
169
|
-
readonly lspServers:
|
|
170
|
-
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
171
|
-
| undefined = undefined;
|
|
169
|
+
readonly lspServers: LspStartupServerInfo[] | undefined = undefined;
|
|
172
170
|
mcpManager?: import("../mcp").MCPManager;
|
|
173
171
|
readonly #toolUiContextSetter: (uiContext: ExtensionUIContext, hasUI: boolean) => void;
|
|
174
172
|
|
|
@@ -187,15 +185,15 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
187
185
|
#resizeHandler?: () => void;
|
|
188
186
|
#observerRegistry: SessionObserverRegistry;
|
|
189
187
|
#eventBus?: EventBus;
|
|
188
|
+
#eventBusUnsubscribers: Array<() => void> = [];
|
|
189
|
+
#welcomeComponent?: WelcomeComponent;
|
|
190
190
|
|
|
191
191
|
constructor(
|
|
192
192
|
session: AgentSession,
|
|
193
193
|
version: string,
|
|
194
194
|
changelogMarkdown: string | undefined = undefined,
|
|
195
195
|
setToolUIContext: (uiContext: ExtensionUIContext, hasUI: boolean) => void = () => {},
|
|
196
|
-
lspServers:
|
|
197
|
-
| Array<{ name: string; status: "ready" | "error"; fileTypes: string[]; error?: string }>
|
|
198
|
-
| undefined = undefined,
|
|
196
|
+
lspServers: LspStartupServerInfo[] | undefined = undefined,
|
|
199
197
|
mcpManager?: import("../mcp").MCPManager,
|
|
200
198
|
eventBus?: EventBus,
|
|
201
199
|
) {
|
|
@@ -210,6 +208,13 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
210
208
|
this.lspServers = lspServers;
|
|
211
209
|
this.mcpManager = mcpManager;
|
|
212
210
|
this.#eventBus = eventBus;
|
|
211
|
+
if (eventBus) {
|
|
212
|
+
this.#eventBusUnsubscribers.push(
|
|
213
|
+
eventBus.on(LSP_STARTUP_EVENT_CHANNEL, data => {
|
|
214
|
+
this.#handleLspStartupEvent(data as LspStartupEvent);
|
|
215
|
+
}),
|
|
216
|
+
);
|
|
217
|
+
}
|
|
213
218
|
|
|
214
219
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
215
220
|
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
@@ -290,13 +295,16 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
290
295
|
async init(): Promise<void> {
|
|
291
296
|
if (this.isInitialized) return;
|
|
292
297
|
|
|
293
|
-
|
|
298
|
+
logger.time("InteractiveMode.init:keybindings");
|
|
299
|
+
this.keybindings = KeybindingsManager.create();
|
|
294
300
|
|
|
295
301
|
// Register session manager flush for signal handlers (SIGINT, SIGTERM, SIGHUP)
|
|
296
302
|
this.#cleanupUnsubscribe = postmortem.register("session-manager-flush", () => this.sessionManager.flush());
|
|
297
303
|
|
|
298
|
-
await logger.
|
|
299
|
-
|
|
304
|
+
await logger.time(
|
|
305
|
+
"InteractiveMode.init:slashCommands",
|
|
306
|
+
this.refreshSlashCommandState.bind(this),
|
|
307
|
+
getProjectDir(),
|
|
300
308
|
);
|
|
301
309
|
|
|
302
310
|
// Get current model info for welcome screen
|
|
@@ -304,7 +312,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
304
312
|
const providerName = this.session.model?.provider ?? "Unknown";
|
|
305
313
|
|
|
306
314
|
// Get recent sessions
|
|
307
|
-
const recentSessions = await logger.
|
|
315
|
+
const recentSessions = await logger.time("InteractiveMode.init:recentSessions", () =>
|
|
308
316
|
getRecentSessions(this.sessionManager.getSessionDir()).then(sessions =>
|
|
309
317
|
sessions.map(s => ({
|
|
310
318
|
name: s.name,
|
|
@@ -313,15 +321,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
313
321
|
),
|
|
314
322
|
);
|
|
315
323
|
|
|
316
|
-
// Convert LSP servers to welcome format
|
|
317
|
-
const lspServerInfo =
|
|
318
|
-
this.lspServers?.map(s => ({
|
|
319
|
-
name: s.name,
|
|
320
|
-
status: s.status as "ready" | "error" | "connecting",
|
|
321
|
-
fileTypes: s.fileTypes,
|
|
322
|
-
})) ?? [];
|
|
323
|
-
|
|
324
324
|
const startupQuiet = settings.get("startup.quiet");
|
|
325
|
+
this.#welcomeComponent = undefined;
|
|
325
326
|
|
|
326
327
|
for (const warning of this.session.configWarnings) {
|
|
327
328
|
this.ui.addChild(new Text(theme.fg("warning", `Warning: ${warning}`), 1, 0));
|
|
@@ -330,11 +331,17 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
330
331
|
|
|
331
332
|
if (!startupQuiet) {
|
|
332
333
|
// Add welcome header
|
|
333
|
-
|
|
334
|
+
this.#welcomeComponent = new WelcomeComponent(
|
|
335
|
+
this.#version,
|
|
336
|
+
modelName,
|
|
337
|
+
providerName,
|
|
338
|
+
recentSessions,
|
|
339
|
+
this.#getWelcomeLspServers(),
|
|
340
|
+
);
|
|
334
341
|
|
|
335
342
|
// Setup UI layout
|
|
336
343
|
this.ui.addChild(new Spacer(1));
|
|
337
|
-
this.ui.addChild(
|
|
344
|
+
this.ui.addChild(this.#welcomeComponent);
|
|
338
345
|
this.ui.addChild(new Spacer(1));
|
|
339
346
|
|
|
340
347
|
// Add changelog if provided
|
|
@@ -779,17 +786,21 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
779
786
|
}
|
|
780
787
|
|
|
781
788
|
#renderPlanPreview(planContent: string): void {
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
789
|
+
const planReviewContainer = this.#planReviewContainer ?? new Container();
|
|
790
|
+
if (this.#planReviewContainer) {
|
|
791
|
+
// Re-append the preview so repeated plan-review refreshes stay adjacent to the
|
|
792
|
+
// active selector instead of updating an older off-screen preview in place.
|
|
793
|
+
this.chatContainer.removeChild(this.#planReviewContainer);
|
|
794
|
+
}
|
|
795
|
+
planReviewContainer.clear();
|
|
796
|
+
planReviewContainer.addChild(new Spacer(1));
|
|
797
|
+
planReviewContainer.addChild(new DynamicBorder());
|
|
798
|
+
planReviewContainer.addChild(new Text(theme.bold(theme.fg("accent", "Plan Review")), 1, 1));
|
|
799
|
+
planReviewContainer.addChild(new Spacer(1));
|
|
800
|
+
planReviewContainer.addChild(new Markdown(planContent, 1, 1, getMarkdownTheme()));
|
|
801
|
+
planReviewContainer.addChild(new DynamicBorder());
|
|
802
|
+
this.chatContainer.addChild(planReviewContainer);
|
|
803
|
+
this.#planReviewContainer = planReviewContainer;
|
|
793
804
|
this.ui.requestRender();
|
|
794
805
|
}
|
|
795
806
|
|
|
@@ -895,11 +906,11 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
895
906
|
}
|
|
896
907
|
this.session.setPlanReferencePath(options.finalPlanFilePath);
|
|
897
908
|
this.session.markPlanReferenceSent();
|
|
898
|
-
const
|
|
909
|
+
const planModePrompt = prompt.render(planModeApprovedPrompt, {
|
|
899
910
|
planContent,
|
|
900
911
|
finalPlanFilePath: options.finalPlanFilePath,
|
|
901
912
|
});
|
|
902
|
-
await this.session.prompt(
|
|
913
|
+
await this.session.prompt(planModePrompt, { synthetic: true });
|
|
903
914
|
}
|
|
904
915
|
|
|
905
916
|
async handlePlanModeCommand(initialPrompt?: string): Promise<void> {
|
|
@@ -988,6 +999,10 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
988
999
|
}
|
|
989
1000
|
this.#extensionUiController.clearExtensionTerminalInputListeners();
|
|
990
1001
|
this.#extensionUiController.clearHookWidgets();
|
|
1002
|
+
for (const unsubscribe of this.#eventBusUnsubscribers) {
|
|
1003
|
+
unsubscribe();
|
|
1004
|
+
}
|
|
1005
|
+
this.#eventBusUnsubscribers = [];
|
|
991
1006
|
this.#observerRegistry.dispose();
|
|
992
1007
|
this.#eventController.dispose();
|
|
993
1008
|
this.statusLine.dispose();
|
|
@@ -1086,6 +1101,48 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
1086
1101
|
this.#uiHelpers.showWarning(message);
|
|
1087
1102
|
}
|
|
1088
1103
|
|
|
1104
|
+
#handleLspStartupEvent(event: LspStartupEvent): void {
|
|
1105
|
+
this.#updateWelcomeLspServers();
|
|
1106
|
+
|
|
1107
|
+
if (event.type === "failed") {
|
|
1108
|
+
this.showWarning(`LSP startup failed: ${event.error}. It will retry lazily on write.`);
|
|
1109
|
+
return;
|
|
1110
|
+
}
|
|
1111
|
+
|
|
1112
|
+
const failedServers = event.servers.filter(server => server.status === "error");
|
|
1113
|
+
|
|
1114
|
+
if (failedServers.length === 1) {
|
|
1115
|
+
const failedServer = failedServers[0];
|
|
1116
|
+
const detail = failedServer.error ? `: ${failedServer.error}` : "";
|
|
1117
|
+
this.showWarning(`LSP startup failed for ${failedServer.name}${detail}. It will retry lazily on write.`);
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
if (failedServers.length > 1) {
|
|
1122
|
+
const failedNames = failedServers.map(server => server.name).join(", ");
|
|
1123
|
+
this.showWarning(`LSP startup failed for ${failedNames}. It will retry lazily on write.`);
|
|
1124
|
+
}
|
|
1125
|
+
}
|
|
1126
|
+
|
|
1127
|
+
#getWelcomeLspServers(): WelcomeLspServerInfo[] {
|
|
1128
|
+
return (
|
|
1129
|
+
this.lspServers?.map(server => ({
|
|
1130
|
+
name: server.name,
|
|
1131
|
+
status: server.status,
|
|
1132
|
+
fileTypes: server.fileTypes,
|
|
1133
|
+
})) ?? []
|
|
1134
|
+
);
|
|
1135
|
+
}
|
|
1136
|
+
|
|
1137
|
+
#updateWelcomeLspServers(): void {
|
|
1138
|
+
if (!this.#welcomeComponent) {
|
|
1139
|
+
return;
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
this.#welcomeComponent.setLspServers(this.#getWelcomeLspServers());
|
|
1143
|
+
this.ui.requestRender();
|
|
1144
|
+
}
|
|
1145
|
+
|
|
1089
1146
|
ensureLoadingAnimation(): void {
|
|
1090
1147
|
if (!this.loadingAnimation) {
|
|
1091
1148
|
this.statusContainer.clear();
|