@oh-my-pi/pi-coding-agent 9.2.1 → 9.2.3
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 +81 -0
- package/package.json +7 -7
- package/src/cli/update-cli.ts +2 -7
- package/src/commit/agentic/agent.ts +4 -0
- package/src/commit/agentic/index.ts +5 -1
- package/src/commit/changelog/detect.ts +2 -1
- package/src/commit/changelog/index.ts +2 -1
- package/src/config/prompt-templates.ts +2 -1
- package/src/config/settings-manager.ts +39 -3
- package/src/exec/bash-executor.ts +53 -3
- package/src/exec/shell-session.ts +593 -0
- package/src/ipy/gateway-coordinator.ts +5 -5
- package/src/ipy/kernel.ts +8 -7
- package/src/lsp/config.ts +13 -16
- package/src/lsp/index.ts +5 -5
- package/src/modes/components/settings-defs.ts +9 -0
- package/src/modes/components/status-line.ts +28 -32
- package/src/patch/applicator.ts +5 -4
- package/src/prompts/system/system-prompt.md +42 -58
- package/src/sdk.ts +3 -0
- package/src/session/auth-storage.ts +26 -5
- package/src/system-prompt.ts +5 -0
- package/src/utils/shell-snapshot.ts +27 -13
- package/src/utils/tools-manager.ts +9 -9
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,87 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [9.2.3] - 2026-01-31
|
|
6
|
+
### Added
|
|
7
|
+
|
|
8
|
+
- Persistent shell session support for bash tool with environment variable preservation across commands
|
|
9
|
+
- New `shellForceBasic` setting to force bash/sh even if user's default shell is different (default: true)
|
|
10
|
+
- New `OMP_SHELL_PERSIST` environment variable to control persistent shell behavior (set to 0 to disable)
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- Bash tool now reuses a persistent shell session by default on Unix systems for improved performance and state preservation
|
|
15
|
+
- Replaced Bun file APIs with Node.js `fs` module for better cross-runtime compatibility
|
|
16
|
+
- LSP configuration loading is now synchronous instead of async
|
|
17
|
+
- Shell snapshot generation now sanitizes `BASH_ENV` and `ENV` variables to prevent shell exit issues
|
|
18
|
+
- Shell snapshot caching now per-shell-binary instead of global to avoid cross-shell contamination
|
|
19
|
+
- System prompt restructured with coordinator-specific guidance for parallel task delegation
|
|
20
|
+
- Bash tool now reuses a persistent shell session by default on Unix. Set `OMP_SHELL_PERSIST=0` to disable or fall back to per-command execution on Windows/unsupported shells.
|
|
21
|
+
- Added a shellForceBasic setting to force bash/sh and keep environment changes across bash commands (default: true).
|
|
22
|
+
|
|
23
|
+
### Fixed
|
|
24
|
+
|
|
25
|
+
- Shell snapshots now filter unsafe bash options (onecmd, monitor, restricted) to prevent session exits
|
|
26
|
+
- Git branch detection in status line now works synchronously without race conditions
|
|
27
|
+
- Shell session initialization properly restores trap handlers and shell functions after command execution
|
|
28
|
+
- Sanitized `BASH_ENV`/`ENV` during persistent shell startup and snapshot creation to prevent basic shells from exiting immediately.
|
|
29
|
+
- Cached shell snapshots per shell binary to avoid sourcing zsh snapshots in bash sessions.
|
|
30
|
+
- Filtered unsafe bash options (onecmd/monitor/restricted) out of shell snapshots to prevent session exits.
|
|
31
|
+
|
|
32
|
+
## [9.2.2] - 2026-01-31
|
|
33
|
+
|
|
34
|
+
### Added
|
|
35
|
+
- Added grep CLI subcommand (`omp grep`) for testing pattern matching
|
|
36
|
+
- Added fuzzy matching for model resolution with scoring and ranking fallback
|
|
37
|
+
- Added 'Open: artifact folder' menu option to debug selector for quick access to session artifacts
|
|
38
|
+
- Added Kimi API format setting for selecting between OpenAI and Anthropic formats
|
|
39
|
+
- Added Codex and Gemini web search providers with OAuth and grounding support
|
|
40
|
+
- Added /debug command with interactive menu for profiling, heap snapshots, session dumps, and diagnostics
|
|
41
|
+
- Added configurable ask timeout and notification settings
|
|
42
|
+
- Added gitignore-aware project tree scanning with ripgrep integration
|
|
43
|
+
- Added project tree visualization to system prompts with configurable depth and entry limits
|
|
44
|
+
- Added reset() method to CountdownTimer with integration into HookSelectorComponent
|
|
45
|
+
- Added custom message support to AgentSession via promptCustomMessage() method
|
|
46
|
+
- Added skill message component for rendering /skill command messages as compact entries
|
|
47
|
+
- Added model preference matching system for intelligent model selection based on usage history
|
|
48
|
+
- Added designer agent with UI/UX review and accessibility audit capabilities
|
|
49
|
+
- Added model-specific edit variant configuration for patch/replace modes
|
|
50
|
+
- Added automatic browser opening when stats dashboard starts
|
|
51
|
+
- Added model statistics table and TTFT/throughput metrics to stats dashboard
|
|
52
|
+
- Added artifact allocation for truncated fetch responses to preserve full content
|
|
53
|
+
- Added 30-second timeout to ask tool with auto-selection of recommended option
|
|
54
|
+
- Added recommended parameter (0-indexed) to ask tool for specifying default option
|
|
55
|
+
- Added JTD to TypeScript converter for rendering schemas in system prompts
|
|
56
|
+
- Added tools list to system prompt for better agent awareness
|
|
57
|
+
- Added synthetic message flag for system-injected prompts
|
|
58
|
+
- Added session compaction enhancements with auto-continue, tool pruning, and remote endpoint support
|
|
59
|
+
- Added detection and rendering of missing complete tool warning in subagent output
|
|
60
|
+
- Added outline UI components for bordered list containers
|
|
61
|
+
- Added macOS NFD normalization and curly quote variant resolution for file paths
|
|
62
|
+
- Enhanced session compaction with dynamic token ratio adjustment and improved summary preservation
|
|
63
|
+
|
|
64
|
+
### Changed
|
|
65
|
+
- Simplified find tool API by consolidating path and pattern parameters
|
|
66
|
+
- Replaced bulk file loading with streaming for read tool to reduce memory overhead
|
|
67
|
+
- Migrated grep and find tools to WASM-based implementation
|
|
68
|
+
- Replaced ripgrep-based file listing with glob-based file discovery for project scans
|
|
69
|
+
- Updated minimum Bun runtime requirement to >=1.3.7
|
|
70
|
+
- Renamed task parameter from output to schema
|
|
71
|
+
- Renamed complete tool to submit_result for clarity and consistency
|
|
72
|
+
- Improved output preview logic: shows full output for ≤30 lines, truncates to 10 lines for larger output
|
|
73
|
+
|
|
74
|
+
### Fixed
|
|
75
|
+
- Enhanced error reporting with debug stack trace when DEBUG env is set
|
|
76
|
+
- Improved OAuth token refresh error handling to distinguish transient vs definitive failures
|
|
77
|
+
- Added windowsHide option to child process spawn calls to prevent console windows on Windows
|
|
78
|
+
- External edits to config.yml are now preserved when omp reloads or saves settings
|
|
79
|
+
- Exposed LSP server startup errors in session display and logs
|
|
80
|
+
- Improved error handling and security in agent storage initialization with restrictive file permissions
|
|
81
|
+
- Fixed LSP server display showing unknown when server warmup fails
|
|
82
|
+
- Preserved null timeout when user disables ask timeout setting
|
|
83
|
+
- Removed incorrect timeout unit conversion logic in cursor, fetch, gemini-image, and ssh tools
|
|
84
|
+
- Blocked /fork command while streaming to prevent split session logs
|
|
85
|
+
|
|
5
86
|
## [9.0.0] - 2026-01-29
|
|
6
87
|
|
|
7
88
|
### Fixed
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@oh-my-pi/pi-coding-agent",
|
|
3
|
-
"version": "9.2.
|
|
3
|
+
"version": "9.2.3",
|
|
4
4
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"ompConfig": {
|
|
@@ -79,12 +79,12 @@
|
|
|
79
79
|
"test": "bun test"
|
|
80
80
|
},
|
|
81
81
|
"dependencies": {
|
|
82
|
-
"@oh-my-pi/omp-stats": "9.2.
|
|
83
|
-
"@oh-my-pi/pi-agent-core": "9.2.
|
|
84
|
-
"@oh-my-pi/pi-ai": "9.2.
|
|
85
|
-
"@oh-my-pi/pi-natives": "9.2.
|
|
86
|
-
"@oh-my-pi/pi-tui": "9.2.
|
|
87
|
-
"@oh-my-pi/pi-utils": "9.2.
|
|
82
|
+
"@oh-my-pi/omp-stats": "9.2.3",
|
|
83
|
+
"@oh-my-pi/pi-agent-core": "9.2.3",
|
|
84
|
+
"@oh-my-pi/pi-ai": "9.2.3",
|
|
85
|
+
"@oh-my-pi/pi-natives": "9.2.3",
|
|
86
|
+
"@oh-my-pi/pi-tui": "9.2.3",
|
|
87
|
+
"@oh-my-pi/pi-utils": "9.2.3",
|
|
88
88
|
"@openai/agents": "^0.4.4",
|
|
89
89
|
"@sinclair/typebox": "^0.34.48",
|
|
90
90
|
"ajv": "^8.17.1",
|
package/src/cli/update-cli.ts
CHANGED
|
@@ -195,15 +195,10 @@ async function updateViaBinary(release: ReleaseInfo): Promise<void> {
|
|
|
195
195
|
console.log(chalk.green(`\n${theme.status.success} Updated to ${release.version}`));
|
|
196
196
|
console.log(chalk.dim(`Restart ${APP_NAME} to use the new version`));
|
|
197
197
|
} catch (err) {
|
|
198
|
-
|
|
199
|
-
Bun.file(backupPath).exists(),
|
|
200
|
-
Bun.file(execPath).exists(),
|
|
201
|
-
Bun.file(tempPath).exists(),
|
|
202
|
-
]);
|
|
203
|
-
if (backupExists && !execExists) {
|
|
198
|
+
if (fs.existsSync(backupPath) && !fs.existsSync(execPath)) {
|
|
204
199
|
await fs.promises.rename(backupPath, execPath);
|
|
205
200
|
}
|
|
206
|
-
if (
|
|
201
|
+
if (fs.existsSync(tempPath)) {
|
|
207
202
|
await fs.promises.unlink(tempPath);
|
|
208
203
|
}
|
|
209
204
|
throw err;
|
|
@@ -113,6 +113,10 @@ export async function runCommitAgentSession(input: CommitAgentInput): Promise<Co
|
|
|
113
113
|
messageCount += 1;
|
|
114
114
|
isThinking = false;
|
|
115
115
|
clearThinkingLine();
|
|
116
|
+
const assistantMessage = event.message as { stopReason?: string; errorMessage?: string };
|
|
117
|
+
if (assistantMessage.stopReason === "error" && assistantMessage.errorMessage) {
|
|
118
|
+
writeStdout(`● Error: ${assistantMessage.errorMessage}`);
|
|
119
|
+
}
|
|
116
120
|
const messageText = extractMessageText(event.message?.content ?? []);
|
|
117
121
|
if (messageText) {
|
|
118
122
|
writeAssistantMessage(messageText);
|
|
@@ -134,7 +134,11 @@ export async function runAgenticCommit(args: CommitCommandArgs): Promise<void> {
|
|
|
134
134
|
existingChangelogEntries,
|
|
135
135
|
});
|
|
136
136
|
} catch (error) {
|
|
137
|
-
|
|
137
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
138
|
+
writeStderr(`Agent error: ${errorMessage}`);
|
|
139
|
+
if (error instanceof Error && error.stack && process.env.DEBUG) {
|
|
140
|
+
writeStderr(error.stack);
|
|
141
|
+
}
|
|
138
142
|
writeStdout("● Using fallback commit generation...");
|
|
139
143
|
commitState = { proposal: generateFallbackProposal(numstat) };
|
|
140
144
|
usedFallback = true;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import type { ChangelogBoundary } from "../../commit/types";
|
|
3
4
|
|
|
@@ -25,7 +26,7 @@ async function findNearestChangelog(cwd: string, filePath: string): Promise<stri
|
|
|
25
26
|
const root = path.resolve(cwd);
|
|
26
27
|
while (true) {
|
|
27
28
|
const candidate = path.resolve(current, CHANGELOG_NAME);
|
|
28
|
-
if (
|
|
29
|
+
if (fs.existsSync(candidate)) {
|
|
29
30
|
return candidate;
|
|
30
31
|
}
|
|
31
32
|
if (current === root) return null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import type { Api, Model } from "@oh-my-pi/pi-ai";
|
|
3
4
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
@@ -108,7 +109,7 @@ export async function applyChangelogProposals({
|
|
|
108
109
|
)
|
|
109
110
|
continue;
|
|
110
111
|
onProgress?.(`Applying entries for ${proposal.path}...`);
|
|
111
|
-
const exists =
|
|
112
|
+
const exists = fs.existsSync(proposal.path);
|
|
112
113
|
if (!exists) {
|
|
113
114
|
logger.warn("commit changelog path missing", { path: proposal.path });
|
|
114
115
|
continue;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import * as fs from "node:fs";
|
|
1
2
|
import * as path from "node:path";
|
|
2
3
|
import { logger } from "@oh-my-pi/pi-utils";
|
|
3
4
|
import Handlebars from "handlebars";
|
|
@@ -435,7 +436,7 @@ async function loadTemplatesFromDir(
|
|
|
435
436
|
}
|
|
436
437
|
}
|
|
437
438
|
} catch (error) {
|
|
438
|
-
if (!
|
|
439
|
+
if (!fs.existsSync(dir)) {
|
|
439
440
|
return [];
|
|
440
441
|
}
|
|
441
442
|
logger.warn("Failed to scan prompt templates directory", { dir, error: String(error) });
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import * as fs from "node:fs
|
|
1
|
+
import * as fs from "node:fs";
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { isEnoent, logger, procmgr } from "@oh-my-pi/pi-utils";
|
|
4
4
|
import { YAML } from "bun";
|
|
@@ -222,6 +222,7 @@ export interface Settings {
|
|
|
222
222
|
retry?: RetrySettings;
|
|
223
223
|
hideThinkingBlock?: boolean;
|
|
224
224
|
shellPath?: string; // Custom shell path (e.g., for Cygwin users on Windows)
|
|
225
|
+
shellForceBasic?: boolean; // Force bash/sh even if user's default shell is different
|
|
225
226
|
collapseChangelog?: boolean; // Show condensed changelog after update (use /changelog for full)
|
|
226
227
|
startup?: StartupSettings;
|
|
227
228
|
doubleEscapeAction?: "branch" | "tree"; // Action for double-escape with empty editor (default: "tree")
|
|
@@ -635,7 +636,7 @@ export class SettingsManager {
|
|
|
635
636
|
migrated = true;
|
|
636
637
|
// Backup settings.json
|
|
637
638
|
try {
|
|
638
|
-
|
|
639
|
+
fs.renameSync(settingsJsonPath, `${settingsJsonPath}.bak`);
|
|
639
640
|
} catch (error) {
|
|
640
641
|
logger.warn("SettingsManager failed to backup settings.json", { error: String(error) });
|
|
641
642
|
}
|
|
@@ -1061,12 +1062,22 @@ export class SettingsManager {
|
|
|
1061
1062
|
return this.settings.shellPath;
|
|
1062
1063
|
}
|
|
1063
1064
|
|
|
1065
|
+
getShellForceBasic(): boolean {
|
|
1066
|
+
return this.settings.shellForceBasic ?? true;
|
|
1067
|
+
}
|
|
1068
|
+
|
|
1064
1069
|
async setShellPath(path: string | undefined): Promise<void> {
|
|
1065
1070
|
this.globalSettings.shellPath = path;
|
|
1066
1071
|
this.markModified("shellPath");
|
|
1067
1072
|
await this.save();
|
|
1068
1073
|
}
|
|
1069
1074
|
|
|
1075
|
+
async setShellForceBasic(force: boolean): Promise<void> {
|
|
1076
|
+
this.globalSettings.shellForceBasic = force;
|
|
1077
|
+
this.markModified("shellForceBasic");
|
|
1078
|
+
await this.save();
|
|
1079
|
+
}
|
|
1080
|
+
|
|
1070
1081
|
getCollapseChangelog(): boolean {
|
|
1071
1082
|
return this.settings.collapseChangelog ?? false;
|
|
1072
1083
|
}
|
|
@@ -1963,7 +1974,13 @@ export class SettingsManager {
|
|
|
1963
1974
|
* Gets the shell configuration
|
|
1964
1975
|
* @returns The shell configuration
|
|
1965
1976
|
*/
|
|
1966
|
-
|
|
1977
|
+
getShellConfig() {
|
|
1978
|
+
if (this.getShellForceBasic()) {
|
|
1979
|
+
const basicShell = resolveBasicShell();
|
|
1980
|
+
if (basicShell) {
|
|
1981
|
+
return procmgr.getShellConfig(basicShell);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1967
1984
|
const shell = this.getShellPath();
|
|
1968
1985
|
return procmgr.getShellConfig(shell);
|
|
1969
1986
|
}
|
|
@@ -1977,3 +1994,22 @@ export class SettingsManager {
|
|
|
1977
1994
|
return settings.getShellConfig();
|
|
1978
1995
|
}
|
|
1979
1996
|
}
|
|
1997
|
+
|
|
1998
|
+
function resolveBasicShell(): string | undefined {
|
|
1999
|
+
const searchPaths = ["/bin", "/usr/bin", "/usr/local/bin", "/opt/homebrew/bin"];
|
|
2000
|
+
const candidates = ["bash", "sh"];
|
|
2001
|
+
|
|
2002
|
+
for (const name of candidates) {
|
|
2003
|
+
for (const dir of searchPaths) {
|
|
2004
|
+
const fullPath = path.join(dir, name);
|
|
2005
|
+
if (fs.existsSync(fullPath)) return fullPath;
|
|
2006
|
+
}
|
|
2007
|
+
}
|
|
2008
|
+
|
|
2009
|
+
for (const name of ["bash", "bash.exe", "sh", "sh.exe"]) {
|
|
2010
|
+
const resolved = Bun.which(name);
|
|
2011
|
+
if (resolved) return resolved;
|
|
2012
|
+
}
|
|
2013
|
+
|
|
2014
|
+
return undefined;
|
|
2015
|
+
}
|
|
@@ -7,6 +7,7 @@ import { Exception, ptree } from "@oh-my-pi/pi-utils";
|
|
|
7
7
|
import { SettingsManager } from "../config/settings-manager";
|
|
8
8
|
import { OutputSink } from "../session/streaming-output";
|
|
9
9
|
import { getOrCreateSnapshot, getSnapshotSourceCommand } from "../utils/shell-snapshot";
|
|
10
|
+
import { executeShellCommand } from "./shell-session";
|
|
10
11
|
|
|
11
12
|
export interface BashExecutorOptions {
|
|
12
13
|
cwd?: string;
|
|
@@ -34,13 +35,62 @@ export interface BashResult {
|
|
|
34
35
|
|
|
35
36
|
export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
|
|
36
37
|
const { shell, args, env, prefix } = await SettingsManager.getGlobalShellConfig();
|
|
38
|
+
const snapshotPath = await getOrCreateSnapshot(shell, env);
|
|
39
|
+
|
|
40
|
+
if (shouldUsePersistentShell(shell)) {
|
|
41
|
+
return await executeShellCommand({ shell, env, prefix, snapshotPath }, command, {
|
|
42
|
+
cwd: options?.cwd,
|
|
43
|
+
timeout: options?.timeout,
|
|
44
|
+
signal: options?.signal,
|
|
45
|
+
onChunk: options?.onChunk,
|
|
46
|
+
env: options?.env,
|
|
47
|
+
artifactPath: options?.artifactPath,
|
|
48
|
+
artifactId: options?.artifactId,
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return await executeBashOnce(command, options, { shell, args, env, prefix, snapshotPath });
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function shouldUsePersistentShell(shell: string): boolean {
|
|
56
|
+
const flag = parseEnvFlag(process.env.OMP_SHELL_PERSIST);
|
|
57
|
+
if (flag !== undefined) return flag;
|
|
58
|
+
if (process.platform === "win32") return false;
|
|
59
|
+
const normalized = shell.toLowerCase();
|
|
60
|
+
return (
|
|
61
|
+
normalized.includes("bash") ||
|
|
62
|
+
normalized.includes("zsh") ||
|
|
63
|
+
normalized.includes("fish") ||
|
|
64
|
+
normalized.endsWith("/sh") ||
|
|
65
|
+
normalized.endsWith("\\\\sh") ||
|
|
66
|
+
normalized.endsWith("sh")
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function parseEnvFlag(value: string | undefined): boolean | undefined {
|
|
71
|
+
if (!value) return undefined;
|
|
72
|
+
const normalized = value.toLowerCase();
|
|
73
|
+
if (["1", "true", "yes", "on"].includes(normalized)) return true;
|
|
74
|
+
if (["0", "false", "no", "off"].includes(normalized)) return false;
|
|
75
|
+
return undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
async function executeBashOnce(
|
|
79
|
+
command: string,
|
|
80
|
+
options: BashExecutorOptions | undefined,
|
|
81
|
+
config: {
|
|
82
|
+
shell: string;
|
|
83
|
+
args: string[];
|
|
84
|
+
env: Record<string, string | undefined>;
|
|
85
|
+
prefix?: string;
|
|
86
|
+
snapshotPath: string | null;
|
|
87
|
+
},
|
|
88
|
+
): Promise<BashResult> {
|
|
89
|
+
const { shell, args, env, prefix, snapshotPath } = config;
|
|
37
90
|
|
|
38
91
|
// Merge additional env vars if provided
|
|
39
92
|
const finalEnv = options?.env ? { ...env, ...options.env } : env;
|
|
40
|
-
|
|
41
|
-
const snapshotPath = await getOrCreateSnapshot(shell, env);
|
|
42
93
|
const snapshotPrefix = getSnapshotSourceCommand(snapshotPath);
|
|
43
|
-
|
|
44
94
|
const prefixedCommand = prefix ? `${prefix} ${command}` : command;
|
|
45
95
|
const finalCommand = `${snapshotPrefix}${prefixedCommand}`;
|
|
46
96
|
|