@oh-my-pi/pi-coding-agent 4.2.3 → 4.3.1
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 +23 -0
- package/package.json +5 -5
- package/src/cli/update-cli.ts +2 -2
- package/src/config.ts +5 -5
- package/src/core/auth-storage.ts +13 -1
- package/src/core/cursor/exec-bridge.ts +234 -0
- package/src/core/custom-commands/loader.ts +3 -1
- package/src/core/custom-tools/loader.ts +1 -18
- package/src/core/extensions/loader.ts +5 -21
- package/src/core/hooks/loader.ts +1 -18
- package/src/core/keybindings.ts +3 -1
- package/src/core/logger.ts +1 -2
- package/src/core/model-resolver.ts +1 -0
- package/src/core/prompt-templates.ts +5 -4
- package/src/core/sdk.ts +17 -4
- package/src/core/skills.ts +5 -4
- package/src/core/tools/edit-diff.ts +44 -21
- package/src/core/tools/exa/mcp-client.ts +2 -2
- package/src/core/tools/task/agents.ts +5 -64
- package/src/core/tools/task/commands.ts +7 -33
- package/src/core/tools/task/discovery.ts +4 -66
- package/src/core/tools/task/executor.ts +32 -3
- package/src/core/tools/task/index.ts +11 -2
- package/src/core/tools/task/render.ts +25 -15
- package/src/core/tools/task/types.ts +3 -0
- package/src/core/tools/task/worker-protocol.ts +2 -1
- package/src/core/tools/task/worker.ts +2 -1
- package/src/core/tools/web-scrapers/huggingface.ts +1 -1
- package/src/core/tools/web-scrapers/readthedocs.ts +1 -1
- package/src/core/tools/web-scrapers/types.ts +1 -1
- package/src/core/tools/web-search/auth.ts +5 -3
- package/src/discovery/codex.ts +3 -1
- package/src/discovery/helpers.ts +124 -3
- package/src/migrations.ts +11 -9
- package/src/modes/interactive/components/extensions/state-manager.ts +19 -18
- package/src/prompts/agents/frontmatter.md +1 -0
- package/src/prompts/agents/reviewer.md +32 -4
- package/src/prompts/tools/task.md +3 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,29 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [4.3.1] - 2026-01-11
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- Expanded system prompt with defensive reasoning guidance and assumption checks
|
|
10
|
+
- Allowed agent frontmatter to override subagent thinking level, clamped to model capabilities
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- Ensured reviewer agents use structured output schemas and include reported findings in task outputs
|
|
15
|
+
|
|
16
|
+
## [4.3.0] - 2026-01-11
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- Added Cursor provider support with browser-based OAuth authentication
|
|
21
|
+
- Added default model configuration for Cursor provider (claude-sonnet-4-5)
|
|
22
|
+
- Added execution bridge for Cursor tool calls including read, ls, grep, write, delete, shell, diagnostics, and MCP operations
|
|
23
|
+
|
|
24
|
+
### Fixed
|
|
25
|
+
|
|
26
|
+
- Improved fuzzy matching accuracy for edit operations when file and target have inconsistent indentation patterns
|
|
27
|
+
|
|
5
28
|
## [4.2.3] - 2026-01-11
|
|
6
29
|
|
|
7
30
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.3.1",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -39,10 +39,10 @@
|
|
|
39
39
|
"prepublishOnly": "bun run generate-template && bun run clean && bun run build"
|
|
40
40
|
},
|
|
41
41
|
"dependencies": {
|
|
42
|
-
"@oh-my-pi/pi-ai": "4.
|
|
43
|
-
"@oh-my-pi/pi-agent-core": "4.
|
|
44
|
-
"@oh-my-pi/pi-git-tool": "4.
|
|
45
|
-
"@oh-my-pi/pi-tui": "4.
|
|
42
|
+
"@oh-my-pi/pi-ai": "4.3.1",
|
|
43
|
+
"@oh-my-pi/pi-agent-core": "4.3.1",
|
|
44
|
+
"@oh-my-pi/pi-git-tool": "4.3.1",
|
|
45
|
+
"@oh-my-pi/pi-tui": "4.3.1",
|
|
46
46
|
"@openai/agents": "^0.3.7",
|
|
47
47
|
"@sinclair/typebox": "^0.34.46",
|
|
48
48
|
"ajv": "^8.17.1",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -144,8 +144,8 @@ async function updateViaBun(): Promise<void> {
|
|
|
144
144
|
try {
|
|
145
145
|
execSync(`bun update -g ${PACKAGE}`, { stdio: "inherit" });
|
|
146
146
|
console.log(chalk.green(`\n${theme.status.success} Update complete`));
|
|
147
|
-
} catch {
|
|
148
|
-
throw new Error("bun update failed");
|
|
147
|
+
} catch (error) {
|
|
148
|
+
throw new Error("bun update failed", { cause: error });
|
|
149
149
|
}
|
|
150
150
|
}
|
|
151
151
|
|
package/src/config.ts
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync, statSync } from "node:fs";
|
|
2
2
|
import { homedir } from "node:os";
|
|
3
3
|
import { dirname, join, resolve } from "node:path";
|
|
4
|
-
|
|
5
4
|
// Embed package.json at build time for config
|
|
6
5
|
import packageJson from "../package.json" with { type: "json" };
|
|
6
|
+
import { logger } from "./core/logger";
|
|
7
7
|
|
|
8
8
|
// =============================================================================
|
|
9
9
|
// App Config (from embedded package.json)
|
|
@@ -244,8 +244,8 @@ export function readConfigFile<T = unknown>(
|
|
|
244
244
|
content: JSON.parse(content) as T,
|
|
245
245
|
};
|
|
246
246
|
}
|
|
247
|
-
} catch {
|
|
248
|
-
|
|
247
|
+
} catch (error) {
|
|
248
|
+
logger.warn("Failed to parse config file", { path: filePath, error: String(error) });
|
|
249
249
|
}
|
|
250
250
|
}
|
|
251
251
|
|
|
@@ -275,8 +275,8 @@ export function readAllConfigFiles<T = unknown>(
|
|
|
275
275
|
content: JSON.parse(content) as T,
|
|
276
276
|
});
|
|
277
277
|
}
|
|
278
|
-
} catch {
|
|
279
|
-
|
|
278
|
+
} catch (error) {
|
|
279
|
+
logger.warn("Failed to parse config file", { path: filePath, error: String(error) });
|
|
280
280
|
}
|
|
281
281
|
}
|
|
282
282
|
|
package/src/core/auth-storage.ts
CHANGED
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getOAuthApiKey,
|
|
10
10
|
loginAnthropic,
|
|
11
11
|
loginAntigravity,
|
|
12
|
+
loginCursor,
|
|
12
13
|
loginGeminiCli,
|
|
13
14
|
loginGitHubCopilot,
|
|
14
15
|
loginOpenAICodex,
|
|
@@ -585,6 +586,12 @@ export class AuthStorage {
|
|
|
585
586
|
onManualCodeInput: callbacks.onManualCodeInput,
|
|
586
587
|
});
|
|
587
588
|
break;
|
|
589
|
+
case "cursor":
|
|
590
|
+
credentials = await loginCursor(
|
|
591
|
+
(url) => callbacks.onAuth({ url }),
|
|
592
|
+
callbacks.onProgress ? () => callbacks.onProgress?.("Waiting for browser authentication...") : undefined,
|
|
593
|
+
);
|
|
594
|
+
break;
|
|
588
595
|
default:
|
|
589
596
|
throw new Error(`Unknown OAuth provider: ${provider}`);
|
|
590
597
|
}
|
|
@@ -932,7 +939,12 @@ export class AuthStorage {
|
|
|
932
939
|
|
|
933
940
|
this.recordSessionCredential(provider, sessionId, "oauth", selection.index);
|
|
934
941
|
return result.apiKey;
|
|
935
|
-
} catch {
|
|
942
|
+
} catch (error) {
|
|
943
|
+
logger.warn("OAuth token refresh failed, removing credential", {
|
|
944
|
+
provider,
|
|
945
|
+
index: selection.index,
|
|
946
|
+
error: String(error),
|
|
947
|
+
});
|
|
936
948
|
this.removeCredentialAt(provider, selection.index);
|
|
937
949
|
if (this.getCredentialsForProvider(provider).some((credential) => credential.type === "oauth")) {
|
|
938
950
|
return this.getApiKey(provider, sessionId, options);
|
|
@@ -0,0 +1,234 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { rmSync, statSync } from "node:fs";
|
|
3
|
+
import type {
|
|
4
|
+
AgentEvent,
|
|
5
|
+
AgentTool,
|
|
6
|
+
AgentToolContext,
|
|
7
|
+
AgentToolResult,
|
|
8
|
+
AgentToolUpdateCallback,
|
|
9
|
+
} from "@oh-my-pi/pi-agent-core";
|
|
10
|
+
import type { CursorExecHandlers, CursorMcpCall, ToolResultMessage } from "@oh-my-pi/pi-ai";
|
|
11
|
+
import { resolveToCwd } from "../tools/path-utils";
|
|
12
|
+
|
|
13
|
+
interface CursorExecBridgeOptions {
|
|
14
|
+
cwd: string;
|
|
15
|
+
tools: Map<string, AgentTool>;
|
|
16
|
+
getToolContext?: () => AgentToolContext | undefined;
|
|
17
|
+
emitEvent?: (event: AgentEvent) => void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
function createToolResultMessage(
|
|
21
|
+
toolCallId: string,
|
|
22
|
+
toolName: string,
|
|
23
|
+
result: AgentToolResult<unknown>,
|
|
24
|
+
isError: boolean,
|
|
25
|
+
): ToolResultMessage {
|
|
26
|
+
return {
|
|
27
|
+
role: "toolResult",
|
|
28
|
+
toolCallId,
|
|
29
|
+
toolName,
|
|
30
|
+
content: result.content,
|
|
31
|
+
details: result.details,
|
|
32
|
+
isError,
|
|
33
|
+
timestamp: Date.now(),
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function buildToolErrorResult(message: string): AgentToolResult<unknown> {
|
|
38
|
+
return {
|
|
39
|
+
content: [{ type: "text", text: message }],
|
|
40
|
+
details: {},
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
async function executeTool(
|
|
45
|
+
options: CursorExecBridgeOptions,
|
|
46
|
+
toolName: string,
|
|
47
|
+
toolCallId: string,
|
|
48
|
+
args: Record<string, unknown>,
|
|
49
|
+
): Promise<ToolResultMessage> {
|
|
50
|
+
const tool = options.tools.get(toolName);
|
|
51
|
+
if (!tool) {
|
|
52
|
+
const result = buildToolErrorResult(`Tool "${toolName}" not available`);
|
|
53
|
+
return createToolResultMessage(toolCallId, toolName, result, true);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args });
|
|
57
|
+
|
|
58
|
+
let result: AgentToolResult<unknown>;
|
|
59
|
+
let isError = false;
|
|
60
|
+
|
|
61
|
+
const onUpdate: AgentToolUpdateCallback<unknown> | undefined = options.emitEvent
|
|
62
|
+
? (partialResult) => {
|
|
63
|
+
options.emitEvent?.({
|
|
64
|
+
type: "tool_execution_update",
|
|
65
|
+
toolCallId,
|
|
66
|
+
toolName,
|
|
67
|
+
args,
|
|
68
|
+
partialResult,
|
|
69
|
+
});
|
|
70
|
+
}
|
|
71
|
+
: undefined;
|
|
72
|
+
|
|
73
|
+
try {
|
|
74
|
+
result = await tool.execute(
|
|
75
|
+
toolCallId,
|
|
76
|
+
args as Record<string, unknown>,
|
|
77
|
+
undefined,
|
|
78
|
+
onUpdate,
|
|
79
|
+
options.getToolContext?.(),
|
|
80
|
+
);
|
|
81
|
+
} catch (error) {
|
|
82
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
83
|
+
result = buildToolErrorResult(message);
|
|
84
|
+
isError = true;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
options.emitEvent?.({ type: "tool_execution_end", toolCallId, toolName, result, isError });
|
|
88
|
+
|
|
89
|
+
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
async function executeDelete(options: CursorExecBridgeOptions, pathArg: string, toolCallId: string) {
|
|
93
|
+
const toolName = "delete";
|
|
94
|
+
options.emitEvent?.({ type: "tool_execution_start", toolCallId, toolName, args: { path: pathArg } });
|
|
95
|
+
|
|
96
|
+
const absolutePath = resolveToCwd(pathArg, options.cwd);
|
|
97
|
+
let isError = false;
|
|
98
|
+
let result: AgentToolResult<unknown>;
|
|
99
|
+
|
|
100
|
+
try {
|
|
101
|
+
const stat = statSync(absolutePath, { throwIfNoEntry: false });
|
|
102
|
+
if (!stat) {
|
|
103
|
+
throw new Error(`File not found: ${pathArg}`);
|
|
104
|
+
}
|
|
105
|
+
if (!stat.isFile()) {
|
|
106
|
+
throw new Error(`Path is not a file: ${pathArg}`);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
rmSync(absolutePath);
|
|
110
|
+
|
|
111
|
+
const sizeText = stat.size ? ` (${stat.size} bytes)` : "";
|
|
112
|
+
const message = `Deleted ${pathArg}${sizeText}`;
|
|
113
|
+
result = { content: [{ type: "text", text: message }], details: {} };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
116
|
+
result = buildToolErrorResult(message);
|
|
117
|
+
isError = true;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
options.emitEvent?.({ type: "tool_execution_end", toolCallId, toolName, result, isError });
|
|
121
|
+
return createToolResultMessage(toolCallId, toolName, result, isError);
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function decodeToolCallId(toolCallId?: string): string {
|
|
125
|
+
return toolCallId && toolCallId.length > 0 ? toolCallId : randomUUID();
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function decodeMcpArgs(rawArgs: Record<string, Uint8Array>): Record<string, unknown> {
|
|
129
|
+
const decoded: Record<string, unknown> = {};
|
|
130
|
+
for (const [key, value] of Object.entries(rawArgs)) {
|
|
131
|
+
const text = new TextDecoder().decode(value);
|
|
132
|
+
try {
|
|
133
|
+
decoded[key] = JSON.parse(text);
|
|
134
|
+
} catch {
|
|
135
|
+
decoded[key] = text;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
return decoded;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function formatMcpToolErrorMessage(toolName: string, availableTools: string[]): string {
|
|
142
|
+
const list = availableTools.length > 0 ? availableTools.join(", ") : "none";
|
|
143
|
+
return `MCP tool "${toolName}" not found. Available tools: ${list}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function createCursorExecHandlers(options: CursorExecBridgeOptions): CursorExecHandlers {
|
|
147
|
+
return {
|
|
148
|
+
read: async (args) => {
|
|
149
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
150
|
+
const toolResultMessage = await executeTool(options, "read", toolCallId, { path: args.path });
|
|
151
|
+
return toolResultMessage;
|
|
152
|
+
},
|
|
153
|
+
ls: async (args) => {
|
|
154
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
155
|
+
const toolResultMessage = await executeTool(options, "ls", toolCallId, { path: args.path });
|
|
156
|
+
return toolResultMessage;
|
|
157
|
+
},
|
|
158
|
+
grep: async (args) => {
|
|
159
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
160
|
+
const toolResultMessage = await executeTool(options, "grep", toolCallId, {
|
|
161
|
+
pattern: args.pattern,
|
|
162
|
+
path: args.path || undefined,
|
|
163
|
+
glob: args.glob || undefined,
|
|
164
|
+
outputMode: args.outputMode || undefined,
|
|
165
|
+
context: args.context ?? args.contextBefore ?? args.contextAfter ?? undefined,
|
|
166
|
+
ignoreCase: args.caseInsensitive || undefined,
|
|
167
|
+
type: args.type || undefined,
|
|
168
|
+
headLimit: args.headLimit ?? undefined,
|
|
169
|
+
multiline: args.multiline || undefined,
|
|
170
|
+
});
|
|
171
|
+
return toolResultMessage;
|
|
172
|
+
},
|
|
173
|
+
write: async (args) => {
|
|
174
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
175
|
+
const content = args.fileText ?? new TextDecoder().decode(args.fileBytes ?? new Uint8Array());
|
|
176
|
+
const toolResultMessage = await executeTool(options, "write", toolCallId, {
|
|
177
|
+
path: args.path,
|
|
178
|
+
content,
|
|
179
|
+
});
|
|
180
|
+
return toolResultMessage;
|
|
181
|
+
},
|
|
182
|
+
delete: async (args) => {
|
|
183
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
184
|
+
const toolResultMessage = await executeDelete(options, args.path, toolCallId);
|
|
185
|
+
return toolResultMessage;
|
|
186
|
+
},
|
|
187
|
+
shell: async (args) => {
|
|
188
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
189
|
+
const timeoutSeconds =
|
|
190
|
+
args.timeout && args.timeout > 0
|
|
191
|
+
? args.timeout > 1000
|
|
192
|
+
? Math.ceil(args.timeout / 1000)
|
|
193
|
+
: args.timeout
|
|
194
|
+
: undefined;
|
|
195
|
+
const toolResultMessage = await executeTool(options, "bash", toolCallId, {
|
|
196
|
+
command: args.command,
|
|
197
|
+
workdir: args.workingDirectory || undefined,
|
|
198
|
+
timeout: timeoutSeconds,
|
|
199
|
+
});
|
|
200
|
+
return toolResultMessage;
|
|
201
|
+
},
|
|
202
|
+
diagnostics: async (args) => {
|
|
203
|
+
const toolCallId = decodeToolCallId(args.toolCallId);
|
|
204
|
+
const toolResultMessage = await executeTool(options, "lsp", toolCallId, {
|
|
205
|
+
action: "diagnostics",
|
|
206
|
+
file: args.path,
|
|
207
|
+
});
|
|
208
|
+
return toolResultMessage;
|
|
209
|
+
},
|
|
210
|
+
mcp: async (call: CursorMcpCall) => {
|
|
211
|
+
const toolName = call.toolName || call.name;
|
|
212
|
+
const toolCallId = decodeToolCallId(call.toolCallId);
|
|
213
|
+
const tool = options.tools.get(toolName);
|
|
214
|
+
if (!tool) {
|
|
215
|
+
const availableTools = Array.from(options.tools.keys()).filter((name) => name.startsWith("mcp_"));
|
|
216
|
+
const message = formatMcpToolErrorMessage(toolName, availableTools);
|
|
217
|
+
const toolResult: ToolResultMessage = {
|
|
218
|
+
role: "toolResult",
|
|
219
|
+
toolCallId,
|
|
220
|
+
toolName,
|
|
221
|
+
content: [{ type: "text", text: message }],
|
|
222
|
+
details: {},
|
|
223
|
+
isError: true,
|
|
224
|
+
timestamp: Date.now(),
|
|
225
|
+
};
|
|
226
|
+
return toolResult;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
const args = Object.keys(call.args ?? {}).length > 0 ? call.args : decodeMcpArgs(call.rawArgs ?? {});
|
|
230
|
+
const toolResultMessage = await executeTool(options, toolName, toolCallId, args);
|
|
231
|
+
return toolResultMessage;
|
|
232
|
+
},
|
|
233
|
+
};
|
|
234
|
+
}
|
|
@@ -11,6 +11,7 @@ import * as typebox from "@sinclair/typebox";
|
|
|
11
11
|
import { getAgentDir, getConfigDirs } from "../../config";
|
|
12
12
|
import * as piCodingAgent from "../../index";
|
|
13
13
|
import { execCommand } from "../exec";
|
|
14
|
+
import { logger } from "../logger";
|
|
14
15
|
import { createReviewCommand } from "./bundled/review";
|
|
15
16
|
import { createWorktreeCommand } from "./bundled/wt";
|
|
16
17
|
import type {
|
|
@@ -110,7 +111,8 @@ export function discoverCustomCommands(options: DiscoverCustomCommandsOptions =
|
|
|
110
111
|
let entries: Dirent[];
|
|
111
112
|
try {
|
|
112
113
|
entries = readdirSync(commandsDir, { withFileTypes: true });
|
|
113
|
-
} catch {
|
|
114
|
+
} catch (error) {
|
|
115
|
+
logger.warn("Failed to read custom commands directory", { path: commandsDir, error: String(error) });
|
|
114
116
|
continue;
|
|
115
117
|
}
|
|
116
118
|
for (const entry of entries) {
|
|
@@ -5,11 +5,11 @@
|
|
|
5
5
|
* to avoid import resolution issues with custom tools loaded from user directories.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import * as os from "node:os";
|
|
9
8
|
import * as path from "node:path";
|
|
10
9
|
import * as typebox from "@sinclair/typebox";
|
|
11
10
|
import { toolCapability } from "../../capability/tool";
|
|
12
11
|
import { type CustomTool, loadCapability } from "../../discovery";
|
|
12
|
+
import { expandPath } from "../../discovery/helpers";
|
|
13
13
|
import * as piCodingAgent from "../../index";
|
|
14
14
|
import { theme } from "../../modes/interactive/theme/theme";
|
|
15
15
|
import type { ExecOptions } from "../exec";
|
|
@@ -19,23 +19,6 @@ import { logger } from "../logger";
|
|
|
19
19
|
import { getAllPluginToolPaths } from "../plugins/loader";
|
|
20
20
|
import type { CustomToolAPI, CustomToolFactory, CustomToolsLoadResult, LoadedCustomTool } from "./types";
|
|
21
21
|
|
|
22
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
23
|
-
|
|
24
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
25
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
function expandPath(p: string): string {
|
|
29
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
30
|
-
if (normalized.startsWith("~/")) {
|
|
31
|
-
return path.join(os.homedir(), normalized.slice(2));
|
|
32
|
-
}
|
|
33
|
-
if (normalized.startsWith("~")) {
|
|
34
|
-
return path.join(os.homedir(), normalized.slice(1));
|
|
35
|
-
}
|
|
36
|
-
return normalized;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
22
|
/**
|
|
40
23
|
* Resolve tool path.
|
|
41
24
|
* - Absolute paths used as-is
|
|
@@ -3,13 +3,12 @@
|
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
5
|
import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
|
|
6
|
-
import { homedir } from "node:os";
|
|
7
6
|
import * as path from "node:path";
|
|
8
7
|
import type { KeyId } from "@oh-my-pi/pi-tui";
|
|
9
8
|
import * as TypeBox from "@sinclair/typebox";
|
|
10
9
|
import { type ExtensionModule, extensionModuleCapability } from "../../capability/extension-module";
|
|
11
10
|
import { loadCapability } from "../../discovery";
|
|
12
|
-
import { getExtensionNameFromPath } from "../../discovery/helpers";
|
|
11
|
+
import { expandPath, getExtensionNameFromPath } from "../../discovery/helpers";
|
|
13
12
|
import * as piCodingAgent from "../../index";
|
|
14
13
|
import { createEventBus, type EventBus } from "../event-bus";
|
|
15
14
|
import type { ExecOptions } from "../exec";
|
|
@@ -27,23 +26,6 @@ import type {
|
|
|
27
26
|
ToolDefinition,
|
|
28
27
|
} from "./types";
|
|
29
28
|
|
|
30
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
31
|
-
|
|
32
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
33
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
function expandPath(p: string): string {
|
|
37
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
38
|
-
if (normalized.startsWith("~/")) {
|
|
39
|
-
return path.join(homedir(), normalized.slice(2));
|
|
40
|
-
}
|
|
41
|
-
if (normalized.startsWith("~")) {
|
|
42
|
-
return path.join(homedir(), normalized.slice(1));
|
|
43
|
-
}
|
|
44
|
-
return normalized;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
29
|
function resolvePath(extPath: string, cwd: string): string {
|
|
48
30
|
const expanded = expandPath(extPath);
|
|
49
31
|
if (path.isAbsolute(expanded)) {
|
|
@@ -291,7 +273,8 @@ function readExtensionManifest(packageJsonPath: string): ExtensionManifest | nul
|
|
|
291
273
|
return manifest;
|
|
292
274
|
}
|
|
293
275
|
return null;
|
|
294
|
-
} catch {
|
|
276
|
+
} catch (error) {
|
|
277
|
+
logger.warn("Failed to read extension manifest", { path: packageJsonPath, error: String(error) });
|
|
295
278
|
return null;
|
|
296
279
|
}
|
|
297
280
|
}
|
|
@@ -370,7 +353,8 @@ function discoverExtensionsInDir(dir: string): string[] {
|
|
|
370
353
|
}
|
|
371
354
|
}
|
|
372
355
|
}
|
|
373
|
-
} catch {
|
|
356
|
+
} catch (error) {
|
|
357
|
+
logger.warn("Failed to discover extensions in directory", { path: dir, error: String(error) });
|
|
374
358
|
return [];
|
|
375
359
|
}
|
|
376
360
|
|
package/src/core/hooks/loader.ts
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
* Hook loader - loads TypeScript hook modules using native Bun import.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import * as os from "node:os";
|
|
6
5
|
import * as path from "node:path";
|
|
7
6
|
import * as typebox from "@sinclair/typebox";
|
|
8
7
|
import { hookCapability } from "../../capability/hook";
|
|
9
8
|
import type { Hook } from "../../discovery";
|
|
10
9
|
import { loadCapability } from "../../discovery";
|
|
10
|
+
import { expandPath } from "../../discovery/helpers";
|
|
11
11
|
import * as piCodingAgent from "../../index";
|
|
12
12
|
import { logger } from "../logger";
|
|
13
13
|
import type { HookMessage } from "../messages";
|
|
@@ -84,23 +84,6 @@ export interface LoadHooksResult {
|
|
|
84
84
|
errors: Array<{ path: string; error: string }>;
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
const UNICODE_SPACES = /[\u00A0\u2000-\u200A\u202F\u205F\u3000]/g;
|
|
88
|
-
|
|
89
|
-
function normalizeUnicodeSpaces(str: string): string {
|
|
90
|
-
return str.replace(UNICODE_SPACES, " ");
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
function expandPath(p: string): string {
|
|
94
|
-
const normalized = normalizeUnicodeSpaces(p);
|
|
95
|
-
if (normalized.startsWith("~/")) {
|
|
96
|
-
return path.join(os.homedir(), normalized.slice(2));
|
|
97
|
-
}
|
|
98
|
-
if (normalized.startsWith("~")) {
|
|
99
|
-
return path.join(os.homedir(), normalized.slice(1));
|
|
100
|
-
}
|
|
101
|
-
return normalized;
|
|
102
|
-
}
|
|
103
|
-
|
|
104
87
|
/**
|
|
105
88
|
* Resolve hook path.
|
|
106
89
|
* - Absolute paths used as-is
|
package/src/core/keybindings.ts
CHANGED
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
setEditorKeybindings,
|
|
11
11
|
} from "@oh-my-pi/pi-tui";
|
|
12
12
|
import { getAgentDir } from "../config";
|
|
13
|
+
import { logger } from "./logger";
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* Application-level actions (coding agent specific).
|
|
@@ -136,7 +137,8 @@ export class KeybindingsManager {
|
|
|
136
137
|
if (!existsSync(path)) return {};
|
|
137
138
|
try {
|
|
138
139
|
return JSON.parse(readFileSync(path, "utf-8"));
|
|
139
|
-
} catch {
|
|
140
|
+
} catch (error) {
|
|
141
|
+
logger.warn("Failed to parse keybindings config", { path, error: String(error) });
|
|
140
142
|
return {};
|
|
141
143
|
}
|
|
142
144
|
}
|
package/src/core/logger.ts
CHANGED
|
@@ -10,11 +10,10 @@ import { homedir } from "node:os";
|
|
|
10
10
|
import { join } from "node:path";
|
|
11
11
|
import winston from "winston";
|
|
12
12
|
import DailyRotateFile from "winston-daily-rotate-file";
|
|
13
|
-
import { CONFIG_DIR_NAME } from "../config";
|
|
14
13
|
|
|
15
14
|
/** Get the logs directory (~/.omp/logs/) */
|
|
16
15
|
function getLogsDir(): string {
|
|
17
|
-
return join(homedir(),
|
|
16
|
+
return join(homedir(), ".omp", "logs");
|
|
18
17
|
}
|
|
19
18
|
|
|
20
19
|
/** Ensure logs directory exists */
|
|
@@ -19,6 +19,7 @@ export const defaultModelPerProvider: Record<KnownProvider, string> = {
|
|
|
19
19
|
"google-antigravity": "gemini-3-pro-high",
|
|
20
20
|
"google-vertex": "gemini-2.5-pro",
|
|
21
21
|
"github-copilot": "gpt-4o",
|
|
22
|
+
cursor: "claude-sonnet-4-5",
|
|
22
23
|
openrouter: "openai/gpt-5.1-codex",
|
|
23
24
|
xai: "grok-4-fast-non-reasoning",
|
|
24
25
|
groq: "openai/gpt-oss-120b",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { join, resolve } from "node:path";
|
|
2
2
|
import Handlebars from "handlebars";
|
|
3
3
|
import { CONFIG_DIR_NAME, getPromptsDir } from "../config";
|
|
4
|
+
import { logger } from "./logger";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Represents a prompt template loaded from a markdown file
|
|
@@ -448,12 +449,12 @@ async function loadTemplatesFromDir(
|
|
|
448
449
|
source: sourceStr,
|
|
449
450
|
});
|
|
450
451
|
}
|
|
451
|
-
} catch (
|
|
452
|
-
|
|
452
|
+
} catch (error) {
|
|
453
|
+
logger.warn("Failed to load prompt template", { path: fullPath, error: String(error) });
|
|
453
454
|
}
|
|
454
455
|
}
|
|
455
|
-
} catch (
|
|
456
|
-
|
|
456
|
+
} catch (error) {
|
|
457
|
+
logger.warn("Failed to scan prompt templates directory", { dir, error: String(error) });
|
|
457
458
|
}
|
|
458
459
|
|
|
459
460
|
return templates;
|
package/src/core/sdk.ts
CHANGED
|
@@ -27,8 +27,8 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { join } from "node:path";
|
|
30
|
-
import { Agent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
31
|
-
import type
|
|
30
|
+
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
31
|
+
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
32
32
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
33
33
|
import chalk from "chalk";
|
|
34
34
|
// Import discovery to register all providers on startup
|
|
@@ -40,6 +40,7 @@ import { initializeWithSettings } from "../discovery";
|
|
|
40
40
|
import { registerAsyncCleanup } from "../modes/cleanup";
|
|
41
41
|
import { AgentSession } from "./agent-session";
|
|
42
42
|
import { AuthStorage } from "./auth-storage";
|
|
43
|
+
import { createCursorExecHandlers } from "./cursor/exec-bridge";
|
|
43
44
|
import {
|
|
44
45
|
type CustomCommandsLoadResult,
|
|
45
46
|
loadCustomCommands as loadCustomCommandsInternal,
|
|
@@ -630,6 +631,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
630
631
|
// Clamp to model capabilities
|
|
631
632
|
if (!model || !model.reasoning) {
|
|
632
633
|
thinkingLevel = "off";
|
|
634
|
+
} else if (thinkingLevel === "xhigh" && !supportsXhigh(model)) {
|
|
635
|
+
thinkingLevel = "high";
|
|
633
636
|
}
|
|
634
637
|
|
|
635
638
|
let skills: Skill[];
|
|
@@ -854,6 +857,14 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
854
857
|
}
|
|
855
858
|
time("combineTools");
|
|
856
859
|
|
|
860
|
+
let cursorEventEmitter: ((event: AgentEvent) => void) | undefined;
|
|
861
|
+
const cursorExecHandlers = createCursorExecHandlers({
|
|
862
|
+
cwd,
|
|
863
|
+
tools: toolRegistry,
|
|
864
|
+
getToolContext: toolContextStore.getContext,
|
|
865
|
+
emitEvent: (event) => cursorEventEmitter?.(event),
|
|
866
|
+
});
|
|
867
|
+
|
|
857
868
|
const rebuildSystemPrompt = async (toolNames: string[], tools: Map<string, AgentTool>): Promise<string> => {
|
|
858
869
|
toolContextStore.setToolNames(toolNames);
|
|
859
870
|
const defaultPrompt = await buildSystemPromptInternal({
|
|
@@ -964,7 +975,9 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
964
975
|
}
|
|
965
976
|
return key;
|
|
966
977
|
},
|
|
978
|
+
cursorExecHandlers,
|
|
967
979
|
});
|
|
980
|
+
cursorEventEmitter = (event) => agent.emitExternalEvent(event);
|
|
968
981
|
time("createAgent");
|
|
969
982
|
|
|
970
983
|
// Restore messages if session has existing data
|
|
@@ -1010,8 +1023,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1010
1023
|
});
|
|
1011
1024
|
lspServers = result.servers;
|
|
1012
1025
|
time("warmupLspServers");
|
|
1013
|
-
} catch {
|
|
1014
|
-
|
|
1026
|
+
} catch (error) {
|
|
1027
|
+
logger.warn("LSP server warmup failed", { cwd, error: String(error) });
|
|
1015
1028
|
}
|
|
1016
1029
|
}
|
|
1017
1030
|
|
package/src/core/skills.ts
CHANGED
|
@@ -7,6 +7,7 @@ import type { SourceMeta } from "../capability/types";
|
|
|
7
7
|
import type { Skill as CapabilitySkill, SkillFrontmatter as ImportedSkillFrontmatter } from "../discovery";
|
|
8
8
|
import { loadCapability } from "../discovery";
|
|
9
9
|
import { parseFrontmatter } from "../discovery/helpers";
|
|
10
|
+
import { logger } from "./logger";
|
|
10
11
|
import type { SkillsSettings } from "./settings-manager";
|
|
11
12
|
|
|
12
13
|
// Re-export SkillFrontmatter for backward compatibility
|
|
@@ -67,8 +68,8 @@ export function loadSkillsFromDir(options: LoadSkillsFromDirOptions): LoadSkills
|
|
|
67
68
|
source: options.source,
|
|
68
69
|
});
|
|
69
70
|
}
|
|
70
|
-
} catch {
|
|
71
|
-
|
|
71
|
+
} catch (error) {
|
|
72
|
+
logger.warn("Failed to load skill", { path: skillFile, error: String(error) });
|
|
72
73
|
}
|
|
73
74
|
}
|
|
74
75
|
|
|
@@ -131,8 +132,8 @@ function scanDirectoryForSkills(dir: string): LoadSkillsResult {
|
|
|
131
132
|
source: "custom",
|
|
132
133
|
});
|
|
133
134
|
}
|
|
134
|
-
} catch {
|
|
135
|
-
|
|
135
|
+
} catch (error) {
|
|
136
|
+
logger.warn("Failed to load skill", { path: skillFile, error: String(error) });
|
|
136
137
|
}
|
|
137
138
|
}
|
|
138
139
|
|