@oh-my-pi/pi-coding-agent 12.1.1 → 12.2.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 +30 -0
- package/examples/sdk/11-sessions.ts +1 -1
- package/package.json +7 -7
- package/src/capability/index.ts +2 -1
- package/src/capability/types.ts +1 -1
- package/src/cli/file-processor.ts +2 -1
- package/src/cli/shell-cli.ts +2 -2
- package/src/commit/agentic/index.ts +2 -1
- package/src/commit/pipeline.ts +2 -1
- package/src/config/prompt-templates.ts +3 -3
- package/src/config/settings-schema.ts +11 -0
- package/src/config/settings.ts +2 -2
- package/src/config.ts +6 -6
- package/src/debug/system-info.ts +2 -2
- package/src/extensibility/custom-commands/loader.ts +5 -5
- package/src/extensibility/plugins/installer.ts +2 -2
- package/src/extensibility/plugins/manager.ts +2 -1
- package/src/extensibility/skills.ts +3 -2
- package/src/extensibility/slash-commands.ts +1 -1
- package/src/ipy/executor.ts +2 -2
- package/src/ipy/modules.ts +3 -3
- package/src/main.ts +9 -7
- package/src/mcp/transports/stdio.ts +2 -1
- package/src/modes/components/footer.ts +3 -2
- package/src/modes/components/oauth-selector.ts +96 -21
- package/src/modes/components/status-line/segments.ts +2 -1
- package/src/modes/components/status-line.ts +2 -1
- package/src/modes/components/tool-execution.ts +2 -1
- package/src/modes/controllers/command-controller.ts +60 -2
- package/src/modes/controllers/mcp-command-controller.ts +8 -8
- package/src/modes/controllers/selector-controller.ts +35 -11
- package/src/modes/interactive-mode.ts +2 -2
- package/src/sdk.ts +37 -11
- package/src/session/agent-session.ts +12 -0
- package/src/session/session-manager.ts +7 -4
- package/src/system-prompt.ts +41 -28
- package/src/tools/bash-normalize.ts +2 -2
- package/src/tools/bash.ts +2 -1
- package/src/tools/fetch.ts +3 -3
- package/src/tools/python.ts +2 -1
package/src/sdk.ts
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import { Agent, type AgentEvent, type AgentMessage, type AgentTool, type ThinkingLevel } from "@oh-my-pi/pi-agent-core";
|
|
2
2
|
import { type Message, type Model, supportsXhigh } from "@oh-my-pi/pi-ai";
|
|
3
|
+
import { prewarmOpenAICodexResponses } from "@oh-my-pi/pi-ai/providers/openai-codex-responses";
|
|
3
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
4
5
|
import { $env, logger, postmortem } from "@oh-my-pi/pi-utils";
|
|
5
|
-
import { getAgentDbPath, getAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
6
|
+
import { getAgentDbPath, getAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
6
7
|
import chalk from "chalk";
|
|
7
8
|
import { loadCapability } from "./capability";
|
|
8
9
|
import { type Rule, ruleCapability } from "./capability/rule";
|
|
@@ -85,7 +86,7 @@ const debugStartup = $env.PI_DEBUG_STARTUP ? (stage: string) => process.stderr.w
|
|
|
85
86
|
|
|
86
87
|
// Types
|
|
87
88
|
export interface CreateAgentSessionOptions {
|
|
88
|
-
/** Working directory for project-local discovery. Default:
|
|
89
|
+
/** Working directory for project-local discovery. Default: getProjectDir() */
|
|
89
90
|
cwd?: string;
|
|
90
91
|
/** Global config directory. Default: ~/.omp/agent */
|
|
91
92
|
agentDir?: string;
|
|
@@ -240,7 +241,7 @@ export async function discoverAuthStorage(agentDir: string = getDefaultAgentDir(
|
|
|
240
241
|
* Discover extensions from cwd.
|
|
241
242
|
*/
|
|
242
243
|
export async function discoverExtensions(cwd?: string): Promise<LoadExtensionsResult> {
|
|
243
|
-
const resolvedCwd = cwd ??
|
|
244
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
244
245
|
|
|
245
246
|
return discoverAndLoadExtensions([], resolvedCwd);
|
|
246
247
|
}
|
|
@@ -255,7 +256,7 @@ export async function discoverSkills(
|
|
|
255
256
|
): Promise<{ skills: Skill[]; warnings: SkillWarning[] }> {
|
|
256
257
|
return await loadSkillsInternal({
|
|
257
258
|
...settings,
|
|
258
|
-
cwd: cwd ??
|
|
259
|
+
cwd: cwd ?? getProjectDir(),
|
|
259
260
|
});
|
|
260
261
|
}
|
|
261
262
|
|
|
@@ -268,7 +269,7 @@ export async function discoverContextFiles(
|
|
|
268
269
|
_agentDir?: string,
|
|
269
270
|
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
|
270
271
|
return await loadContextFilesInternal({
|
|
271
|
-
cwd: cwd ??
|
|
272
|
+
cwd: cwd ?? getProjectDir(),
|
|
272
273
|
});
|
|
273
274
|
}
|
|
274
275
|
|
|
@@ -277,7 +278,7 @@ export async function discoverContextFiles(
|
|
|
277
278
|
*/
|
|
278
279
|
export async function discoverPromptTemplates(cwd?: string, agentDir?: string): Promise<PromptTemplate[]> {
|
|
279
280
|
return await loadPromptTemplatesInternal({
|
|
280
|
-
cwd: cwd ??
|
|
281
|
+
cwd: cwd ?? getProjectDir(),
|
|
281
282
|
agentDir: agentDir ?? getDefaultAgentDir(),
|
|
282
283
|
});
|
|
283
284
|
}
|
|
@@ -286,14 +287,14 @@ export async function discoverPromptTemplates(cwd?: string, agentDir?: string):
|
|
|
286
287
|
* Discover file-based slash commands from commands/ directories.
|
|
287
288
|
*/
|
|
288
289
|
export async function discoverSlashCommands(cwd?: string): Promise<FileSlashCommand[]> {
|
|
289
|
-
return loadSlashCommandsInternal({ cwd: cwd ??
|
|
290
|
+
return loadSlashCommandsInternal({ cwd: cwd ?? getProjectDir() });
|
|
290
291
|
}
|
|
291
292
|
|
|
292
293
|
/**
|
|
293
294
|
* Discover custom commands (TypeScript slash commands) from cwd and agentDir.
|
|
294
295
|
*/
|
|
295
296
|
export async function discoverCustomTSCommands(cwd?: string, agentDir?: string): Promise<CustomCommandsLoadResult> {
|
|
296
|
-
const resolvedCwd = cwd ??
|
|
297
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
297
298
|
const resolvedAgentDir = agentDir ?? getDefaultAgentDir();
|
|
298
299
|
|
|
299
300
|
return loadCustomCommandsInternal({
|
|
@@ -307,7 +308,7 @@ export async function discoverCustomTSCommands(cwd?: string, agentDir?: string):
|
|
|
307
308
|
* Returns the manager and loaded tools.
|
|
308
309
|
*/
|
|
309
310
|
export async function discoverMCPServers(cwd?: string): Promise<MCPToolsLoadResult> {
|
|
310
|
-
const resolvedCwd = cwd ??
|
|
311
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
311
312
|
return discoverAndLoadMCPTools(resolvedCwd);
|
|
312
313
|
}
|
|
313
314
|
|
|
@@ -469,7 +470,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
469
470
|
* model: myModel,
|
|
470
471
|
* getApiKey: async () => Bun.env.MY_KEY,
|
|
471
472
|
* systemPrompt: 'You are helpful.',
|
|
472
|
-
* tools: codingTools({ cwd:
|
|
473
|
+
* tools: codingTools({ cwd: getProjectDir() }),
|
|
473
474
|
* skills: [],
|
|
474
475
|
* sessionManager: SessionManager.inMemory(),
|
|
475
476
|
* });
|
|
@@ -477,7 +478,7 @@ function createCustomToolsExtension(tools: CustomTool[]): ExtensionFactory {
|
|
|
477
478
|
*/
|
|
478
479
|
export async function createAgentSession(options: CreateAgentSessionOptions = {}): Promise<CreateAgentSessionResult> {
|
|
479
480
|
debugStartup("sdk:createAgentSession:entry");
|
|
480
|
-
const cwd = options.cwd ??
|
|
481
|
+
const cwd = options.cwd ?? getProjectDir();
|
|
481
482
|
const agentDir = options.agentDir ?? getDefaultAgentDir();
|
|
482
483
|
const eventBus = options.eventBus ?? new EventBus();
|
|
483
484
|
|
|
@@ -1016,6 +1017,10 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1016
1017
|
.map(name => toolRegistry.get(name))
|
|
1017
1018
|
.filter((tool): tool is AgentTool => tool !== undefined);
|
|
1018
1019
|
|
|
1020
|
+
const openaiWebsocketSetting = settings.get("providers.openaiWebsockets") ?? "auto";
|
|
1021
|
+
const preferOpenAICodexWebsockets =
|
|
1022
|
+
openaiWebsocketSetting === "on" ? true : openaiWebsocketSetting === "off" ? false : undefined;
|
|
1023
|
+
|
|
1019
1024
|
agent = new Agent({
|
|
1020
1025
|
initialState: {
|
|
1021
1026
|
systemPrompt,
|
|
@@ -1036,6 +1041,7 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1036
1041
|
thinkingBudgets: settings.getGroup("thinkingBudgets"),
|
|
1037
1042
|
temperature: settings.get("temperature") >= 0 ? settings.get("temperature") : undefined,
|
|
1038
1043
|
kimiApiFormat: settings.get("providers.kimiApiFormat") ?? "anthropic",
|
|
1044
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1039
1045
|
getToolContext: tc => toolContextStore.getContext(tc),
|
|
1040
1046
|
getApiKey: async provider => {
|
|
1041
1047
|
// Use the provider argument from the in-flight request;
|
|
@@ -1087,6 +1093,26 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
|
|
|
1087
1093
|
debugStartup("sdk:createAgentSession");
|
|
1088
1094
|
time("createAgentSession");
|
|
1089
1095
|
|
|
1096
|
+
if (model?.api === "openai-codex-responses") {
|
|
1097
|
+
try {
|
|
1098
|
+
debugStartup("sdk:prewarmCodexWebsocket:start");
|
|
1099
|
+
await prewarmOpenAICodexResponses(model, {
|
|
1100
|
+
apiKey: await modelRegistry.getApiKey(model, sessionId),
|
|
1101
|
+
sessionId,
|
|
1102
|
+
preferWebsockets: preferOpenAICodexWebsockets,
|
|
1103
|
+
providerSessionState: session.providerSessionState,
|
|
1104
|
+
});
|
|
1105
|
+
debugStartup("sdk:prewarmCodexWebsocket:done");
|
|
1106
|
+
time("prewarmCodexWebsocket");
|
|
1107
|
+
} catch (error) {
|
|
1108
|
+
logger.debug("Codex websocket prewarm failed", {
|
|
1109
|
+
error: error instanceof Error ? error.message : String(error),
|
|
1110
|
+
provider: model.provider,
|
|
1111
|
+
model: model.id,
|
|
1112
|
+
});
|
|
1113
|
+
}
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1090
1116
|
// Warm up LSP servers (connects to detected servers)
|
|
1091
1117
|
let lspServers: CreateAgentSessionResult["lspServers"];
|
|
1092
1118
|
if (enableLsp && settings.get("lsp.diagnosticsOnWrite")) {
|
|
@@ -21,6 +21,7 @@ import type {
|
|
|
21
21
|
ImageContent,
|
|
22
22
|
Message,
|
|
23
23
|
Model,
|
|
24
|
+
ProviderSessionState,
|
|
24
25
|
TextContent,
|
|
25
26
|
ToolCall,
|
|
26
27
|
ToolChoice,
|
|
@@ -339,6 +340,7 @@ export class AgentSession {
|
|
|
339
340
|
#streamingEditCheckedLineCounts = new Map<string, number>();
|
|
340
341
|
#streamingEditFileCache = new Map<string, string>();
|
|
341
342
|
#promptInFlight = false;
|
|
343
|
+
#providerSessionState = new Map<string, ProviderSessionState>();
|
|
342
344
|
|
|
343
345
|
constructor(config: AgentSessionConfig) {
|
|
344
346
|
this.agent = config.agent;
|
|
@@ -358,6 +360,7 @@ export class AgentSession {
|
|
|
358
360
|
this.#baseSystemPrompt = this.agent.state.systemPrompt;
|
|
359
361
|
this.#ttsrManager = config.ttsrManager;
|
|
360
362
|
this.#forceCopilotAgentInitiator = config.forceCopilotAgentInitiator ?? false;
|
|
363
|
+
this.agent.providerSessionState = this.#providerSessionState;
|
|
361
364
|
|
|
362
365
|
// Always subscribe to agent events for internal handling
|
|
363
366
|
// (session persistence, hooks, auto-compaction, retry logic)
|
|
@@ -369,6 +372,11 @@ export class AgentSession {
|
|
|
369
372
|
return this.#modelRegistry;
|
|
370
373
|
}
|
|
371
374
|
|
|
375
|
+
/** Provider-scoped mutable state store for transport/session caches. */
|
|
376
|
+
get providerSessionState(): Map<string, ProviderSessionState> {
|
|
377
|
+
return this.#providerSessionState;
|
|
378
|
+
}
|
|
379
|
+
|
|
372
380
|
/** TTSR manager for time-traveling stream rules */
|
|
373
381
|
get ttsrManager(): TtsrManager | undefined {
|
|
374
382
|
return this.#ttsrManager;
|
|
@@ -888,6 +896,10 @@ export class AgentSession {
|
|
|
888
896
|
async dispose(): Promise<void> {
|
|
889
897
|
await this.sessionManager.flush();
|
|
890
898
|
await cleanupSshResources();
|
|
899
|
+
for (const state of this.#providerSessionState.values()) {
|
|
900
|
+
state.close();
|
|
901
|
+
}
|
|
902
|
+
this.#providerSessionState.clear();
|
|
891
903
|
this.#disconnectFromAgent();
|
|
892
904
|
this.#eventListeners = [];
|
|
893
905
|
}
|
|
@@ -3,7 +3,7 @@ import * as path from "node:path";
|
|
|
3
3
|
import type { AgentMessage } from "@oh-my-pi/pi-agent-core";
|
|
4
4
|
import type { ImageContent, Message, TextContent, Usage } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import { isEnoent, logger, parseJsonlLenient, Snowflake } from "@oh-my-pi/pi-utils";
|
|
6
|
-
import { getBlobsDir, getAgentDir as getDefaultAgentDir } from "@oh-my-pi/pi-utils/dirs";
|
|
6
|
+
import { getBlobsDir, getAgentDir as getDefaultAgentDir, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
7
7
|
import { type BlobPutResult, BlobStore, externalizeImageData, isBlobRef, resolveImageData } from "./blob-store";
|
|
8
8
|
import {
|
|
9
9
|
type BashExecutionMessage,
|
|
@@ -2153,10 +2153,10 @@ export class SessionManager {
|
|
|
2153
2153
|
sessionDir?: string,
|
|
2154
2154
|
storage: SessionStorage = new FileSessionStorage(),
|
|
2155
2155
|
): Promise<SessionManager> {
|
|
2156
|
-
// Extract cwd from session header if possible, otherwise use
|
|
2156
|
+
// Extract cwd from session header if possible, otherwise use getProjectDir()
|
|
2157
2157
|
const entries = await loadEntriesFromFile(filePath, storage);
|
|
2158
2158
|
const header = entries.find(e => e.type === "session") as SessionHeader | undefined;
|
|
2159
|
-
const cwd = header?.cwd ??
|
|
2159
|
+
const cwd = header?.cwd ?? getProjectDir();
|
|
2160
2160
|
// If no sessionDir provided, derive from file's parent directory
|
|
2161
2161
|
const dir = sessionDir ?? path.resolve(filePath, "..");
|
|
2162
2162
|
const manager = new SessionManager(cwd, dir, true, storage);
|
|
@@ -2188,7 +2188,10 @@ export class SessionManager {
|
|
|
2188
2188
|
}
|
|
2189
2189
|
|
|
2190
2190
|
/** Create an in-memory session (no file persistence) */
|
|
2191
|
-
static inMemory(
|
|
2191
|
+
static inMemory(
|
|
2192
|
+
cwd: string = getProjectDir(),
|
|
2193
|
+
storage: SessionStorage = new MemorySessionStorage(),
|
|
2194
|
+
): SessionManager {
|
|
2192
2195
|
const manager = new SessionManager(cwd, "", false, storage);
|
|
2193
2196
|
manager.#initNewSession();
|
|
2194
2197
|
return manager;
|
package/src/system-prompt.ts
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import * as os from "node:os";
|
|
5
5
|
import { getSystemInfo as getNativeSystemInfo, type SystemInfo } from "@oh-my-pi/pi-natives";
|
|
6
6
|
import { $env, logger } from "@oh-my-pi/pi-utils";
|
|
7
|
-
import { getGpuCachePath } from "@oh-my-pi/pi-utils/dirs";
|
|
7
|
+
import { getGpuCachePath, getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
8
8
|
import { $ } from "bun";
|
|
9
9
|
import { contextFileCapability } from "./capability/context-file";
|
|
10
10
|
import { systemPromptCapability } from "./capability/system-prompt";
|
|
@@ -50,41 +50,54 @@ async function loadPreloadedSkillContents(preloadedSkills: Skill[]): Promise<Pre
|
|
|
50
50
|
* Returns structured git data or null if not in a git repo.
|
|
51
51
|
*/
|
|
52
52
|
export async function loadGitContext(cwd: string): Promise<GitContext | null> {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
53
|
+
const runGit = async (args: string[], timeoutMs = 1500): Promise<string | null> => {
|
|
54
|
+
const proc = Bun.spawn(["git", ...args], {
|
|
55
|
+
cwd,
|
|
56
|
+
stdout: "pipe",
|
|
57
|
+
stderr: "ignore",
|
|
58
|
+
});
|
|
59
|
+
const stdoutPromise = proc.stdout ? new Response(proc.stdout).text() : Promise.resolve("");
|
|
60
|
+
const race = await Promise.race([
|
|
61
|
+
proc.exited.then(() => "exited" as const),
|
|
62
|
+
Bun.sleep(timeoutMs).then(() => "timeout" as const),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
if (race === "timeout") {
|
|
66
|
+
proc.kill();
|
|
67
|
+
await stdoutPromise.catch(() => null);
|
|
68
|
+
logger.debug("Git context command timed out", { cwd, args, timeoutMs });
|
|
69
|
+
return null;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const exitCode = await proc.exited;
|
|
73
|
+
const stdout = await stdoutPromise.catch(() => "");
|
|
74
|
+
if (exitCode !== 0) return null;
|
|
60
75
|
|
|
76
|
+
const trimmed = stdout.trim();
|
|
77
|
+
return trimmed.length > 0 ? trimmed : "";
|
|
78
|
+
};
|
|
61
79
|
// Check if inside a git repo
|
|
62
|
-
const isGitRepo = await
|
|
80
|
+
const isGitRepo = await runGit(["rev-parse", "--is-inside-work-tree"]);
|
|
63
81
|
if (isGitRepo !== "true") return null;
|
|
64
|
-
|
|
65
|
-
// Get current branch
|
|
66
|
-
const currentBranch = await git("rev-parse", "--abbrev-ref", "HEAD");
|
|
82
|
+
const currentBranch = await runGit(["rev-parse", "--abbrev-ref", "HEAD"]);
|
|
67
83
|
if (!currentBranch) return null;
|
|
68
|
-
|
|
69
|
-
// Detect main branch (check for 'main' first, then 'master')
|
|
70
84
|
let mainBranch = "main";
|
|
71
|
-
const mainExists = await
|
|
85
|
+
const mainExists = await runGit(["rev-parse", "--verify", "main"]);
|
|
72
86
|
if (mainExists === null) {
|
|
73
|
-
const masterExists = await
|
|
87
|
+
const masterExists = await runGit(["rev-parse", "--verify", "master"]);
|
|
74
88
|
if (masterExists !== null) mainBranch = "master";
|
|
75
89
|
}
|
|
76
90
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
const commits = (await git("log", "--oneline", "-5")) || "(no commits)";
|
|
91
|
+
const [status, commits] = await Promise.all([
|
|
92
|
+
runGit(["status", "--porcelain", "--untracked-files=no"], 2000),
|
|
93
|
+
runGit(["log", "--oneline", "-5"]),
|
|
94
|
+
]);
|
|
82
95
|
return {
|
|
83
96
|
isRepo: true,
|
|
84
97
|
currentBranch,
|
|
85
98
|
mainBranch,
|
|
86
|
-
status,
|
|
87
|
-
commits,
|
|
99
|
+
status: status === "" ? "(clean)" : (status ?? "(status unavailable)"),
|
|
100
|
+
commits: commits && commits.length > 0 ? commits : "(no commits)",
|
|
88
101
|
};
|
|
89
102
|
}
|
|
90
103
|
|
|
@@ -349,7 +362,7 @@ export async function resolvePromptInput(input: string | undefined, description:
|
|
|
349
362
|
}
|
|
350
363
|
|
|
351
364
|
export interface LoadContextFilesOptions {
|
|
352
|
-
/** Working directory to start walking up from. Default:
|
|
365
|
+
/** Working directory to start walking up from. Default: getProjectDir() */
|
|
353
366
|
cwd?: string;
|
|
354
367
|
}
|
|
355
368
|
|
|
@@ -361,7 +374,7 @@ export interface LoadContextFilesOptions {
|
|
|
361
374
|
export async function loadProjectContextFiles(
|
|
362
375
|
options: LoadContextFilesOptions = {},
|
|
363
376
|
): Promise<Array<{ path: string; content: string; depth?: number }>> {
|
|
364
|
-
const resolvedCwd = options.cwd ??
|
|
377
|
+
const resolvedCwd = options.cwd ?? getProjectDir();
|
|
365
378
|
|
|
366
379
|
const result = await loadCapability(contextFileCapability.id, { cwd: resolvedCwd });
|
|
367
380
|
|
|
@@ -391,7 +404,7 @@ export async function loadProjectContextFiles(
|
|
|
391
404
|
* Returns combined content from all discovered SYSTEM.md files.
|
|
392
405
|
*/
|
|
393
406
|
export async function loadSystemPromptFiles(options: LoadContextFilesOptions = {}): Promise<string | null> {
|
|
394
|
-
const resolvedCwd = options.cwd ??
|
|
407
|
+
const resolvedCwd = options.cwd ?? getProjectDir();
|
|
395
408
|
|
|
396
409
|
const result = await loadCapability<SystemPromptFile>(systemPromptCapability.id, { cwd: resolvedCwd });
|
|
397
410
|
|
|
@@ -420,7 +433,7 @@ export interface BuildSystemPromptOptions {
|
|
|
420
433
|
appendSystemPrompt?: string;
|
|
421
434
|
/** Skills settings for discovery. */
|
|
422
435
|
skillsSettings?: SkillsSettings;
|
|
423
|
-
/** Working directory. Default:
|
|
436
|
+
/** Working directory. Default: getProjectDir() */
|
|
424
437
|
cwd?: string;
|
|
425
438
|
/** Pre-loaded context files (skips discovery if provided). */
|
|
426
439
|
contextFiles?: Array<{ path: string; content: string; depth?: number }>;
|
|
@@ -450,7 +463,7 @@ export async function buildSystemPrompt(options: BuildSystemPromptOptions = {}):
|
|
|
450
463
|
preloadedSkills: providedPreloadedSkills,
|
|
451
464
|
rules,
|
|
452
465
|
} = options;
|
|
453
|
-
const resolvedCwd = cwd ??
|
|
466
|
+
const resolvedCwd = cwd ?? getProjectDir();
|
|
454
467
|
const resolvedCustomPrompt = await resolvePromptInput(customPrompt, "system prompt");
|
|
455
468
|
const resolvedAppendPrompt = await resolvePromptInput(appendSystemPrompt, "append system prompt");
|
|
456
469
|
|
|
@@ -57,8 +57,8 @@ export function normalizeBashCommand(command: string): NormalizedCommand {
|
|
|
57
57
|
normalized = normalized.slice(0, -fullMatch.length);
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
-
//
|
|
61
|
-
normalized = normalized.
|
|
60
|
+
// Preserve internal whitespace (important for heredocs / indentation-sensitive scripts)
|
|
61
|
+
normalized = normalized.trim();
|
|
62
62
|
|
|
63
63
|
return {
|
|
64
64
|
command: normalized,
|
package/src/tools/bash.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
5
5
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { $env, isEnoent } from "@oh-my-pi/pi-utils";
|
|
7
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
7
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
9
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
9
10
|
import { type BashResult, executeBash } from "../exec/bash-executor";
|
|
@@ -205,7 +206,7 @@ interface BashRenderContext {
|
|
|
205
206
|
function formatBashCommand(args: BashRenderArgs, _uiTheme: Theme): string {
|
|
206
207
|
const command = args.command || "…";
|
|
207
208
|
const prompt = "$";
|
|
208
|
-
const cwd =
|
|
209
|
+
const cwd = getProjectDir();
|
|
209
210
|
let displayWorkdir = args.cwd;
|
|
210
211
|
|
|
211
212
|
if (displayWorkdir) {
|
package/src/tools/fetch.ts
CHANGED
|
@@ -555,13 +555,13 @@ async function renderUrl(url: string, timeout: number, raw: boolean, signal?: Ab
|
|
|
555
555
|
if (!response.ok) {
|
|
556
556
|
return {
|
|
557
557
|
url,
|
|
558
|
-
finalUrl: url,
|
|
559
|
-
contentType: "unknown",
|
|
558
|
+
finalUrl: response.finalUrl || url,
|
|
559
|
+
contentType: response.contentType || "unknown",
|
|
560
560
|
method: "failed",
|
|
561
561
|
content: "",
|
|
562
562
|
fetchedAt,
|
|
563
563
|
truncated: false,
|
|
564
|
-
notes: ["Failed to fetch URL"],
|
|
564
|
+
notes: [response.status ? `Failed to fetch URL (HTTP ${response.status})` : "Failed to fetch URL"],
|
|
565
565
|
};
|
|
566
566
|
}
|
|
567
567
|
|
package/src/tools/python.ts
CHANGED
|
@@ -4,6 +4,7 @@ import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallb
|
|
|
4
4
|
import type { ImageContent } from "@oh-my-pi/pi-ai";
|
|
5
5
|
import type { Component } from "@oh-my-pi/pi-tui";
|
|
6
6
|
import { Text } from "@oh-my-pi/pi-tui";
|
|
7
|
+
import { getProjectDir } from "@oh-my-pi/pi-utils/dirs";
|
|
7
8
|
import { type Static, Type } from "@sinclair/typebox";
|
|
8
9
|
import { renderPromptTemplate } from "../config/prompt-templates";
|
|
9
10
|
import type { RenderResultOptions } from "../extensibility/custom-tools/types";
|
|
@@ -836,7 +837,7 @@ export const pythonToolRenderer = {
|
|
|
836
837
|
renderCall(args: PythonRenderArgs, uiTheme: Theme): Component {
|
|
837
838
|
const ui = new ToolUIKit(uiTheme);
|
|
838
839
|
const cells = args.cells ?? [];
|
|
839
|
-
const cwd =
|
|
840
|
+
const cwd = getProjectDir();
|
|
840
841
|
let displayWorkdir = args.cwd;
|
|
841
842
|
|
|
842
843
|
if (displayWorkdir) {
|