@oh-my-pi/pi-coding-agent 12.17.0 → 12.17.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,25 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [12.17.2] - 2026-02-21
6
+ ### Changed
7
+
8
+ - Modified bash command normalization to only apply explicit head/tail parameters from tool input, removing automatic extraction from command pipes
9
+ - Updated shell snapshot creation to use explicit timeout and kill signal configuration for more reliable process termination
10
+
11
+ ### Fixed
12
+
13
+ - Fixed persistent shell session state not being reset after command abort or hard timeout, preventing stale environment variables from affecting subsequent commands
14
+ - Fixed hard timeout handling to properly interrupt long-running commands that exceed the grace period beyond the configured timeout
15
+
16
+ ## [12.17.1] - 2026-02-21
17
+ ### Added
18
+
19
+ - Added `filterBrowser` option to filter out browser automation MCP servers when builtin browser tool is enabled
20
+ - Added `isBrowserMCPServer()` function to detect browser automation MCP servers by name, URL, or command patterns
21
+ - Added `filterBrowserMCPServers()` function to remove browser MCP servers from loaded configurations
22
+ - Added `BrowserFilterResult` type for browser MCP server filtering results
23
+
5
24
  ## [12.17.0] - 2026-02-21
6
25
  ### Added
7
26
 
package/package.json CHANGED
@@ -1,50 +1,259 @@
1
1
  {
2
+ "type": "module",
2
3
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "12.17.0",
4
+ "version": "12.17.2",
4
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
5
- "type": "module",
6
- "bin": {
7
- "omp": "src/cli.ts"
6
+ "homepage": "https://github.com/can1357/oh-my-pi",
7
+ "author": "Can Bölük",
8
+ "contributors": [
9
+ "Mario Zechner"
10
+ ],
11
+ "license": "MIT",
12
+ "repository": {
13
+ "type": "git",
14
+ "url": "git+https://github.com/can1357/oh-my-pi.git",
15
+ "directory": "packages/coding-agent"
8
16
  },
17
+ "bugs": {
18
+ "url": "https://github.com/can1357/oh-my-pi/issues"
19
+ },
20
+ "keywords": [
21
+ "coding-agent",
22
+ "ai",
23
+ "llm",
24
+ "cli",
25
+ "tui",
26
+ "agent"
27
+ ],
9
28
  "main": "./src/index.ts",
10
29
  "types": "./src/index.ts",
30
+ "bin": {
31
+ "omp": "src/cli.ts"
32
+ },
33
+ "scripts": {
34
+ "check": "tsgo -p tsconfig.json",
35
+ "format-prompts": "bun scripts/format-prompts.ts",
36
+ "generate-docs-index": "bun scripts/generate-docs-index.ts",
37
+ "prepack": "bun scripts/generate-docs-index.ts",
38
+ "build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
39
+ "generate-template": "bun scripts/generate-template.ts",
40
+ "test": "bun test"
41
+ },
42
+ "dependencies": {
43
+ "@mozilla/readability": "0.6.0",
44
+ "@oh-my-pi/omp-stats": "12.17.2",
45
+ "@oh-my-pi/pi-agent-core": "12.17.2",
46
+ "@oh-my-pi/pi-ai": "12.17.2",
47
+ "@oh-my-pi/pi-natives": "12.17.2",
48
+ "@oh-my-pi/pi-tui": "12.17.2",
49
+ "@oh-my-pi/pi-utils": "12.17.2",
50
+ "@sinclair/typebox": "^0.34.48",
51
+ "@xterm/headless": "^6.0.0",
52
+ "ajv": "^8.18.0",
53
+ "chalk": "^5.6.2",
54
+ "diff": "^8.0.3",
55
+ "file-type": "^21.3.0",
56
+ "glob": "^13.0.3",
57
+ "handlebars": "^4.7.8",
58
+ "ignore": "^7.0.5",
59
+ "linkedom": "^0.18.12",
60
+ "marked": "^17.0.2",
61
+ "node-html-parser": "^7.0.2",
62
+ "puppeteer": "^24.37.3",
63
+ "smol-toml": "^1.6.0",
64
+ "zod": "^4.3.6"
65
+ },
66
+ "devDependencies": {
67
+ "@types/bun": "^1.3.9",
68
+ "@types/ms": "^2.1.0",
69
+ "ms": "^2.1.3"
70
+ },
71
+ "engines": {
72
+ "bun": ">=1.3.7"
73
+ },
74
+ "files": [
75
+ "src",
76
+ "scripts",
77
+ "examples",
78
+ "README.md",
79
+ "CHANGELOG.md"
80
+ ],
11
81
  "exports": {
12
82
  ".": {
13
83
  "types": "./src/index.ts",
14
84
  "import": "./src/index.ts"
15
85
  },
16
- "./hooks": {
17
- "types": "./src/extensibility/hooks/index.ts",
18
- "import": "./src/extensibility/hooks/index.ts"
86
+ "./*": {
87
+ "types": "./src/*.ts",
88
+ "import": "./src/*.ts"
19
89
  },
20
- "./sdk": {
21
- "types": "./src/sdk.ts",
22
- "import": "./src/sdk.ts"
90
+ "./capability": {
91
+ "types": "./src/capability/index.ts",
92
+ "import": "./src/capability/index.ts"
23
93
  },
24
- "./prompts/*": "./src/prompts/*.md",
25
94
  "./capability/*": {
26
95
  "types": "./src/capability/*.ts",
27
96
  "import": "./src/capability/*.ts"
28
97
  },
98
+ "./cli/*": {
99
+ "types": "./src/cli/*.ts",
100
+ "import": "./src/cli/*.ts"
101
+ },
102
+ "./commands/*": {
103
+ "types": "./src/commands/*.ts",
104
+ "import": "./src/commands/*.ts"
105
+ },
106
+ "./commit": {
107
+ "types": "./src/commit/index.ts",
108
+ "import": "./src/commit/index.ts"
109
+ },
110
+ "./commit/*": {
111
+ "types": "./src/commit/*.ts",
112
+ "import": "./src/commit/*.ts"
113
+ },
114
+ "./commit/agentic": {
115
+ "types": "./src/commit/agentic/index.ts",
116
+ "import": "./src/commit/agentic/index.ts"
117
+ },
118
+ "./commit/agentic/*": {
119
+ "types": "./src/commit/agentic/*.ts",
120
+ "import": "./src/commit/agentic/*.ts"
121
+ },
122
+ "./commit/agentic/tools": {
123
+ "types": "./src/commit/agentic/tools/index.ts",
124
+ "import": "./src/commit/agentic/tools/index.ts"
125
+ },
126
+ "./commit/agentic/tools/*": {
127
+ "types": "./src/commit/agentic/tools/*.ts",
128
+ "import": "./src/commit/agentic/tools/*.ts"
129
+ },
130
+ "./commit/analysis": {
131
+ "types": "./src/commit/analysis/index.ts",
132
+ "import": "./src/commit/analysis/index.ts"
133
+ },
134
+ "./commit/analysis/*": {
135
+ "types": "./src/commit/analysis/*.ts",
136
+ "import": "./src/commit/analysis/*.ts"
137
+ },
138
+ "./commit/changelog": {
139
+ "types": "./src/commit/changelog/index.ts",
140
+ "import": "./src/commit/changelog/index.ts"
141
+ },
142
+ "./commit/changelog/*": {
143
+ "types": "./src/commit/changelog/*.ts",
144
+ "import": "./src/commit/changelog/*.ts"
145
+ },
146
+ "./commit/git": {
147
+ "types": "./src/commit/git/index.ts",
148
+ "import": "./src/commit/git/index.ts"
149
+ },
150
+ "./commit/git/*": {
151
+ "types": "./src/commit/git/*.ts",
152
+ "import": "./src/commit/git/*.ts"
153
+ },
154
+ "./commit/map-reduce": {
155
+ "types": "./src/commit/map-reduce/index.ts",
156
+ "import": "./src/commit/map-reduce/index.ts"
157
+ },
158
+ "./commit/map-reduce/*": {
159
+ "types": "./src/commit/map-reduce/*.ts",
160
+ "import": "./src/commit/map-reduce/*.ts"
161
+ },
162
+ "./commit/utils/*": {
163
+ "types": "./src/commit/utils/*.ts",
164
+ "import": "./src/commit/utils/*.ts"
165
+ },
29
166
  "./config/*": {
30
167
  "types": "./src/config/*.ts",
31
168
  "import": "./src/config/*.ts"
32
169
  },
170
+ "./debug": {
171
+ "types": "./src/debug/index.ts",
172
+ "import": "./src/debug/index.ts"
173
+ },
174
+ "./debug/*": {
175
+ "types": "./src/debug/*.ts",
176
+ "import": "./src/debug/*.ts"
177
+ },
178
+ "./discovery": {
179
+ "types": "./src/discovery/index.ts",
180
+ "import": "./src/discovery/index.ts"
181
+ },
182
+ "./discovery/*": {
183
+ "types": "./src/discovery/*.ts",
184
+ "import": "./src/discovery/*.ts"
185
+ },
186
+ "./exa": {
187
+ "types": "./src/exa/index.ts",
188
+ "import": "./src/exa/index.ts"
189
+ },
190
+ "./exa/*": {
191
+ "types": "./src/exa/*.ts",
192
+ "import": "./src/exa/*.ts"
193
+ },
194
+ "./exec/*": {
195
+ "types": "./src/exec/*.ts",
196
+ "import": "./src/exec/*.ts"
197
+ },
198
+ "./export/*": {
199
+ "types": "./src/export/*.ts",
200
+ "import": "./src/export/*.ts"
201
+ },
202
+ "./export/html": {
203
+ "types": "./src/export/html/index.ts",
204
+ "import": "./src/export/html/index.ts"
205
+ },
206
+ "./export/html/*": {
207
+ "types": "./src/export/html/*.ts",
208
+ "import": "./src/export/html/*.ts"
209
+ },
33
210
  "./extensibility/*": {
34
211
  "types": "./src/extensibility/*.ts",
35
212
  "import": "./src/extensibility/*.ts"
36
213
  },
37
- "./extensibility/hooks/*": {
38
- "types": "./src/extensibility/hooks/*.ts",
39
- "import": "./src/extensibility/hooks/*.ts"
214
+ "./extensibility/custom-commands": {
215
+ "types": "./src/extensibility/custom-commands/index.ts",
216
+ "import": "./src/extensibility/custom-commands/index.ts"
217
+ },
218
+ "./extensibility/custom-commands/*": {
219
+ "types": "./src/extensibility/custom-commands/*.ts",
220
+ "import": "./src/extensibility/custom-commands/*.ts"
221
+ },
222
+ "./extensibility/custom-commands/bundled/review": {
223
+ "types": "./src/extensibility/custom-commands/bundled/review/index.ts",
224
+ "import": "./src/extensibility/custom-commands/bundled/review/index.ts"
225
+ },
226
+ "./extensibility/custom-tools": {
227
+ "types": "./src/extensibility/custom-tools/index.ts",
228
+ "import": "./src/extensibility/custom-tools/index.ts"
229
+ },
230
+ "./extensibility/custom-tools/*": {
231
+ "types": "./src/extensibility/custom-tools/*.ts",
232
+ "import": "./src/extensibility/custom-tools/*.ts"
233
+ },
234
+ "./extensibility/extensions": {
235
+ "types": "./src/extensibility/extensions/index.ts",
236
+ "import": "./src/extensibility/extensions/index.ts"
40
237
  },
41
238
  "./extensibility/extensions/*": {
42
239
  "types": "./src/extensibility/extensions/*.ts",
43
240
  "import": "./src/extensibility/extensions/*.ts"
44
241
  },
45
- "./session/*": {
46
- "types": "./src/session/*.ts",
47
- "import": "./src/session/*.ts"
242
+ "./extensibility/hooks": {
243
+ "types": "./src/extensibility/hooks/index.ts",
244
+ "import": "./src/extensibility/hooks/index.ts"
245
+ },
246
+ "./extensibility/hooks/*": {
247
+ "types": "./src/extensibility/hooks/*.ts",
248
+ "import": "./src/extensibility/hooks/*.ts"
249
+ },
250
+ "./extensibility/plugins": {
251
+ "types": "./src/extensibility/plugins/index.ts",
252
+ "import": "./src/extensibility/plugins/index.ts"
253
+ },
254
+ "./extensibility/plugins/*": {
255
+ "types": "./src/extensibility/plugins/*.ts",
256
+ "import": "./src/extensibility/plugins/*.ts"
48
257
  },
49
258
  "./internal-urls": {
50
259
  "types": "./src/internal-urls/index.ts",
@@ -54,85 +263,198 @@
54
263
  "types": "./src/internal-urls/*.ts",
55
264
  "import": "./src/internal-urls/*.ts"
56
265
  },
266
+ "./ipy/*": {
267
+ "types": "./src/ipy/*.ts",
268
+ "import": "./src/ipy/*.ts"
269
+ },
270
+ "./lsp": {
271
+ "types": "./src/lsp/index.ts",
272
+ "import": "./src/lsp/index.ts"
273
+ },
274
+ "./lsp/*": {
275
+ "types": "./src/lsp/*.ts",
276
+ "import": "./src/lsp/*.ts"
277
+ },
278
+ "./lsp/clients": {
279
+ "types": "./src/lsp/clients/index.ts",
280
+ "import": "./src/lsp/clients/index.ts"
281
+ },
282
+ "./lsp/clients/*": {
283
+ "types": "./src/lsp/clients/*.ts",
284
+ "import": "./src/lsp/clients/*.ts"
285
+ },
286
+ "./mcp": {
287
+ "types": "./src/mcp/index.ts",
288
+ "import": "./src/mcp/index.ts"
289
+ },
290
+ "./mcp/*": {
291
+ "types": "./src/mcp/*.ts",
292
+ "import": "./src/mcp/*.ts"
293
+ },
294
+ "./mcp/transports": {
295
+ "types": "./src/mcp/transports/index.ts",
296
+ "import": "./src/mcp/transports/index.ts"
297
+ },
298
+ "./mcp/transports/*": {
299
+ "types": "./src/mcp/transports/*.ts",
300
+ "import": "./src/mcp/transports/*.ts"
301
+ },
302
+ "./memories": {
303
+ "types": "./src/memories/index.ts",
304
+ "import": "./src/memories/index.ts"
305
+ },
306
+ "./memories/*": {
307
+ "types": "./src/memories/*.ts",
308
+ "import": "./src/memories/*.ts"
309
+ },
310
+ "./modes": {
311
+ "types": "./src/modes/index.ts",
312
+ "import": "./src/modes/index.ts"
313
+ },
314
+ "./modes/*": {
315
+ "types": "./src/modes/*.ts",
316
+ "import": "./src/modes/*.ts"
317
+ },
318
+ "./modes/components": {
319
+ "types": "./src/modes/components/index.ts",
320
+ "import": "./src/modes/components/index.ts"
321
+ },
322
+ "./modes/components/*": {
323
+ "types": "./src/modes/components/*.ts",
324
+ "import": "./src/modes/components/*.ts"
325
+ },
326
+ "./modes/components/extensions": {
327
+ "types": "./src/modes/components/extensions/index.ts",
328
+ "import": "./src/modes/components/extensions/index.ts"
329
+ },
330
+ "./modes/components/extensions/*": {
331
+ "types": "./src/modes/components/extensions/*.ts",
332
+ "import": "./src/modes/components/extensions/*.ts"
333
+ },
334
+ "./modes/components/status-line": {
335
+ "types": "./src/modes/components/status-line/index.ts",
336
+ "import": "./src/modes/components/status-line/index.ts"
337
+ },
338
+ "./modes/components/status-line/*": {
339
+ "types": "./src/modes/components/status-line/*.ts",
340
+ "import": "./src/modes/components/status-line/*.ts"
341
+ },
342
+ "./modes/controllers/*": {
343
+ "types": "./src/modes/controllers/*.ts",
344
+ "import": "./src/modes/controllers/*.ts"
345
+ },
346
+ "./modes/rpc/*": {
347
+ "types": "./src/modes/rpc/*.ts",
348
+ "import": "./src/modes/rpc/*.ts"
349
+ },
350
+ "./modes/theme/*": {
351
+ "types": "./src/modes/theme/*.ts",
352
+ "import": "./src/modes/theme/*.ts"
353
+ },
354
+ "./modes/theme/defaults": {
355
+ "types": "./src/modes/theme/defaults/index.ts",
356
+ "import": "./src/modes/theme/defaults/index.ts"
357
+ },
358
+ "./modes/utils/*": {
359
+ "types": "./src/modes/utils/*.ts",
360
+ "import": "./src/modes/utils/*.ts"
361
+ },
362
+ "./patch": {
363
+ "types": "./src/patch/index.ts",
364
+ "import": "./src/patch/index.ts"
365
+ },
57
366
  "./patch/*": {
58
367
  "types": "./src/patch/*.ts",
59
368
  "import": "./src/patch/*.ts"
60
369
  },
61
- "./cli": {
62
- "types": "./src/cli.ts",
63
- "import": "./src/cli.ts"
370
+ "./plan-mode/*": {
371
+ "types": "./src/plan-mode/*.ts",
372
+ "import": "./src/plan-mode/*.ts"
64
373
  },
65
- "./*": {
66
- "types": "./src/*.ts",
67
- "import": "./src/*.ts"
374
+ "./prompts/*": "./src/prompts/*.md",
375
+ "./secrets": {
376
+ "types": "./src/secrets/index.ts",
377
+ "import": "./src/secrets/index.ts"
378
+ },
379
+ "./secrets/*": {
380
+ "types": "./src/secrets/*.ts",
381
+ "import": "./src/secrets/*.ts"
382
+ },
383
+ "./session/*": {
384
+ "types": "./src/session/*.ts",
385
+ "import": "./src/session/*.ts"
386
+ },
387
+ "./session/compaction": {
388
+ "types": "./src/session/compaction/index.ts",
389
+ "import": "./src/session/compaction/index.ts"
390
+ },
391
+ "./session/compaction/*": {
392
+ "types": "./src/session/compaction/*.ts",
393
+ "import": "./src/session/compaction/*.ts"
394
+ },
395
+ "./slash-commands/*": {
396
+ "types": "./src/slash-commands/*.ts",
397
+ "import": "./src/slash-commands/*.ts"
398
+ },
399
+ "./ssh/*": {
400
+ "types": "./src/ssh/*.ts",
401
+ "import": "./src/ssh/*.ts"
402
+ },
403
+ "./stt": {
404
+ "types": "./src/stt/index.ts",
405
+ "import": "./src/stt/index.ts"
406
+ },
407
+ "./stt/*": {
408
+ "types": "./src/stt/*.ts",
409
+ "import": "./src/stt/*.ts"
410
+ },
411
+ "./task": {
412
+ "types": "./src/task/index.ts",
413
+ "import": "./src/task/index.ts"
414
+ },
415
+ "./task/*": {
416
+ "types": "./src/task/*.ts",
417
+ "import": "./src/task/*.ts"
418
+ },
419
+ "./tools": {
420
+ "types": "./src/tools/index.ts",
421
+ "import": "./src/tools/index.ts"
422
+ },
423
+ "./tools/*": {
424
+ "types": "./src/tools/*.ts",
425
+ "import": "./src/tools/*.ts"
426
+ },
427
+ "./tui": {
428
+ "types": "./src/tui/index.ts",
429
+ "import": "./src/tui/index.ts"
430
+ },
431
+ "./tui/*": {
432
+ "types": "./src/tui/*.ts",
433
+ "import": "./src/tui/*.ts"
434
+ },
435
+ "./utils/*": {
436
+ "types": "./src/utils/*.ts",
437
+ "import": "./src/utils/*.ts"
438
+ },
439
+ "./web/scrapers": {
440
+ "types": "./src/web/scrapers/index.ts",
441
+ "import": "./src/web/scrapers/index.ts"
442
+ },
443
+ "./web/scrapers/*": {
444
+ "types": "./src/web/scrapers/*.ts",
445
+ "import": "./src/web/scrapers/*.ts"
446
+ },
447
+ "./web/search": {
448
+ "types": "./src/web/search/index.ts",
449
+ "import": "./src/web/search/index.ts"
450
+ },
451
+ "./web/search/*": {
452
+ "types": "./src/web/search/*.ts",
453
+ "import": "./src/web/search/*.ts"
454
+ },
455
+ "./web/search/providers/*": {
456
+ "types": "./src/web/search/providers/*.ts",
457
+ "import": "./src/web/search/providers/*.ts"
68
458
  }
69
- },
70
- "files": [
71
- "src",
72
- "scripts",
73
- "examples",
74
- "README.md",
75
- "CHANGELOG.md"
76
- ],
77
- "scripts": {
78
- "check": "tsgo -p tsconfig.json",
79
- "format-prompts": "bun scripts/format-prompts.ts",
80
- "generate-docs-index": "bun scripts/generate-docs-index.ts",
81
- "prepack": "bun scripts/generate-docs-index.ts",
82
- "build:binary": "cd ../.. && bun --cwd=packages/stats scripts/generate-client-bundle.ts && bun --cwd=packages/natives run embed:native && bun build --compile --define PI_COMPILED=true --root . ./packages/coding-agent/src/cli.ts --outfile packages/coding-agent/dist/omp && bun --cwd=packages/natives run embed:native --reset && bun --cwd=packages/stats scripts/generate-client-bundle.ts --reset",
83
- "generate-template": "bun scripts/generate-template.ts",
84
- "test": "bun test"
85
- },
86
- "dependencies": {
87
- "@mozilla/readability": "0.6.0",
88
- "@oh-my-pi/omp-stats": "12.17.0",
89
- "@oh-my-pi/pi-agent-core": "12.17.0",
90
- "@oh-my-pi/pi-ai": "12.17.0",
91
- "@oh-my-pi/pi-natives": "12.17.0",
92
- "@oh-my-pi/pi-tui": "12.17.0",
93
- "@oh-my-pi/pi-utils": "12.17.0",
94
- "@sinclair/typebox": "^0.34.48",
95
- "@xterm/headless": "^6.0.0",
96
- "ajv": "^8.18.0",
97
- "chalk": "^5.6.2",
98
- "diff": "^8.0.3",
99
- "file-type": "^21.3.0",
100
- "glob": "^13.0.3",
101
- "handlebars": "^4.7.8",
102
- "ignore": "^7.0.5",
103
- "linkedom": "^0.18.12",
104
- "marked": "^17.0.2",
105
- "node-html-parser": "^7.0.2",
106
- "puppeteer": "^24.37.3",
107
- "smol-toml": "^1.6.0",
108
- "zod": "^4.3.6"
109
- },
110
- "devDependencies": {
111
- "@types/bun": "^1.3.9",
112
- "@types/ms": "^2.1.0",
113
- "ms": "^2.1.3"
114
- },
115
- "keywords": [
116
- "coding-agent",
117
- "ai",
118
- "llm",
119
- "cli",
120
- "tui",
121
- "agent"
122
- ],
123
- "author": "Can Bölük",
124
- "contributors": ["Mario Zechner"],
125
- "license": "MIT",
126
- "repository": {
127
- "type": "git",
128
- "url": "git+https://github.com/can1357/oh-my-pi.git",
129
- "directory": "packages/coding-agent"
130
- },
131
- "homepage": "https://github.com/can1357/oh-my-pi",
132
- "bugs": {
133
- "url": "https://github.com/can1357/oh-my-pi/issues"
134
- },
135
- "engines": {
136
- "bun": ">=1.3.7"
137
- }
459
+ }
138
460
  }
@@ -85,9 +85,9 @@ function formatPercent(n: number): string {
85
85
 
86
86
  export async function runStatsCommand(cmd: StatsCommandArgs): Promise<void> {
87
87
  // Lazy import to avoid loading stats module when not needed
88
- const { getDashboardStats, syncAllSessions, getTotalMessageCount } = await import("@oh-my-pi/omp-stats");
89
- const { startServer } = await import("@oh-my-pi/omp-stats/src/server");
90
- const { closeDb } = await import("@oh-my-pi/omp-stats/src/db");
88
+ const { getDashboardStats, syncAllSessions, getTotalMessageCount, startServer, closeDb } = await import(
89
+ "@oh-my-pi/omp-stats"
90
+ );
91
91
 
92
92
  // Sync session files first
93
93
  console.log("Syncing session files...");
@@ -34,6 +34,8 @@ export interface BashResult {
34
34
  artifactId?: string;
35
35
  }
36
36
 
37
+ const HARD_TIMEOUT_GRACE_MS = 5_000;
38
+
37
39
  const shellSessions = new Map<string, Shell>();
38
40
 
39
41
  export async function executeBash(command: string, options?: BashExecutorOptions): Promise<BashResult> {
@@ -65,74 +67,106 @@ export async function executeBash(command: string, options?: BashExecutorOptions
65
67
  };
66
68
  }
67
69
 
70
+ const sessionKey = buildSessionKey(shell, prefix, snapshotPath, shellEnv, options?.sessionKey);
71
+ let shellSession = shellSessions.get(sessionKey);
72
+ if (!shellSession) {
73
+ shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined });
74
+ shellSessions.set(sessionKey, shellSession);
75
+ }
76
+ const signal = options?.signal;
77
+ const abortHandler = () => {
78
+ shellSession.abort(signal?.reason instanceof Error ? signal.reason.message : undefined);
79
+ };
80
+ if (signal) {
81
+ signal.addEventListener("abort", abortHandler, { once: true });
82
+ }
83
+
84
+ let hardTimeoutTimer: NodeJS.Timeout | undefined;
85
+ const hardTimeoutDeferred = Promise.withResolvers<"hard-timeout">();
86
+ const baseTimeoutMs = Math.max(1_000, options?.timeout ?? 300_000);
87
+ const hardTimeoutMs = baseTimeoutMs + HARD_TIMEOUT_GRACE_MS;
88
+ hardTimeoutTimer = setTimeout(() => {
89
+ shellSession.abort(`Hard timeout after ${Math.round(hardTimeoutMs / 1000)}s`);
90
+ hardTimeoutDeferred.resolve("hard-timeout");
91
+ }, hardTimeoutMs);
92
+
93
+ let resetSession = false;
94
+
68
95
  try {
69
- const sessionKey = buildSessionKey(shell, prefix, snapshotPath, shellEnv, options?.sessionKey);
70
- let shellSession = shellSessions.get(sessionKey);
71
- if (!shellSession) {
72
- shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined });
73
- shellSessions.set(sessionKey, shellSession);
96
+ const runPromise = shellSession.run(
97
+ {
98
+ command: finalCommand,
99
+ cwd: options?.cwd,
100
+ env: options?.env,
101
+ timeoutMs: options?.timeout,
102
+ signal,
103
+ },
104
+ (err, chunk) => {
105
+ if (!err) {
106
+ enqueueChunk(chunk);
107
+ }
108
+ },
109
+ );
110
+
111
+ const winner = await Promise.race([
112
+ runPromise.then(result => ({ kind: "result" as const, result })),
113
+ hardTimeoutDeferred.promise.then(() => ({ kind: "hard-timeout" as const })),
114
+ ]);
115
+
116
+ await pendingChunks;
117
+
118
+ if (winner.kind === "hard-timeout") {
119
+ resetSession = true;
120
+ return {
121
+ exitCode: undefined,
122
+ cancelled: true,
123
+ ...(await sink.dump(`Command exceeded hard timeout after ${Math.round(hardTimeoutMs / 1000)} seconds`)),
124
+ };
74
125
  }
75
126
 
76
- const signal = options?.signal;
77
- const abortHandler = () => {
78
- shellSession.abort(signal?.reason instanceof Error ? signal.reason.message : undefined);
79
- };
80
- if (signal) {
81
- signal.addEventListener("abort", abortHandler, { once: true });
127
+ // Handle timeout
128
+ if (winner.result.timedOut) {
129
+ const annotation = options?.timeout
130
+ ? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
131
+ : "Command timed out";
132
+ resetSession = true;
133
+ return {
134
+ exitCode: undefined,
135
+ cancelled: true,
136
+ ...(await sink.dump(annotation)),
137
+ };
82
138
  }
83
139
 
84
- try {
85
- const result = await shellSession.run(
86
- {
87
- command: finalCommand,
88
- cwd: options?.cwd,
89
- env: options?.env,
90
- timeoutMs: options?.timeout,
91
- signal,
92
- },
93
- (err, chunk) => {
94
- if (!err) {
95
- enqueueChunk(chunk);
96
- }
97
- },
98
- );
99
-
100
- await pendingChunks;
101
-
102
- // Handle timeout
103
- if (result.timedOut) {
104
- const annotation = options?.timeout
105
- ? `Command timed out after ${Math.round(options.timeout / 1000)} seconds`
106
- : "Command timed out";
107
- return {
108
- exitCode: undefined,
109
- cancelled: true,
110
- ...(await sink.dump(annotation)),
111
- };
112
- }
113
-
114
- // Handle cancellation
115
- if (result.cancelled) {
116
- return {
117
- exitCode: undefined,
118
- cancelled: true,
119
- ...(await sink.dump("Command cancelled")),
120
- };
121
- }
122
-
123
- // Normal completion
140
+ // Handle cancellation
141
+ if (winner.result.cancelled) {
142
+ resetSession = true;
124
143
  return {
125
- exitCode: result.exitCode,
126
- cancelled: false,
127
- ...(await sink.dump()),
144
+ exitCode: undefined,
145
+ cancelled: true,
146
+ ...(await sink.dump("Command cancelled")),
128
147
  };
129
- } finally {
130
- if (signal) {
131
- signal.removeEventListener("abort", abortHandler);
132
- }
133
148
  }
149
+
150
+ // Normal completion
151
+ return {
152
+ exitCode: winner.result.exitCode,
153
+ cancelled: false,
154
+ ...(await sink.dump()),
155
+ };
156
+ } catch (err) {
157
+ resetSession = true;
158
+ throw err;
134
159
  } finally {
160
+ if (hardTimeoutTimer) {
161
+ clearTimeout(hardTimeoutTimer);
162
+ }
163
+ if (signal) {
164
+ signal.removeEventListener("abort", abortHandler);
165
+ }
135
166
  await pendingChunks;
167
+ if (resetSession) {
168
+ shellSessions.delete(sessionKey);
169
+ }
136
170
  }
137
171
  }
138
172
 
package/src/mcp/config.ts CHANGED
@@ -18,6 +18,8 @@ export interface LoadMCPConfigsOptions {
18
18
  enableProjectConfig?: boolean;
19
19
  /** Whether to filter out Exa MCP servers (default: true) */
20
20
  filterExa?: boolean;
21
+ /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
22
+ filterBrowser?: boolean;
21
23
  }
22
24
 
23
25
  /** Result of loading MCP configs */
@@ -91,6 +93,7 @@ function convertToLegacyConfig(server: MCPServer): MCPServerConfig {
91
93
  export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOptions): Promise<LoadMCPConfigsResult> {
92
94
  const enableProjectConfig = options?.enableProjectConfig ?? true;
93
95
  const filterExa = options?.filterExa ?? true;
96
+ const filterBrowser = options?.filterBrowser ?? false;
94
97
 
95
98
  // Load MCP servers via capability system
96
99
  const result = await loadCapability<MCPServer>(mcpCapability.id, { cwd });
@@ -103,8 +106,8 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
103
106
  // Load user-level disabled servers list
104
107
  const disabledServers = new Set(await readDisabledServers(getMCPConfigPath("user", cwd)));
105
108
  // Convert to legacy format and preserve source metadata
106
- const configs: Record<string, MCPServerConfig> = {};
107
- const sources: Record<string, SourceMeta> = {};
109
+ let configs: Record<string, MCPServerConfig> = {};
110
+ let sources: Record<string, SourceMeta> = {};
108
111
  for (const server of servers) {
109
112
  const config = convertToLegacyConfig(server);
110
113
  if (config.enabled === false || (server._source.level !== "user" && disabledServers.has(server.name))) {
@@ -114,11 +117,19 @@ export async function loadAllMCPConfigs(cwd: string, options?: LoadMCPConfigsOpt
114
117
  sources[server.name] = server._source;
115
118
  }
116
119
 
117
- const exaApiKeys: string[] = [];
120
+ let exaApiKeys: string[] = [];
118
121
 
119
122
  if (filterExa) {
120
- const filterResult = filterExaMCPServers(configs, sources);
121
- return { configs: filterResult.configs, exaApiKeys: filterResult.exaApiKeys, sources: filterResult.sources };
123
+ const exaResult = filterExaMCPServers(configs, sources);
124
+ configs = exaResult.configs;
125
+ sources = exaResult.sources;
126
+ exaApiKeys = exaResult.exaApiKeys;
127
+ }
128
+
129
+ if (filterBrowser) {
130
+ const browserResult = filterBrowserMCPServers(configs, sources);
131
+ configs = browserResult.configs;
132
+ sources = browserResult.sources;
122
133
  }
123
134
 
124
135
  return { configs, exaApiKeys, sources };
@@ -264,3 +275,89 @@ export function validateServerConfig(name: string, config: MCPServerConfig): str
264
275
 
265
276
  return errors;
266
277
  }
278
+
279
+ /** Known browser automation MCP server names (lowercase) */
280
+ const BROWSER_MCP_NAMES = new Set([
281
+ "puppeteer",
282
+ "playwright",
283
+ "browserbase",
284
+ "browser-tools",
285
+ "browser-use",
286
+ "browser",
287
+ ]);
288
+
289
+ /** Patterns matching browser MCP package names in command/args */
290
+ const BROWSER_MCP_PKG_PATTERN =
291
+ // Official packages
292
+ // - @modelcontextprotocol/server-puppeteer
293
+ // - @playwright/mcp
294
+ // - @browserbasehq/mcp-server-browserbase
295
+ // - @agentdeskai/browser-tools-mcp
296
+ // - @agent-infra/mcp-server-browser
297
+ // Community packages: puppeteer-mcp-server, playwright-mcp, pptr-mcp, etc.
298
+ /(?:@modelcontextprotocol\/server-puppeteer|@playwright\/mcp|@browserbasehq\/mcp-server-browserbase|@agentdeskai\/browser-tools-mcp|@agent-infra\/mcp-server-browser|puppeteer-mcp|playwright-mcp|pptr-mcp|browser-use-mcp|mcp-browser-use)/i;
299
+
300
+ /** URL patterns for hosted browser MCP services */
301
+ const BROWSER_MCP_URL_PATTERN = /browserbase\.com|browser-use\.com/i;
302
+
303
+ /**
304
+ * Check if a server config is a browser automation MCP server.
305
+ */
306
+ export function isBrowserMCPServer(name: string, config: MCPServerConfig): boolean {
307
+ // Check by server name
308
+ if (BROWSER_MCP_NAMES.has(name.toLowerCase())) {
309
+ return true;
310
+ }
311
+
312
+ // Check by URL for HTTP/SSE servers
313
+ if (config.type === "http" || config.type === "sse") {
314
+ const httpConfig = config as { url?: string };
315
+ if (httpConfig.url && BROWSER_MCP_URL_PATTERN.test(httpConfig.url)) {
316
+ return true;
317
+ }
318
+ }
319
+
320
+ // Check by command/args for stdio servers
321
+ if (!config.type || config.type === "stdio") {
322
+ const stdioConfig = config as { command?: string; args?: string[] };
323
+ if (stdioConfig.command && BROWSER_MCP_PKG_PATTERN.test(stdioConfig.command)) {
324
+ return true;
325
+ }
326
+ if (stdioConfig.args?.some(arg => BROWSER_MCP_PKG_PATTERN.test(arg))) {
327
+ return true;
328
+ }
329
+ }
330
+
331
+ return false;
332
+ }
333
+
334
+ /** Result of filtering browser MCP servers */
335
+ export interface BrowserFilterResult {
336
+ /** Configs with browser servers removed */
337
+ configs: Record<string, MCPServerConfig>;
338
+ /** Source metadata for remaining servers */
339
+ sources: Record<string, SourceMeta>;
340
+ }
341
+
342
+ /**
343
+ * Filter out browser automation MCP servers.
344
+ * Since we have a native browser tool, we don't need these MCP servers.
345
+ */
346
+ export function filterBrowserMCPServers(
347
+ configs: Record<string, MCPServerConfig>,
348
+ sources: Record<string, SourceMeta>,
349
+ ): BrowserFilterResult {
350
+ const filtered: Record<string, MCPServerConfig> = {};
351
+ const filteredSources: Record<string, SourceMeta> = {};
352
+
353
+ for (const [name, config] of Object.entries(configs)) {
354
+ if (!isBrowserMCPServer(name, config)) {
355
+ filtered[name] = config;
356
+ if (sources[name]) {
357
+ filteredSources[name] = sources[name];
358
+ }
359
+ }
360
+ }
361
+
362
+ return { configs: filtered, sources: filteredSources };
363
+ }
package/src/mcp/index.ts CHANGED
@@ -8,10 +8,12 @@
8
8
  // Client
9
9
  export { callTool, connectToServer, disconnectServer, listTools, serverSupportsTools } from "./client";
10
10
  // Config
11
- export type { ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config";
11
+ export type { BrowserFilterResult, ExaFilterResult, LoadMCPConfigsOptions, LoadMCPConfigsResult } from "./config";
12
12
  export {
13
13
  extractExaApiKey,
14
+ filterBrowserMCPServers,
14
15
  filterExaMCPServers,
16
+ isBrowserMCPServer,
15
17
  isExaMCPServer,
16
18
  loadAllMCPConfigs,
17
19
  validateServerConfig,
package/src/mcp/loader.ts CHANGED
@@ -32,6 +32,8 @@ export interface MCPToolsLoadOptions {
32
32
  enableProjectConfig?: boolean;
33
33
  /** Whether to filter out Exa MCP servers (default: true) */
34
34
  filterExa?: boolean;
35
+ /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
36
+ filterBrowser?: boolean;
35
37
  /** SQLite storage for MCP tool cache (null disables cache) */
36
38
  cacheStorage?: AgentStorage | null;
37
39
  /** Auth storage used to resolve OAuth credentials before initial MCP connect */
@@ -69,6 +71,7 @@ export async function discoverAndLoadMCPTools(cwd: string, options?: MCPToolsLoa
69
71
  onConnecting: options?.onConnecting,
70
72
  enableProjectConfig: options?.enableProjectConfig,
71
73
  filterExa: options?.filterExa,
74
+ filterBrowser: options?.filterBrowser,
72
75
  });
73
76
  } catch (error) {
74
77
  // If discovery fails entirely, return empty result
@@ -68,6 +68,8 @@ export interface MCPDiscoverOptions {
68
68
  enableProjectConfig?: boolean;
69
69
  /** Whether to filter out Exa MCP servers (default: true) */
70
70
  filterExa?: boolean;
71
+ /** Whether to filter out browser MCP servers when builtin browser tool is enabled (default: false) */
72
+ filterBrowser?: boolean;
71
73
  /** Called when starting to connect to servers */
72
74
  onConnecting?: (serverNames: string[]) => void;
73
75
  }
@@ -105,6 +107,7 @@ export class MCPManager {
105
107
  const { configs, exaApiKeys, sources } = await loadAllMCPConfigs(this.cwd, {
106
108
  enableProjectConfig: options?.enableProjectConfig,
107
109
  filterExa: options?.filterExa,
110
+ filterBrowser: options?.filterBrowser,
108
111
  });
109
112
  const result = await this.connectServers(configs, sources, options?.onConnecting);
110
113
  result.exaApiKeys = exaApiKeys;
package/src/sdk.ts CHANGED
@@ -806,6 +806,8 @@ export async function createAgentSession(options: CreateAgentSessionOptions = {}
806
806
  enableProjectConfig: settings.get("mcp.enableProjectConfig") ?? true,
807
807
  // Always filter Exa - we have native integration
808
808
  filterExa: true,
809
+ // Filter browser MCP servers when builtin browser tool is active
810
+ filterBrowser: (settings.get("browser.enabled") as boolean) ?? false,
809
811
  cacheStorage: settings.getStorage(),
810
812
  authStorage,
811
813
  });
@@ -61,6 +61,7 @@ export async function loadGitContext(cwd: string): Promise<GitContext | null> {
61
61
  stdout: "pipe",
62
62
  stderr: "ignore",
63
63
  timeout: timeout,
64
+ killSignal: "SIGKILL",
64
65
  });
65
66
  return untilAborted(abortSignal, async () => {
66
67
  const exitCode = await proc.exited;
package/src/tools/bash.ts CHANGED
@@ -17,7 +17,7 @@ import { CachedOutputBlock } from "../tui/output-block";
17
17
  import type { ToolSession } from ".";
18
18
  import { type BashInteractiveResult, runInteractiveBashPty } from "./bash-interactive";
19
19
  import { checkBashInterception } from "./bash-interceptor";
20
- import { applyHeadTail, normalizeBashCommand } from "./bash-normalize";
20
+ import { applyHeadTail } from "./bash-normalize";
21
21
  import { expandInternalUrls } from "./bash-skill-urls";
22
22
  import type { OutputMeta } from "./output-meta";
23
23
  import { allocateOutputArtifact, createTailBuffer } from "./output-utils";
@@ -75,13 +75,11 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
75
75
  onUpdate?: AgentToolUpdateCallback<BashToolDetails>,
76
76
  ctx?: AgentToolContext,
77
77
  ): Promise<AgentToolResult<BashToolDetails>> {
78
- // Normalize command: strip head/tail pipes and 2>&1
79
- const normalized = normalizeBashCommand(rawCommand);
80
- let command = normalized.command;
78
+ let command = rawCommand;
81
79
 
82
- // Merge explicit params with extracted ones (explicit takes precedence)
83
- const headLines = head ?? normalized.headLines;
84
- const tailLines = tail ?? normalized.tailLines;
80
+ // Only apply explicit head/tail params from tool input.
81
+ const headLines = head;
82
+ const tailLines = tail;
85
83
 
86
84
  // Check interception if enabled and available tools are known
87
85
  if (this.session.settings.get("bashInterceptor.enabled")) {
@@ -140,6 +138,7 @@ export class BashTool implements AgentTool<typeof bashSchema, BashToolDetails> {
140
138
  })
141
139
  : await executeBash(command, {
142
140
  cwd: commandCwd,
141
+ sessionKey: this.session.getSessionId?.() ?? undefined,
143
142
  timeout: timeoutMs,
144
143
  signal,
145
144
  env: extraEnv,
@@ -9,9 +9,9 @@ import * as fs from "node:fs";
9
9
  import * as os from "node:os";
10
10
  import * as path from "node:path";
11
11
  import { postmortem } from "@oh-my-pi/pi-utils";
12
- import { $ } from "bun";
13
12
 
14
13
  const cachedSnapshotPaths = new Map<string, string>();
14
+ const SNAPSHOT_TIMEOUT_MS = 2_000;
15
15
 
16
16
  function sanitizeSnapshotEnv(env: Record<string, string | undefined>): Record<string, string | undefined> {
17
17
  const sanitized = { ...env };
@@ -153,8 +153,23 @@ export async function getOrCreateSnapshot(
153
153
 
154
154
  try {
155
155
  const snapshotEnv = sanitizeSnapshotEnv(env);
156
- await $`${shell} -c ${script}`.env(snapshotEnv).quiet().text();
157
- if (fs.existsSync(snapshotPath)) {
156
+ const spawnEnv: Record<string, string> = {};
157
+ for (const [key, value] of Object.entries(snapshotEnv)) {
158
+ if (value !== undefined) {
159
+ spawnEnv[key] = value;
160
+ }
161
+ }
162
+ const child = Bun.spawn([shell, "-c", script], {
163
+ env: spawnEnv,
164
+ stdin: "ignore",
165
+ stdout: "ignore",
166
+ stderr: "ignore",
167
+ timeout: SNAPSHOT_TIMEOUT_MS,
168
+ killSignal: "SIGKILL",
169
+ });
170
+
171
+ await child.exited;
172
+ if (child.exitCode === 0 && fs.existsSync(snapshotPath)) {
158
173
  cachedSnapshotPaths.set(cacheKey, snapshotPath);
159
174
  return snapshotPath;
160
175
  }