@oh-my-pi/pi-coding-agent 8.2.2 → 8.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +31 -0
- package/package.json +6 -6
- package/src/config/model-registry.ts +8 -0
- package/src/discovery/helpers.ts +12 -2
- package/src/extensibility/extensions/runner.ts +1 -0
- package/src/extensibility/extensions/types.ts +3 -0
- package/src/extensibility/extensions/wrapper.ts +4 -0
- package/src/extensibility/hooks/tool-wrapper.ts +4 -0
- package/src/ipy/executor.ts +4 -0
- package/src/lsp/index.ts +6 -4
- package/src/lsp/render.ts +115 -19
- package/src/lsp/types.ts +1 -0
- package/src/modes/components/tool-execution.ts +22 -6
- package/src/modes/controllers/event-controller.ts +1 -0
- package/src/modes/controllers/extension-ui-controller.ts +2 -0
- package/src/modes/controllers/input-controller.ts +2 -2
- package/src/modes/interactive-mode.ts +30 -0
- package/src/modes/rpc/rpc-mode.ts +4 -0
- package/src/modes/theme/theme.ts +10 -107
- package/src/modes/types.ts +2 -0
- package/src/patch/applicator.ts +209 -41
- package/src/patch/fuzzy.ts +148 -28
- package/src/patch/index.ts +25 -2
- package/src/patch/parser.ts +5 -0
- package/src/patch/types.ts +25 -0
- package/src/sdk.ts +6 -0
- package/src/session/agent-session.ts +1 -0
- package/src/session/session-manager.ts +3 -0
- package/src/task/agents.ts +1 -1
- package/src/task/executor.ts +49 -17
- package/src/task/index.ts +16 -3
- package/src/task/types.ts +3 -3
- package/src/task/worker-protocol.ts +8 -0
- package/src/task/worker.ts +11 -0
- package/src/tools/bash.ts +1 -0
- package/src/tools/fetch.ts +1 -0
- package/src/tools/index.ts +19 -1
- package/src/tools/write.ts +82 -83
- package/src/tui/output-block.ts +26 -13
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,37 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [8.4.0] - 2026-01-25
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Added extension API to set working/loading messages during streaming
|
|
9
|
+
- Added task worker propagation of context files, skills, and prompt templates
|
|
10
|
+
- Added subagent option to skip Python preflight checks when Python tooling is unused
|
|
11
|
+
- Model field now accepts string arrays for fallback model prioritization
|
|
12
|
+
|
|
13
|
+
### Changed
|
|
14
|
+
- Merged patch application warnings into edit tool diagnostics output
|
|
15
|
+
- Cached Python prelude docs for subagent workers to avoid repeated warmups
|
|
16
|
+
- Simplified image placeholders inserted on paste to match Claude-style markers
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
- Rewrote empty or corrupted session files to restore valid headers
|
|
20
|
+
- Improved patch applicator ambiguity errors with match previews and overlap detection
|
|
21
|
+
- Fixed Task tool agent model resolution to honor comma-separated model lists
|
|
22
|
+
## [8.3.0] - 2026-01-25
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
- Added request parameter tracking to LSP tool rendering for better diagnostics visibility
|
|
26
|
+
- Added async diff computation and Kitty protocol support to tool execution rendering
|
|
27
|
+
- Refactored patch applicator with improved fuzzy matching (7-pass sequence matching with Levenshtein distance) and indentation adjustment
|
|
28
|
+
- Added inline rendering flag to bash and fetch tool renderers
|
|
29
|
+
- Extracted constants for preview formatting to improve code maintainability
|
|
30
|
+
- Exposed mergeCallAndResult and inline rendering options from tools to their wrappers
|
|
31
|
+
- Added timeout validation and normalization for tool timeout parameters
|
|
32
|
+
|
|
33
|
+
### Fixed
|
|
34
|
+
- Fixed output block border rendering (bottom-right corner was missing)
|
|
35
|
+
- Added background control parameter to output block rendering
|
|
5
36
|
## [8.2.2] - 2026-01-24
|
|
6
37
|
|
|
7
38
|
### Removed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "8.
|
|
3
|
+
"version": "8.4.0",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -75,11 +75,11 @@
|
|
|
75
75
|
"test": "bun test"
|
|
76
76
|
},
|
|
77
77
|
"dependencies": {
|
|
78
|
-
"@oh-my-pi/omp-stats": "8.
|
|
79
|
-
"@oh-my-pi/pi-agent-core": "8.
|
|
80
|
-
"@oh-my-pi/pi-ai": "8.
|
|
81
|
-
"@oh-my-pi/pi-tui": "8.
|
|
82
|
-
"@oh-my-pi/pi-utils": "8.
|
|
78
|
+
"@oh-my-pi/omp-stats": "8.4.0",
|
|
79
|
+
"@oh-my-pi/pi-agent-core": "8.4.0",
|
|
80
|
+
"@oh-my-pi/pi-ai": "8.4.0",
|
|
81
|
+
"@oh-my-pi/pi-tui": "8.4.0",
|
|
82
|
+
"@oh-my-pi/pi-utils": "8.4.0",
|
|
83
83
|
"@openai/agents": "^0.4.3",
|
|
84
84
|
"@sinclair/typebox": "^0.34.46",
|
|
85
85
|
"ajv": "^8.17.1",
|
|
@@ -19,12 +19,18 @@ import type { AuthStorage } from "../session/auth-storage";
|
|
|
19
19
|
|
|
20
20
|
const Ajv = (AjvModule as any).default || AjvModule;
|
|
21
21
|
|
|
22
|
+
const OpenRouterRoutingSchema = Type.Object({
|
|
23
|
+
only: Type.Optional(Type.Array(Type.String())),
|
|
24
|
+
order: Type.Optional(Type.Array(Type.String())),
|
|
25
|
+
});
|
|
26
|
+
|
|
22
27
|
// Schema for OpenAI compatibility settings
|
|
23
28
|
const OpenAICompatSchema = Type.Object({
|
|
24
29
|
supportsStore: Type.Optional(Type.Boolean()),
|
|
25
30
|
supportsDeveloperRole: Type.Optional(Type.Boolean()),
|
|
26
31
|
supportsReasoningEffort: Type.Optional(Type.Boolean()),
|
|
27
32
|
maxTokensField: Type.Optional(Type.Union([Type.Literal("max_completion_tokens"), Type.Literal("max_tokens")])),
|
|
33
|
+
openRouterRouting: Type.Optional(OpenRouterRoutingSchema),
|
|
28
34
|
});
|
|
29
35
|
|
|
30
36
|
// Schema for custom model definition
|
|
@@ -36,6 +42,7 @@ const ModelDefinitionSchema = Type.Object({
|
|
|
36
42
|
Type.Literal("openai-completions"),
|
|
37
43
|
Type.Literal("openai-responses"),
|
|
38
44
|
Type.Literal("openai-codex-responses"),
|
|
45
|
+
Type.Literal("azure-openai-responses"),
|
|
39
46
|
Type.Literal("anthropic-messages"),
|
|
40
47
|
Type.Literal("google-generative-ai"),
|
|
41
48
|
Type.Literal("google-vertex"),
|
|
@@ -63,6 +70,7 @@ const ProviderConfigSchema = Type.Object({
|
|
|
63
70
|
Type.Literal("openai-completions"),
|
|
64
71
|
Type.Literal("openai-responses"),
|
|
65
72
|
Type.Literal("openai-codex-responses"),
|
|
73
|
+
Type.Literal("azure-openai-responses"),
|
|
66
74
|
Type.Literal("anthropic-messages"),
|
|
67
75
|
Type.Literal("google-generative-ai"),
|
|
68
76
|
Type.Literal("google-vertex"),
|
package/src/discovery/helpers.ts
CHANGED
|
@@ -156,13 +156,23 @@ export function parseArrayOrCSV(value: unknown): string[] | undefined {
|
|
|
156
156
|
return undefined;
|
|
157
157
|
}
|
|
158
158
|
|
|
159
|
+
/**
|
|
160
|
+
* Parse model field into a prioritized list.
|
|
161
|
+
*/
|
|
162
|
+
export function parseModelList(value: unknown): string[] | undefined {
|
|
163
|
+
const parsed = parseArrayOrCSV(value);
|
|
164
|
+
if (!parsed) return undefined;
|
|
165
|
+
const normalized = parsed.map(entry => entry.trim()).filter(Boolean);
|
|
166
|
+
return normalized.length > 0 ? normalized : undefined;
|
|
167
|
+
}
|
|
168
|
+
|
|
159
169
|
/** Parsed agent fields from frontmatter (excludes source/filePath/systemPrompt) */
|
|
160
170
|
export interface ParsedAgentFields {
|
|
161
171
|
name: string;
|
|
162
172
|
description: string;
|
|
163
173
|
tools?: string[];
|
|
164
174
|
spawns?: string[] | "*";
|
|
165
|
-
model?: string;
|
|
175
|
+
model?: string[];
|
|
166
176
|
output?: unknown;
|
|
167
177
|
thinkingLevel?: ThinkingLevel;
|
|
168
178
|
}
|
|
@@ -202,7 +212,7 @@ export function parseAgentFields(frontmatter: Record<string, unknown>): ParsedAg
|
|
|
202
212
|
}
|
|
203
213
|
|
|
204
214
|
const output = frontmatter.output !== undefined ? frontmatter.output : undefined;
|
|
205
|
-
const model =
|
|
215
|
+
const model = parseModelList(frontmatter.model);
|
|
206
216
|
const thinkingLevel = parseThinkingLevel(frontmatter);
|
|
207
217
|
|
|
208
218
|
return { name, description, tools, spawns, model, output, thinkingLevel };
|
|
@@ -69,6 +69,9 @@ export interface ExtensionUIContext {
|
|
|
69
69
|
/** Set status text in the footer/status bar. Pass undefined to clear. */
|
|
70
70
|
setStatus(key: string, text: string | undefined): void;
|
|
71
71
|
|
|
72
|
+
/** Set the working/loading message shown during streaming. Call with no argument to restore default. */
|
|
73
|
+
setWorkingMessage(message?: string): void;
|
|
74
|
+
|
|
72
75
|
/** Set a widget to display above the editor. Accepts string array or component factory. */
|
|
73
76
|
setWidget(key: string, content: string[] | undefined): void;
|
|
74
77
|
setWidget(key: string, content: ((tui: TUI, theme: Theme) => Component & { dispose?(): void }) | undefined): void;
|
|
@@ -79,6 +79,8 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
79
79
|
parameters: TParameters;
|
|
80
80
|
renderCall?: AgentTool<TParameters, TDetails>["renderCall"];
|
|
81
81
|
renderResult?: AgentTool<TParameters, TDetails>["renderResult"];
|
|
82
|
+
mergeCallAndResult?: boolean;
|
|
83
|
+
inline?: boolean;
|
|
82
84
|
|
|
83
85
|
constructor(
|
|
84
86
|
private tool: AgentTool<TParameters, TDetails>,
|
|
@@ -90,6 +92,8 @@ export class ExtensionToolWrapper<TParameters extends TSchema = TSchema, TDetail
|
|
|
90
92
|
this.parameters = tool.parameters;
|
|
91
93
|
this.renderCall = tool.renderCall;
|
|
92
94
|
this.renderResult = tool.renderResult;
|
|
95
|
+
this.mergeCallAndResult = (tool as { mergeCallAndResult?: boolean }).mergeCallAndResult;
|
|
96
|
+
this.inline = (tool as { inline?: boolean }).inline;
|
|
93
97
|
}
|
|
94
98
|
|
|
95
99
|
async execute(
|
|
@@ -23,6 +23,8 @@ export class HookToolWrapper<TParameters extends TSchema = TSchema, TDetails = u
|
|
|
23
23
|
parameters: TParameters;
|
|
24
24
|
renderCall?: AgentTool<TParameters, TDetails>["renderCall"];
|
|
25
25
|
renderResult?: AgentTool<TParameters, TDetails>["renderResult"];
|
|
26
|
+
mergeCallAndResult?: boolean;
|
|
27
|
+
inline?: boolean;
|
|
26
28
|
|
|
27
29
|
constructor(
|
|
28
30
|
private tool: AgentTool<TParameters, TDetails>,
|
|
@@ -34,6 +36,8 @@ export class HookToolWrapper<TParameters extends TSchema = TSchema, TDetails = u
|
|
|
34
36
|
this.parameters = tool.parameters;
|
|
35
37
|
this.renderCall = tool.renderCall;
|
|
36
38
|
this.renderResult = tool.renderResult;
|
|
39
|
+
this.mergeCallAndResult = (tool as { mergeCallAndResult?: boolean }).mergeCallAndResult;
|
|
40
|
+
this.inline = (tool as { inline?: boolean }).inline;
|
|
37
41
|
}
|
|
38
42
|
|
|
39
43
|
async execute(
|
package/src/ipy/executor.ts
CHANGED
|
@@ -187,6 +187,10 @@ export function getPreludeDocs(): PreludeHelper[] {
|
|
|
187
187
|
return cachedPreludeDocs ?? [];
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
export function setPreludeDocsCache(docs: PreludeHelper[]): void {
|
|
191
|
+
cachedPreludeDocs = docs;
|
|
192
|
+
}
|
|
193
|
+
|
|
190
194
|
export function resetPreludeDocsCache(): void {
|
|
191
195
|
cachedPreludeDocs = null;
|
|
192
196
|
}
|
package/src/lsp/index.ts
CHANGED
|
@@ -955,6 +955,8 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
955
955
|
public readonly parameters = lspSchema;
|
|
956
956
|
public readonly renderCall = renderCall;
|
|
957
957
|
public readonly renderResult = renderResult;
|
|
958
|
+
public readonly mergeCallAndResult = true;
|
|
959
|
+
public readonly inline = true;
|
|
958
960
|
|
|
959
961
|
private readonly session: ToolSession;
|
|
960
962
|
|
|
@@ -1011,7 +1013,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1011
1013
|
const output = lspmuxStatus ? `${serverStatus}\n${lspmuxStatus}` : serverStatus;
|
|
1012
1014
|
return {
|
|
1013
1015
|
content: [{ type: "text", text: output }],
|
|
1014
|
-
details: { action, success: true },
|
|
1016
|
+
details: { action, success: true, request: params },
|
|
1015
1017
|
};
|
|
1016
1018
|
}
|
|
1017
1019
|
|
|
@@ -1025,7 +1027,7 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1025
1027
|
text: `Workspace diagnostics (${result.projectType.description}):\n${result.output}`,
|
|
1026
1028
|
},
|
|
1027
1029
|
],
|
|
1028
|
-
details: { action, success: true },
|
|
1030
|
+
details: { action, success: true, request: params },
|
|
1029
1031
|
};
|
|
1030
1032
|
}
|
|
1031
1033
|
|
|
@@ -1636,13 +1638,13 @@ export class LspTool implements AgentTool<typeof lspSchema, LspToolDetails, Them
|
|
|
1636
1638
|
|
|
1637
1639
|
return {
|
|
1638
1640
|
content: [{ type: "text", text: output }],
|
|
1639
|
-
details: { serverName, action, success: true },
|
|
1641
|
+
details: { serverName, action, success: true, request: params },
|
|
1640
1642
|
};
|
|
1641
1643
|
} catch (err) {
|
|
1642
1644
|
const errorMessage = err instanceof Error ? err.message : String(err);
|
|
1643
1645
|
return {
|
|
1644
1646
|
content: [{ type: "text", text: `LSP error: ${errorMessage}` }],
|
|
1645
|
-
details: { serverName, action, success: false },
|
|
1647
|
+
details: { serverName, action, success: false, request: params },
|
|
1646
1648
|
};
|
|
1647
1649
|
}
|
|
1648
1650
|
}
|
package/src/lsp/render.ts
CHANGED
|
@@ -7,11 +7,18 @@
|
|
|
7
7
|
* - Grouped references and symbols
|
|
8
8
|
* - Collapsible/expandable views
|
|
9
9
|
*/
|
|
10
|
-
import type {
|
|
10
|
+
import type { RenderResultOptions } from "@oh-my-pi/pi-agent-core";
|
|
11
11
|
import { type Component, Text } from "@oh-my-pi/pi-tui";
|
|
12
12
|
import { highlight, supportsLanguage } from "cli-highlight";
|
|
13
13
|
import { getLanguageFromPath, type Theme } from "../modes/theme/theme";
|
|
14
|
-
import {
|
|
14
|
+
import {
|
|
15
|
+
formatExpandHint,
|
|
16
|
+
formatMoreItems,
|
|
17
|
+
formatStatusIcon,
|
|
18
|
+
shortenPath,
|
|
19
|
+
TRUNCATE_LENGTHS,
|
|
20
|
+
truncate,
|
|
21
|
+
} from "../tools/render-utils";
|
|
15
22
|
import { renderOutputBlock, renderStatusLine } from "../tui";
|
|
16
23
|
import type { LspParams, LspToolDetails } from "./types";
|
|
17
24
|
|
|
@@ -23,15 +30,74 @@ import type { LspParams, LspToolDetails } from "./types";
|
|
|
23
30
|
* Render the LSP tool call in the TUI.
|
|
24
31
|
* Shows: "lsp <operation> <file/filecount>"
|
|
25
32
|
*/
|
|
26
|
-
export function renderCall(args:
|
|
27
|
-
const
|
|
33
|
+
export function renderCall(args: LspParams, theme: Theme): Text {
|
|
34
|
+
const actionLabel = (args.action ?? "request").replace(/_/g, " ");
|
|
35
|
+
const queryPreview = args.query ? truncate(args.query, TRUNCATE_LENGTHS.SHORT, theme.format.ellipsis) : undefined;
|
|
36
|
+
const replacementPreview = args.replacement
|
|
37
|
+
? truncate(args.replacement, TRUNCATE_LENGTHS.SHORT, theme.format.ellipsis)
|
|
38
|
+
: undefined;
|
|
39
|
+
|
|
40
|
+
let target: string | undefined;
|
|
41
|
+
let hasFileTarget = false;
|
|
42
|
+
|
|
43
|
+
if (args.file) {
|
|
44
|
+
target = shortenPath(args.file);
|
|
45
|
+
hasFileTarget = true;
|
|
46
|
+
} else if (args.files?.length === 1) {
|
|
47
|
+
target = shortenPath(args.files[0]);
|
|
48
|
+
hasFileTarget = true;
|
|
49
|
+
} else if (args.files?.length) {
|
|
50
|
+
target = `${args.files.length} files`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
if (hasFileTarget && args.line !== undefined) {
|
|
54
|
+
const col = args.column !== undefined ? `:${args.column}` : "";
|
|
55
|
+
target += `:${args.line}${col}`;
|
|
56
|
+
if (args.end_line !== undefined) {
|
|
57
|
+
const endCol = args.end_character !== undefined ? `:${args.end_character}` : "";
|
|
58
|
+
target += `-${args.end_line}${endCol}`;
|
|
59
|
+
}
|
|
60
|
+
} else if (!target && args.line !== undefined) {
|
|
61
|
+
const col = args.column !== undefined ? `:${args.column}` : "";
|
|
62
|
+
target = `line ${args.line}${col}`;
|
|
63
|
+
if (args.end_line !== undefined) {
|
|
64
|
+
const endCol = args.end_character !== undefined ? `:${args.end_character}` : "";
|
|
65
|
+
target += `-${args.end_line}${endCol}`;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
28
69
|
const meta: string[] = [];
|
|
29
|
-
if (
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
70
|
+
if (queryPreview && target) meta.push(`query:${queryPreview}`);
|
|
71
|
+
if (args.new_name) meta.push(`new:${args.new_name}`);
|
|
72
|
+
if (replacementPreview) meta.push(`replace:${replacementPreview}`);
|
|
73
|
+
if (args.kind) meta.push(`kind:${args.kind}`);
|
|
74
|
+
if (args.apply !== undefined) meta.push(`apply:${args.apply ? "true" : "false"}`);
|
|
75
|
+
if (args.action_index !== undefined) meta.push(`action:${args.action_index}`);
|
|
76
|
+
if (args.include_declaration !== undefined) {
|
|
77
|
+
meta.push(`include_decl:${args.include_declaration ? "true" : "false"}`);
|
|
78
|
+
}
|
|
79
|
+
if (args.end_line !== undefined && args.line === undefined) {
|
|
80
|
+
const endCol = args.end_character !== undefined ? `:${args.end_character}` : "";
|
|
81
|
+
meta.push(`end:${args.end_line}${endCol}`);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const descriptionParts = [actionLabel];
|
|
85
|
+
if (target) {
|
|
86
|
+
descriptionParts.push(target);
|
|
87
|
+
} else if (queryPreview) {
|
|
88
|
+
descriptionParts.push(queryPreview);
|
|
33
89
|
}
|
|
34
|
-
|
|
90
|
+
|
|
91
|
+
const text = renderStatusLine(
|
|
92
|
+
{
|
|
93
|
+
icon: "pending",
|
|
94
|
+
title: "LSP",
|
|
95
|
+
description: descriptionParts.join(" "),
|
|
96
|
+
meta,
|
|
97
|
+
},
|
|
98
|
+
theme,
|
|
99
|
+
);
|
|
100
|
+
|
|
35
101
|
return new Text(text, 0, 0);
|
|
36
102
|
}
|
|
37
103
|
|
|
@@ -44,14 +110,15 @@ export function renderCall(args: unknown, theme: Theme): Text {
|
|
|
44
110
|
* Detects hover, diagnostics, references, symbols, etc. and formats accordingly.
|
|
45
111
|
*/
|
|
46
112
|
export function renderResult(
|
|
47
|
-
result:
|
|
113
|
+
result: { content: Array<{ type: string; text?: string }>; details?: LspToolDetails; isError?: boolean },
|
|
48
114
|
options: RenderResultOptions,
|
|
49
115
|
theme: Theme,
|
|
50
116
|
args?: LspParams & { file?: string; files?: string[] },
|
|
51
117
|
): Component {
|
|
52
118
|
const content = result.content?.[0];
|
|
53
119
|
if (!content || content.type !== "text" || !("text" in content) || !content.text) {
|
|
54
|
-
const
|
|
120
|
+
const icon = formatStatusIcon("warning", theme, options.spinnerFrame);
|
|
121
|
+
const header = `${icon} LSP`;
|
|
55
122
|
return new Text([header, theme.fg("dim", "No result")].join("\n"), 0, 0);
|
|
56
123
|
}
|
|
57
124
|
|
|
@@ -94,22 +161,50 @@ export function renderResult(
|
|
|
94
161
|
}
|
|
95
162
|
}
|
|
96
163
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
} else if (
|
|
102
|
-
|
|
164
|
+
const request = args ?? result.details?.request;
|
|
165
|
+
const requestLines: string[] = [];
|
|
166
|
+
if (request?.file) {
|
|
167
|
+
requestLines.push(theme.fg("toolOutput", request.file));
|
|
168
|
+
} else if (request?.files?.length === 1) {
|
|
169
|
+
requestLines.push(theme.fg("toolOutput", request.files[0]));
|
|
170
|
+
} else if (request?.files?.length) {
|
|
171
|
+
requestLines.push(theme.fg("dim", `${request.files.length} file(s)`));
|
|
172
|
+
}
|
|
173
|
+
if (request?.line !== undefined) {
|
|
174
|
+
const col = request.column !== undefined ? `:${request.column}` : "";
|
|
175
|
+
requestLines.push(theme.fg("dim", `line ${request.line}${col}`));
|
|
103
176
|
}
|
|
104
|
-
|
|
177
|
+
if (request?.end_line !== undefined) {
|
|
178
|
+
const endCol = request.end_character !== undefined ? `:${request.end_character}` : "";
|
|
179
|
+
requestLines.push(theme.fg("dim", `end ${request.end_line}${endCol}`));
|
|
180
|
+
}
|
|
181
|
+
if (request?.query) requestLines.push(theme.fg("dim", `query: ${request.query}`));
|
|
182
|
+
if (request?.new_name) requestLines.push(theme.fg("dim", `new name: ${request.new_name}`));
|
|
183
|
+
if (request?.replacement) requestLines.push(theme.fg("dim", `replacement: ${request.replacement}`));
|
|
184
|
+
if (request?.kind) requestLines.push(theme.fg("dim", `kind: ${request.kind}`));
|
|
185
|
+
if (request?.apply !== undefined) requestLines.push(theme.fg("dim", `apply: ${request.apply ? "true" : "false"}`));
|
|
186
|
+
if (request?.action_index !== undefined) requestLines.push(theme.fg("dim", `action: ${request.action_index}`));
|
|
187
|
+
if (request?.include_declaration !== undefined) {
|
|
188
|
+
requestLines.push(theme.fg("dim", `include declaration: ${request.include_declaration ? "true" : "false"}`));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
const actionLabel = (request?.action ?? result.details?.action ?? label.toLowerCase()).replace(/_/g, " ");
|
|
192
|
+
const status = options.isPartial ? "running" : result.isError ? "error" : "success";
|
|
193
|
+
const icon = formatStatusIcon(status, theme, options.spinnerFrame);
|
|
194
|
+
const header = `${icon} LSP ${actionLabel}`;
|
|
195
|
+
|
|
105
196
|
return {
|
|
106
197
|
render: (width: number) =>
|
|
107
198
|
renderOutputBlock(
|
|
108
199
|
{
|
|
109
200
|
header,
|
|
110
201
|
state,
|
|
111
|
-
sections: [
|
|
202
|
+
sections: [
|
|
203
|
+
...(requestLines.length > 0 ? [{ lines: requestLines }] : []),
|
|
204
|
+
{ label: theme.fg("toolTitle", "Response"), lines: bodyLines },
|
|
205
|
+
],
|
|
112
206
|
width,
|
|
207
|
+
applyBg: false,
|
|
113
208
|
},
|
|
114
209
|
theme,
|
|
115
210
|
),
|
|
@@ -612,4 +707,5 @@ export const lspToolRenderer = {
|
|
|
612
707
|
renderCall,
|
|
613
708
|
renderResult,
|
|
614
709
|
mergeCallAndResult: true,
|
|
710
|
+
inline: true,
|
|
615
711
|
};
|
package/src/lsp/types.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { AgentTool } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import {
|
|
3
3
|
Box,
|
|
4
|
+
type Component,
|
|
4
5
|
Container,
|
|
5
6
|
getCapabilities,
|
|
6
7
|
getImageDimensions,
|
|
@@ -11,6 +12,7 @@ import {
|
|
|
11
12
|
type TUI,
|
|
12
13
|
} from "@oh-my-pi/pi-tui";
|
|
13
14
|
import { sanitizeText } from "@oh-my-pi/pi-utils";
|
|
15
|
+
import type { Theme } from "../../modes/theme/theme";
|
|
14
16
|
import { theme } from "../../modes/theme/theme";
|
|
15
17
|
import { computeEditDiff, computePatchDiff, type EditDiffError, type EditDiffResult } from "../../patch";
|
|
16
18
|
import { BASH_DEFAULT_PREVIEW_LINES } from "../../tools/bash";
|
|
@@ -376,7 +378,8 @@ export class ToolExecutionComponent extends Container {
|
|
|
376
378
|
const tool = this.tool;
|
|
377
379
|
const mergeCallAndResult = Boolean((tool as { mergeCallAndResult?: boolean }).mergeCallAndResult);
|
|
378
380
|
// Custom tools use Box for flexible component rendering
|
|
379
|
-
|
|
381
|
+
const inline = Boolean((tool as { inline?: boolean }).inline);
|
|
382
|
+
this.contentBox.setBgFn(inline ? undefined : bgFn);
|
|
380
383
|
this.contentBox.clear();
|
|
381
384
|
|
|
382
385
|
// Render call component
|
|
@@ -404,10 +407,17 @@ export class ToolExecutionComponent extends Container {
|
|
|
404
407
|
// Render result component if we have a result
|
|
405
408
|
if (this.result && tool.renderResult) {
|
|
406
409
|
try {
|
|
407
|
-
const
|
|
408
|
-
{ content:
|
|
410
|
+
const renderResult = tool.renderResult as (
|
|
411
|
+
result: { content: Array<{ type: string; text?: string }>; details?: unknown; isError?: boolean },
|
|
412
|
+
options: { expanded: boolean; isPartial: boolean; spinnerFrame?: number },
|
|
413
|
+
theme: Theme,
|
|
414
|
+
args?: unknown,
|
|
415
|
+
) => Component;
|
|
416
|
+
const resultComponent = renderResult(
|
|
417
|
+
{ content: this.result.content as any, details: this.result.details, isError: this.result.isError },
|
|
409
418
|
{ expanded: this.expanded, isPartial: this.isPartial, spinnerFrame: this.spinnerFrame },
|
|
410
419
|
theme,
|
|
420
|
+
this.args,
|
|
411
421
|
);
|
|
412
422
|
if (resultComponent) {
|
|
413
423
|
// Ensure component has invalidate() method for Component interface
|
|
@@ -542,10 +552,16 @@ export class ToolExecutionComponent extends Container {
|
|
|
542
552
|
}
|
|
543
553
|
|
|
544
554
|
/**
|
|
545
|
-
* Build render context for tools that need extra state (bash, edit)
|
|
555
|
+
* Build render context for tools that need extra state (bash, python, edit)
|
|
546
556
|
*/
|
|
547
557
|
private buildRenderContext(): Record<string, unknown> {
|
|
548
558
|
const context: Record<string, unknown> = {};
|
|
559
|
+
const normalizeTimeoutSeconds = (value: unknown, maxSeconds: number): number | undefined => {
|
|
560
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return undefined;
|
|
561
|
+
let timeoutSec = value > 1000 ? value / 1000 : value;
|
|
562
|
+
timeoutSec = Math.max(1, Math.min(maxSeconds, timeoutSec));
|
|
563
|
+
return timeoutSec;
|
|
564
|
+
};
|
|
549
565
|
|
|
550
566
|
if (this.toolName === "bash" && this.result) {
|
|
551
567
|
// Pass raw output and expanded state - renderer handles width-aware truncation
|
|
@@ -553,13 +569,13 @@ export class ToolExecutionComponent extends Container {
|
|
|
553
569
|
context.output = output;
|
|
554
570
|
context.expanded = this.expanded;
|
|
555
571
|
context.previewLines = BASH_DEFAULT_PREVIEW_LINES;
|
|
556
|
-
context.timeout =
|
|
572
|
+
context.timeout = normalizeTimeoutSeconds(this.args?.timeout, 3600);
|
|
557
573
|
} else if (this.toolName === "python" && this.result) {
|
|
558
574
|
const output = this.getTextOutput().trimEnd();
|
|
559
575
|
context.output = output;
|
|
560
576
|
context.expanded = this.expanded;
|
|
561
577
|
context.previewLines = PYTHON_DEFAULT_PREVIEW_LINES;
|
|
562
|
-
context.timeout =
|
|
578
|
+
context.timeout = normalizeTimeoutSeconds(this.args?.timeout, 600);
|
|
563
579
|
} else if (this.toolName === "edit") {
|
|
564
580
|
// Edit needs diff preview and renderDiff function
|
|
565
581
|
context.editDiffPreview = this.editDiffPreview;
|
|
@@ -30,6 +30,7 @@ export class ExtensionUiController {
|
|
|
30
30
|
input: (title, placeholder, _dialogOptions) => this.showHookInput(title, placeholder),
|
|
31
31
|
notify: (message, type) => this.showHookNotify(message, type),
|
|
32
32
|
setStatus: (key, text) => this.setHookStatus(key, text),
|
|
33
|
+
setWorkingMessage: message => this.ctx.setWorkingMessage(message),
|
|
33
34
|
setWidget: (key, content) => this.setHookWidget(key, content),
|
|
34
35
|
setTitle: title => setTerminalTitle(title),
|
|
35
36
|
custom: (factory, _options) => this.showHookCustom(factory),
|
|
@@ -389,6 +390,7 @@ export class ExtensionUiController {
|
|
|
389
390
|
input: async (_title: string, _placeholder?: string, _dialogOptions?: unknown) => undefined,
|
|
390
391
|
notify: () => {},
|
|
391
392
|
setStatus: () => {},
|
|
393
|
+
setWorkingMessage: () => {},
|
|
392
394
|
setWidget: () => {},
|
|
393
395
|
setTitle: () => {},
|
|
394
396
|
custom: async () => undefined as never,
|
|
@@ -541,9 +541,9 @@ export class InputController {
|
|
|
541
541
|
data: imageData.data,
|
|
542
542
|
mimeType: imageData.mimeType,
|
|
543
543
|
});
|
|
544
|
-
// Insert
|
|
544
|
+
// Insert placeholder at cursor like Claude does
|
|
545
545
|
const imageNum = this.ctx.pendingImages.length;
|
|
546
|
-
const placeholder =
|
|
546
|
+
const placeholder = `[Image #${imageNum}]`;
|
|
547
547
|
this.ctx.editor.insertText(`${placeholder} `);
|
|
548
548
|
this.ctx.ui.requestRender();
|
|
549
549
|
return true;
|
|
@@ -102,6 +102,8 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
102
102
|
public loadingAnimation: Loader | undefined = undefined;
|
|
103
103
|
public autoCompactionLoader: Loader | undefined = undefined;
|
|
104
104
|
public retryLoader: Loader | undefined = undefined;
|
|
105
|
+
private pendingWorkingMessage: string | undefined;
|
|
106
|
+
private readonly defaultWorkingMessage = `Working${theme.format.ellipsis} (esc to interrupt)`;
|
|
105
107
|
public autoCompactionEscapeHandler?: () => void;
|
|
106
108
|
public retryEscapeHandler?: () => void;
|
|
107
109
|
public unsubscribe?: () => void;
|
|
@@ -160,6 +162,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
160
162
|
this.statusContainer = new Container();
|
|
161
163
|
this.todoContainer = new Container();
|
|
162
164
|
this.editor = new CustomEditor(getEditorTheme());
|
|
165
|
+
this.editor.setUseTerminalCursor(this.ui.getShowHardwareCursor());
|
|
163
166
|
this.editor.onAutocompleteCancel = () => {
|
|
164
167
|
this.ui.requestRender(true);
|
|
165
168
|
};
|
|
@@ -538,6 +541,33 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
538
541
|
this.uiHelpers.showWarning(message);
|
|
539
542
|
}
|
|
540
543
|
|
|
544
|
+
setWorkingMessage(message?: string): void {
|
|
545
|
+
if (message === undefined) {
|
|
546
|
+
this.pendingWorkingMessage = undefined;
|
|
547
|
+
if (this.loadingAnimation) {
|
|
548
|
+
this.loadingAnimation.setMessage(this.defaultWorkingMessage);
|
|
549
|
+
}
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
|
|
553
|
+
if (this.loadingAnimation) {
|
|
554
|
+
this.loadingAnimation.setMessage(message);
|
|
555
|
+
return;
|
|
556
|
+
}
|
|
557
|
+
|
|
558
|
+
this.pendingWorkingMessage = message;
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
applyPendingWorkingMessage(): void {
|
|
562
|
+
if (this.pendingWorkingMessage === undefined) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
const message = this.pendingWorkingMessage;
|
|
567
|
+
this.pendingWorkingMessage = undefined;
|
|
568
|
+
this.setWorkingMessage(message);
|
|
569
|
+
}
|
|
570
|
+
|
|
541
571
|
showNewVersionNotification(newVersion: string): void {
|
|
542
572
|
this.uiHelpers.showNewVersionNotification(newVersion);
|
|
543
573
|
}
|
|
@@ -187,6 +187,10 @@ export async function runRpcMode(session: AgentSession): Promise<never> {
|
|
|
187
187
|
} as RpcExtensionUIRequest);
|
|
188
188
|
}
|
|
189
189
|
|
|
190
|
+
setWorkingMessage(_message?: string): void {
|
|
191
|
+
// Not supported in RPC mode
|
|
192
|
+
}
|
|
193
|
+
|
|
190
194
|
setWidget(key: string, content: unknown): void {
|
|
191
195
|
// Only support string arrays in RPC mode - factory functions are ignored
|
|
192
196
|
if (content === undefined || Array.isArray(content)) {
|