@oh-my-pi/pi-coding-agent 9.2.0 → 9.2.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -2,6 +2,60 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [9.2.2] - 2026-01-31
6
+
7
+ ### Added
8
+ - Added grep CLI subcommand (`omp grep`) for testing pattern matching
9
+ - Added fuzzy matching for model resolution with scoring and ranking fallback
10
+ - Added 'Open: artifact folder' menu option to debug selector for quick access to session artifacts
11
+ - Added Kimi API format setting for selecting between OpenAI and Anthropic formats
12
+ - Added Codex and Gemini web search providers with OAuth and grounding support
13
+ - Added /debug command with interactive menu for profiling, heap snapshots, session dumps, and diagnostics
14
+ - Added configurable ask timeout and notification settings
15
+ - Added gitignore-aware project tree scanning with ripgrep integration
16
+ - Added project tree visualization to system prompts with configurable depth and entry limits
17
+ - Added reset() method to CountdownTimer with integration into HookSelectorComponent
18
+ - Added custom message support to AgentSession via promptCustomMessage() method
19
+ - Added skill message component for rendering /skill command messages as compact entries
20
+ - Added model preference matching system for intelligent model selection based on usage history
21
+ - Added designer agent with UI/UX review and accessibility audit capabilities
22
+ - Added model-specific edit variant configuration for patch/replace modes
23
+ - Added automatic browser opening when stats dashboard starts
24
+ - Added model statistics table and TTFT/throughput metrics to stats dashboard
25
+ - Added artifact allocation for truncated fetch responses to preserve full content
26
+ - Added 30-second timeout to ask tool with auto-selection of recommended option
27
+ - Added recommended parameter (0-indexed) to ask tool for specifying default option
28
+ - Added JTD to TypeScript converter for rendering schemas in system prompts
29
+ - Added tools list to system prompt for better agent awareness
30
+ - Added synthetic message flag for system-injected prompts
31
+ - Added session compaction enhancements with auto-continue, tool pruning, and remote endpoint support
32
+ - Added detection and rendering of missing complete tool warning in subagent output
33
+ - Added outline UI components for bordered list containers
34
+ - Added macOS NFD normalization and curly quote variant resolution for file paths
35
+ - Enhanced session compaction with dynamic token ratio adjustment and improved summary preservation
36
+
37
+ ### Changed
38
+ - Simplified find tool API by consolidating path and pattern parameters
39
+ - Replaced bulk file loading with streaming for read tool to reduce memory overhead
40
+ - Migrated grep and find tools to WASM-based implementation
41
+ - Replaced ripgrep-based file listing with glob-based file discovery for project scans
42
+ - Updated minimum Bun runtime requirement to >=1.3.7
43
+ - Renamed task parameter from output to schema
44
+ - Renamed complete tool to submit_result for clarity and consistency
45
+ - Improved output preview logic: shows full output for ≤30 lines, truncates to 10 lines for larger output
46
+
47
+ ### Fixed
48
+ - Enhanced error reporting with debug stack trace when DEBUG env is set
49
+ - Improved OAuth token refresh error handling to distinguish transient vs definitive failures
50
+ - Added windowsHide option to child process spawn calls to prevent console windows on Windows
51
+ - External edits to config.yml are now preserved when omp reloads or saves settings
52
+ - Exposed LSP server startup errors in session display and logs
53
+ - Improved error handling and security in agent storage initialization with restrictive file permissions
54
+ - Fixed LSP server display showing unknown when server warmup fails
55
+ - Preserved null timeout when user disables ask timeout setting
56
+ - Removed incorrect timeout unit conversion logic in cursor, fetch, gemini-image, and ssh tools
57
+ - Blocked /fork command while streaming to prevent split session logs
58
+
5
59
  ## [9.0.0] - 2026-01-29
6
60
 
7
61
  ### Fixed
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "9.2.0",
3
+ "version": "9.2.2",
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.0",
83
- "@oh-my-pi/pi-agent-core": "9.2.0",
84
- "@oh-my-pi/pi-ai": "9.2.0",
85
- "@oh-my-pi/pi-natives": "9.2.0",
86
- "@oh-my-pi/pi-tui": "9.2.0",
87
- "@oh-my-pi/pi-utils": "9.2.0",
82
+ "@oh-my-pi/omp-stats": "9.2.2",
83
+ "@oh-my-pi/pi-agent-core": "9.2.2",
84
+ "@oh-my-pi/pi-ai": "9.2.2",
85
+ "@oh-my-pi/pi-natives": "9.2.2",
86
+ "@oh-my-pi/pi-tui": "9.2.2",
87
+ "@oh-my-pi/pi-utils": "9.2.2",
88
88
  "@openai/agents": "^0.4.4",
89
89
  "@sinclair/typebox": "^0.34.48",
90
90
  "ajv": "^8.17.1",
@@ -114,6 +114,7 @@ export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
114
114
  Bun.spawn(openCommand === "cmd" ? ["cmd", "/c", "start", url] : [openCommand, url], {
115
115
  stdout: "ignore",
116
116
  stderr: "ignore",
117
+ windowsHide: true,
117
118
  }).unref();
118
119
 
119
120
  console.log("Press Ctrl+C to stop\n");
@@ -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
- writeStderr(`Agent error: ${error instanceof Error ? error.message : String(error)}`);
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;
@@ -37,6 +37,7 @@ export async function commit(cwd: string, message: string): Promise<GitResult> {
37
37
  stdin: Buffer.from(message),
38
38
  stdout: "pipe",
39
39
  stderr: "pipe",
40
+ windowsHide: true,
40
41
  });
41
42
 
42
43
  const [stdout, stderr, exitCode] = await Promise.all([
@@ -310,7 +310,7 @@ export class DebugSelectorComponent extends Container {
310
310
  const [cmd, ...args] = openArgs;
311
311
 
312
312
  try {
313
- Bun.spawn([cmd, ...args], { stdout: "ignore", stderr: "ignore" }).unref();
313
+ Bun.spawn([cmd, ...args], { stdout: "ignore", stderr: "ignore", windowsHide: true }).unref();
314
314
  this.ctx.showStatus(`Opened: ${artifactsDir}`);
315
315
  } catch (err) {
316
316
  this.ctx.showError(`Failed to open artifact folder: ${err instanceof Error ? err.message : String(err)}`);
@@ -50,6 +50,7 @@ export async function installPlugin(packageName: string): Promise<InstalledPlugi
50
50
  stdin: "ignore",
51
51
  stdout: "pipe",
52
52
  stderr: "pipe",
53
+ windowsHide: true,
53
54
  });
54
55
 
55
56
  const exitCode = await proc.exited;
@@ -91,6 +92,7 @@ export async function uninstallPlugin(name: string): Promise<void> {
91
92
  stdin: "ignore",
92
93
  stdout: "pipe",
93
94
  stderr: "pipe",
95
+ windowsHide: true,
94
96
  });
95
97
 
96
98
  const exitCode = await proc.exited;
@@ -159,6 +159,7 @@ export class PluginManager {
159
159
  stdin: "ignore",
160
160
  stdout: "pipe",
161
161
  stderr: "pipe",
162
+ windowsHide: true,
162
163
  });
163
164
 
164
165
  const exitCode = await proc.exited;
@@ -239,6 +240,7 @@ export class PluginManager {
239
240
  stdin: "ignore",
240
241
  stdout: "pipe",
241
242
  stderr: "pipe",
243
+ windowsHide: true,
242
244
  });
243
245
 
244
246
  const exitCode = await proc.exited;
@@ -621,6 +623,7 @@ export class PluginManager {
621
623
  stdin: "ignore",
622
624
  stdout: "pipe",
623
625
  stderr: "pipe",
626
+ windowsHide: true,
624
627
  });
625
628
  return (await proc.exited) === 0;
626
629
  } catch {
@@ -420,6 +420,7 @@ async function startGatewayProcess(
420
420
  stdout: "pipe",
421
421
  stderr: "pipe",
422
422
  detached: true,
423
+ windowsHide: true,
423
424
  env: kernelEnv,
424
425
  },
425
426
  );
@@ -86,6 +86,7 @@ async function runBiome(
86
86
  cwd,
87
87
  stdout: "pipe",
88
88
  stderr: "pipe",
89
+ windowsHide: true,
89
90
  });
90
91
 
91
92
  const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
package/src/lsp/index.ts CHANGED
@@ -437,6 +437,7 @@ async function runWorkspaceDiagnostics(
437
437
  cwd,
438
438
  stdout: "pipe",
439
439
  stderr: "pipe",
440
+ windowsHide: true,
440
441
  });
441
442
 
442
443
  const [stdout, stderr] = await Promise.all([new Response(proc.stdout).text(), new Response(proc.stderr).text()]);
package/src/lsp/lspmux.ts CHANGED
@@ -104,6 +104,7 @@ async function checkServerRunning(binaryPath: string): Promise<boolean> {
104
104
  const proc = Bun.spawn([binaryPath, "status"], {
105
105
  stdout: "pipe",
106
106
  stderr: "pipe",
107
+ windowsHide: true,
107
108
  });
108
109
 
109
110
  const exited = await Promise.race([
@@ -19,6 +19,7 @@ import {
19
19
  loginGitHubCopilot,
20
20
  loginKimi,
21
21
  loginOpenAICodex,
22
+ loginOpenCode,
22
23
  type OAuthController,
23
24
  type OAuthCredentials,
24
25
  type OAuthProvider,
@@ -782,6 +783,11 @@ export class AuthStorage {
782
783
  ctrl.onProgress ? () => ctrl.onProgress?.("Waiting for browser authentication...") : undefined,
783
784
  );
784
785
  break;
786
+ case "opencode": {
787
+ const apiKey = await loginOpenCode(ctrl);
788
+ credentials = { access: apiKey, refresh: apiKey, expires: Number.MAX_SAFE_INTEGER };
789
+ break;
790
+ }
785
791
  default:
786
792
  throw new Error(`Unknown OAuth provider: ${provider}`);
787
793
  }
@@ -1281,14 +1287,29 @@ export class AuthStorage {
1281
1287
  this.recordSessionCredential(provider, sessionId, "oauth", selection.index);
1282
1288
  return result.apiKey;
1283
1289
  } catch (error) {
1284
- logger.warn("OAuth token refresh failed, removing credential", {
1290
+ const errorMsg = String(error);
1291
+ // Only remove credentials for definitive auth failures
1292
+ // Keep credentials for transient errors (network, 5xx) and block temporarily
1293
+ const isDefinitiveFailure =
1294
+ /invalid_grant|invalid_token|revoked|unauthorized|expired.*refresh|refresh.*expired/i.test(errorMsg) ||
1295
+ (/401|403/.test(errorMsg) && !/timeout|network|fetch failed|ECONNREFUSED/i.test(errorMsg));
1296
+
1297
+ logger.warn("OAuth token refresh failed", {
1285
1298
  provider,
1286
1299
  index: selection.index,
1287
- error: String(error),
1300
+ error: errorMsg,
1301
+ isDefinitiveFailure,
1288
1302
  });
1289
- this.removeCredentialAt(provider, selection.index);
1290
- if (this.getCredentialsForProvider(provider).some(credential => credential.type === "oauth")) {
1291
- return this.getApiKey(provider, sessionId, options);
1303
+
1304
+ if (isDefinitiveFailure) {
1305
+ // Permanently remove invalid credentials
1306
+ this.removeCredentialAt(provider, selection.index);
1307
+ if (this.getCredentialsForProvider(provider).some(credential => credential.type === "oauth")) {
1308
+ return this.getApiKey(provider, sessionId, options);
1309
+ }
1310
+ } else {
1311
+ // Block temporarily for transient failures (5 minutes)
1312
+ this.markCredentialBlocked(providerKey, selection.index, this.usageNow() + 5 * 60 * 1000);
1292
1313
  }
1293
1314
  }
1294
1315
 
@@ -37,25 +37,33 @@ export async function copyToClipboard(text: string): Promise<void> {
37
37
 
38
38
  try {
39
39
  if (p === "darwin") {
40
- await Bun.spawn(["pbcopy"], { stdin: Buffer.from(text), timeout }).exited;
40
+ await Bun.spawn(["pbcopy"], { stdin: Buffer.from(text), timeout, windowsHide: true }).exited;
41
41
  } else if (p === "win32") {
42
- await Bun.spawn(["clip"], { stdin: Buffer.from(text), timeout }).exited;
42
+ await Bun.spawn(["clip"], { stdin: Buffer.from(text), timeout, windowsHide: true }).exited;
43
43
  } else {
44
44
  const wayland = isWaylandSession();
45
45
  if (wayland) {
46
46
  const wlCopyPath = Bun.which("wl-copy");
47
47
  if (wlCopyPath) {
48
48
  // Fire-and-forget: wl-copy may not exit promptly, so we unref to avoid blocking
49
- void Bun.spawn([wlCopyPath], { stdin: Buffer.from(text), timeout }).unref();
49
+ void Bun.spawn([wlCopyPath], { stdin: Buffer.from(text), timeout, windowsHide: true }).unref();
50
50
  return;
51
51
  }
52
52
  }
53
53
 
54
54
  // Linux - try xclip first, fall back to xsel
55
55
  try {
56
- await Bun.spawn(["xclip", "-selection", "clipboard"], { stdin: Buffer.from(text), timeout }).exited;
56
+ await Bun.spawn(["xclip", "-selection", "clipboard"], {
57
+ stdin: Buffer.from(text),
58
+ timeout,
59
+ windowsHide: true,
60
+ }).exited;
57
61
  } catch {
58
- await Bun.spawn(["xsel", "--clipboard", "--input"], { stdin: Buffer.from(text), timeout }).exited;
62
+ await Bun.spawn(["xsel", "--clipboard", "--input"], {
63
+ stdin: Buffer.from(text),
64
+ timeout,
65
+ windowsHide: true,
66
+ }).exited;
59
67
  }
60
68
  }
61
69
  } catch (error) {