@oh-my-pi/pi-coding-agent 9.8.0 → 10.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 CHANGED
@@ -2,6 +2,24 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [10.1.0] - 2026-02-01
6
+ ### Added
7
+
8
+ - Added work scheduling profiler to debug menu for analyzing CPU scheduling patterns over the last 30 seconds
9
+ - Added support for work profile data in report bundles including folded stacks, summary, and flamegraph visualization
10
+
11
+ ## [10.0.0] - 2026-02-01
12
+ ### Added
13
+
14
+ - Added `shell` subcommand for interactive shell console testing with brush-core
15
+ - Added `--cwd` / `-C` option to set working directory for shell commands
16
+ - Added `--timeout` / `-t` option to configure per-command timeout in milliseconds
17
+ - Added `--no-snapshot` option to skip sourcing snapshot from user shell
18
+
19
+ ### Fixed
20
+
21
+ - `find` now returns a single match when given a file path instead of failing with "not a directory"
22
+
5
23
  ## [9.8.0] - 2026-02-01
6
24
  ### Breaking Changes
7
25
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@oh-my-pi/pi-coding-agent",
3
- "version": "9.8.0",
3
+ "version": "10.2.0",
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.8.0",
83
- "@oh-my-pi/pi-agent-core": "9.8.0",
84
- "@oh-my-pi/pi-ai": "9.8.0",
85
- "@oh-my-pi/pi-natives": "9.8.0",
86
- "@oh-my-pi/pi-tui": "9.8.0",
87
- "@oh-my-pi/pi-utils": "9.8.0",
82
+ "@oh-my-pi/omp-stats": "10.2.0",
83
+ "@oh-my-pi/pi-agent-core": "10.2.0",
84
+ "@oh-my-pi/pi-ai": "10.2.0",
85
+ "@oh-my-pi/pi-natives": "10.2.0",
86
+ "@oh-my-pi/pi-tui": "10.2.0",
87
+ "@oh-my-pi/pi-utils": "10.2.0",
88
88
  "@openai/agents": "^0.4.4",
89
89
  "@sinclair/typebox": "^0.34.48",
90
90
  "ajv": "^8.17.1",
package/src/cli/args.ts CHANGED
@@ -191,6 +191,7 @@ ${chalk.bold("Subcommands:")}
191
191
  update Check for and install updates
192
192
  config Manage configuration settings
193
193
  setup Install dependencies for optional features
194
+ shell Interactive shell console (brush-core test)
194
195
 
195
196
  ${chalk.bold("Options:")}
196
197
  --model <pattern> Model to use (fuzzy match: "opus", "gpt-5.2", or "p-openai/gpt-5.2")
@@ -0,0 +1,174 @@
1
+ /**
2
+ * Shell CLI command handlers.
3
+ *
4
+ * Handles `omp shell` subcommand for testing the native brush-core shell.
5
+ */
6
+ import * as path from "node:path";
7
+ import { createInterface } from "node:readline/promises";
8
+ import { Shell } from "@oh-my-pi/pi-natives";
9
+ import chalk from "chalk";
10
+ import { APP_NAME } from "../config";
11
+ import { Settings } from "../config/settings";
12
+ import { getOrCreateSnapshot } from "../utils/shell-snapshot";
13
+
14
+ export interface ShellCommandArgs {
15
+ cwd?: string;
16
+ timeoutMs?: number;
17
+ noSnapshot?: boolean;
18
+ }
19
+
20
+ export function parseShellArgs(args: string[]): ShellCommandArgs | undefined {
21
+ if (args.length === 0 || args[0] !== "shell") {
22
+ return undefined;
23
+ }
24
+
25
+ const result: ShellCommandArgs = {};
26
+
27
+ for (let i = 1; i < args.length; i++) {
28
+ const arg = args[i];
29
+ if (arg === "--cwd" || arg === "-C") {
30
+ result.cwd = args[++i];
31
+ } else if (arg === "--timeout" || arg === "-t") {
32
+ const parsed = Number.parseInt(args[++i], 10);
33
+ if (Number.isFinite(parsed)) {
34
+ result.timeoutMs = parsed;
35
+ }
36
+ } else if (arg === "--no-snapshot") {
37
+ result.noSnapshot = true;
38
+ }
39
+ }
40
+
41
+ return result;
42
+ }
43
+
44
+ export async function runShellCommand(cmd: ShellCommandArgs): Promise<void> {
45
+ if (!process.stdin.isTTY) {
46
+ process.stderr.write("Error: shell console requires an interactive TTY.\n");
47
+ process.exit(1);
48
+ }
49
+
50
+ const cwd = cmd.cwd ? path.resolve(cmd.cwd) : process.cwd();
51
+ const settings = await Settings.init({ cwd });
52
+ const { shell, env: shellEnv } = settings.getShellConfig();
53
+ const snapshotPath = cmd.noSnapshot || !shell.includes("bash") ? null : await getOrCreateSnapshot(shell, shellEnv);
54
+ const shellSession = new Shell({ sessionEnv: shellEnv, snapshotPath: snapshotPath ?? undefined });
55
+
56
+ let active = false;
57
+ let lastChar: string | null = null;
58
+
59
+ const rl = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
60
+ const prompt = chalk.cyan(`${APP_NAME} shell> `);
61
+
62
+ const printHelp = () => {
63
+ process.stdout.write(
64
+ `${chalk.bold("Shell Console Commands")}
65
+
66
+ ` +
67
+ `${chalk.bold("Special Commands:")}
68
+ .help Show this help
69
+ .exit, exit Exit the console
70
+
71
+ ` +
72
+ `${chalk.bold("Options:")}
73
+ --cwd, -C <path> Set working directory for commands
74
+ --timeout, -t <ms> Timeout per command in milliseconds
75
+ --no-snapshot Skip sourcing snapshot from user shell
76
+
77
+ ` +
78
+ `${chalk.bold("Notes:")}
79
+ Runs in a persistent brush-core shell session.
80
+ Variables and functions defined in one command persist for the next.
81
+
82
+ `,
83
+ );
84
+ };
85
+
86
+ const interruptHandler = () => {
87
+ if (active) {
88
+ shellSession.abort();
89
+ return;
90
+ }
91
+ rl.close();
92
+ process.exit(0);
93
+ };
94
+
95
+ process.on("SIGINT", interruptHandler);
96
+ process.stdout.write(chalk.dim("Type .help for commands.\n"));
97
+
98
+ try {
99
+ while (true) {
100
+ const line = (await rl.question(prompt)).trim();
101
+ if (!line) {
102
+ continue;
103
+ }
104
+ if (line === ".help") {
105
+ printHelp();
106
+ continue;
107
+ }
108
+ if (line === ".exit" || line === "exit" || line === "quit") {
109
+ break;
110
+ }
111
+
112
+ active = true;
113
+ lastChar = null;
114
+ try {
115
+ const result = await shellSession.run(
116
+ {
117
+ command: line,
118
+ cwd,
119
+ timeoutMs: cmd.timeoutMs,
120
+ },
121
+ (err, chunk) => {
122
+ if (err) {
123
+ process.stderr.write(`${err.message}\n`);
124
+ return;
125
+ }
126
+ if (chunk.length > 0) {
127
+ lastChar = chunk[chunk.length - 1] ?? null;
128
+ }
129
+ process.stdout.write(chunk);
130
+ },
131
+ );
132
+
133
+ if (lastChar && lastChar !== "\n") {
134
+ process.stdout.write("\n");
135
+ }
136
+
137
+ if (result.timedOut) {
138
+ process.stderr.write(chalk.yellow("Command timed out.\n"));
139
+ } else if (result.cancelled) {
140
+ process.stderr.write(chalk.yellow("Command cancelled.\n"));
141
+ } else if (result.exitCode !== 0 && result.exitCode !== undefined) {
142
+ process.stderr.write(chalk.yellow(`Exit code: ${result.exitCode}\n`));
143
+ }
144
+ } catch (err) {
145
+ const message = err instanceof Error ? err.message : String(err);
146
+ process.stderr.write(chalk.red(`Error: ${message}\n`));
147
+ } finally {
148
+ active = false;
149
+ }
150
+ }
151
+ } finally {
152
+ process.off("SIGINT", interruptHandler);
153
+ rl.close();
154
+ }
155
+ }
156
+
157
+ export function printShellHelp(): void {
158
+ process.stdout.write(`${chalk.bold(`${APP_NAME} shell`)} - Interactive shell console for testing
159
+
160
+ ${chalk.bold("Usage:")}
161
+ ${APP_NAME} shell [options]
162
+
163
+ ${chalk.bold("Options:")}
164
+ --cwd, -C <path> Set working directory for commands
165
+ --timeout, -t <ms> Timeout per command in milliseconds
166
+ --no-snapshot Skip sourcing snapshot from user shell
167
+ -h, --help Show this help
168
+
169
+ ${chalk.bold("Examples:")}
170
+ ${APP_NAME} shell
171
+ ${APP_NAME} shell --cwd ./tmp
172
+ ${APP_NAME} shell --timeout 2000
173
+ `);
174
+ }
@@ -4,6 +4,7 @@
4
4
  * Provides tools for debugging, bug report generation, and system diagnostics.
5
5
  */
6
6
  import * as fs from "node:fs/promises";
7
+ import { getWorkProfile } from "@oh-my-pi/pi-natives/work";
7
8
  import { Container, Loader, type SelectItem, SelectList, Spacer, Text } from "@oh-my-pi/pi-tui";
8
9
  import { getSessionsDir } from "../config";
9
10
  import { DynamicBorder } from "../modes/components/dynamic-border";
@@ -17,6 +18,7 @@ import { collectSystemInfo, formatSystemInfo } from "./system-info";
17
18
  const DEBUG_MENU_ITEMS: SelectItem[] = [
18
19
  { value: "open-artifacts", label: "Open: artifact folder", description: "Open session artifacts in file manager" },
19
20
  { value: "performance", label: "Report: performance issue", description: "Profile CPU, reproduce, then bundle" },
21
+ { value: "work", label: "Profile: work scheduling", description: "Open flamegraph of last 30s" },
20
22
  { value: "dump", label: "Report: dump session", description: "Create report bundle immediately" },
21
23
  { value: "memory", label: "Report: memory issue", description: "Heap snapshot + bundle" },
22
24
  { value: "logs", label: "View: recent logs", description: "Show last 50 log entries" },
@@ -69,6 +71,9 @@ export class DebugSelectorComponent extends Container {
69
71
  case "performance":
70
72
  await this.handlePerformanceReport();
71
73
  break;
74
+ case "work":
75
+ await this.handleWorkReport();
76
+ break;
72
77
  case "dump":
73
78
  await this.handleDumpReport();
74
79
  break;
@@ -138,10 +143,12 @@ export class DebugSelectorComponent extends Container {
138
143
 
139
144
  try {
140
145
  const cpuProfile = await session.stop();
146
+ const workProfile = getWorkProfile(30);
141
147
  const result = await createReportBundle({
142
148
  sessionFile: this.ctx.sessionManager.getSessionFile(),
143
149
  settings: this.getResolvedSettings(),
144
150
  cpuProfile,
151
+ workProfile,
145
152
  });
146
153
 
147
154
  loader.stop();
@@ -162,6 +169,39 @@ export class DebugSelectorComponent extends Container {
162
169
  this.ctx.ui.requestRender();
163
170
  }
164
171
 
172
+ private async handleWorkReport(): Promise<void> {
173
+ try {
174
+ const workProfile = getWorkProfile(30);
175
+
176
+ if (!workProfile.svg) {
177
+ this.ctx.showWarning(`No work profile data (${workProfile.sampleCount} samples)`);
178
+ return;
179
+ }
180
+
181
+ // Write SVG to temp file and open in browser
182
+ const tmpPath = `/tmp/work-profile-${Date.now()}.svg`;
183
+ await Bun.write(tmpPath, workProfile.svg);
184
+
185
+ const openCmd =
186
+ process.platform === "darwin"
187
+ ? ["open", tmpPath]
188
+ : process.platform === "win32"
189
+ ? ["cmd", "/c", "start", "", tmpPath]
190
+ : ["xdg-open", tmpPath];
191
+
192
+ Bun.spawn(openCmd, { stdout: "ignore", stderr: "ignore" }).unref();
193
+
194
+ this.ctx.chatContainer.addChild(new Spacer(1));
195
+ this.ctx.chatContainer.addChild(
196
+ new Text(theme.fg("dim", `Opened flamegraph (${workProfile.sampleCount} samples)`), 1, 0),
197
+ );
198
+ } catch (err) {
199
+ this.ctx.showError(`Failed to open profile: ${err instanceof Error ? err.message : String(err)}`);
200
+ }
201
+
202
+ this.ctx.ui.requestRender();
203
+ }
204
+
165
205
  private async handleDumpReport(): Promise<void> {
166
206
  const loader = new Loader(
167
207
  this.ctx.ui,
@@ -6,6 +6,7 @@
6
6
  import * as fs from "node:fs/promises";
7
7
  import * as os from "node:os";
8
8
  import * as path from "node:path";
9
+ import type { WorkProfile } from "@oh-my-pi/pi-natives/work";
9
10
  import { isEnoent } from "@oh-my-pi/pi-utils";
10
11
  import type { CpuProfile, HeapSnapshot } from "./profiler";
11
12
  import { collectSystemInfo, sanitizeEnv } from "./system-info";
@@ -42,6 +43,8 @@ export interface ReportBundleOptions {
42
43
  cpuProfile?: CpuProfile;
43
44
  /** Heap snapshot (for memory reports) */
44
45
  heapSnapshot?: HeapSnapshot;
46
+ /** Work profile (for work scheduling reports) */
47
+ workProfile?: WorkProfile;
45
48
  }
46
49
 
47
50
  export interface ReportBundleResult {
@@ -63,6 +66,9 @@ export interface ReportBundleResult {
63
66
  * - profile.cpuprofile: CPU profile (performance report only)
64
67
  * - profile.md: Markdown CPU profile (performance report only)
65
68
  * - heap.heapsnapshot: Heap snapshot (memory report only)
69
+ * - work.folded: Work profile folded stacks (work report only)
70
+ * - work.md: Work profile summary (work report only)
71
+ * - work.svg: Work profile flamegraph (work report only)
66
72
  */
67
73
  export async function createReportBundle(options: ReportBundleOptions): Promise<ReportBundleResult> {
68
74
  const reportsDir = getReportsDir();
@@ -131,6 +137,18 @@ export async function createReportBundle(options: ReportBundleOptions): Promise<
131
137
  files.push("heap.heapsnapshot");
132
138
  }
133
139
 
140
+ // Work profile
141
+ if (options.workProfile) {
142
+ data["work.folded"] = options.workProfile.folded;
143
+ files.push("work.folded");
144
+ data["work.md"] = options.workProfile.summary;
145
+ files.push("work.md");
146
+ if (options.workProfile.svg) {
147
+ data["work.svg"] = options.workProfile.svg;
148
+ files.push("work.svg");
149
+ }
150
+ }
151
+
134
152
  // Write archive
135
153
  await Bun.Archive.write(outputPath, data, { compress: "gzip" });
136
154
 
@@ -65,8 +65,6 @@ export async function executeBash(command: string, options?: BashExecutorOptions
65
65
  };
66
66
  }
67
67
 
68
- let abortListener: (() => void) | undefined;
69
-
70
68
  try {
71
69
  const sessionKey = buildSessionKey(shell, prefix, snapshotPath, shellEnv, options?.sessionKey);
72
70
  let shellSession = shellSessions.get(sessionKey);
@@ -75,19 +73,13 @@ export async function executeBash(command: string, options?: BashExecutorOptions
75
73
  shellSessions.set(sessionKey, shellSession);
76
74
  }
77
75
 
78
- if (options?.signal) {
79
- abortListener = () => {
80
- shellSession?.abort();
81
- };
82
- options.signal.addEventListener("abort", abortListener, { once: true });
83
- }
84
-
85
76
  const result = await shellSession.run(
86
77
  {
87
78
  command: finalCommand,
88
79
  cwd: options?.cwd,
89
80
  env: options?.env,
90
81
  timeoutMs: options?.timeout,
82
+ signal: options?.signal,
91
83
  },
92
84
  (err, chunk) => {
93
85
  if (!err) {
@@ -127,9 +119,6 @@ export async function executeBash(command: string, options?: BashExecutorOptions
127
119
  };
128
120
  } finally {
129
121
  await pendingChunks;
130
- if (options?.signal && abortListener) {
131
- options.signal.removeEventListener("abort", abortListener);
132
- }
133
122
  }
134
123
  }
135
124
 
package/src/main.ts CHANGED
@@ -20,6 +20,7 @@ import { listModels } from "./cli/list-models";
20
20
  import { parsePluginArgs, printPluginHelp, runPluginCommand } from "./cli/plugin-cli";
21
21
  import { selectSession } from "./cli/session-picker";
22
22
  import { parseSetupArgs, printSetupHelp, runSetupCommand } from "./cli/setup-cli";
23
+ import { parseShellArgs, printShellHelp, runShellCommand } from "./cli/shell-cli";
23
24
  import { parseStatsArgs, printStatsHelp, runStatsCommand } from "./cli/stats-cli";
24
25
  import { parseUpdateArgs, printUpdateHelp, runUpdateCommand } from "./cli/update-cli";
25
26
  import { runCommitCommand } from "./commit";
@@ -558,6 +559,17 @@ export async function main(args: string[]) {
558
559
  return;
559
560
  }
560
561
 
562
+ // Handle shell subcommand (for testing brush-core shell)
563
+ const shellCmd = parseShellArgs(args);
564
+ if (shellCmd) {
565
+ if (args.includes("--help") || args.includes("-h")) {
566
+ printShellHelp();
567
+ return;
568
+ }
569
+ await runShellCommand(shellCmd);
570
+ return;
571
+ }
572
+
561
573
  // Handle commit subcommand
562
574
  const commitCmd = parseCommitArgs(args);
563
575
  if (commitCmd) {
@@ -4,7 +4,7 @@
4
4
  import * as fs from "node:fs/promises";
5
5
  import * as os from "node:os";
6
6
  import * as path from "node:path";
7
- import { getSystemInfo as getNativeSystemInfo, type SystemInfo, find as wasmFind } from "@oh-my-pi/pi-natives";
7
+ import { FileType, getSystemInfo as getNativeSystemInfo, glob, type SystemInfo } from "@oh-my-pi/pi-natives";
8
8
  import { untilAborted } from "@oh-my-pi/pi-utils";
9
9
  import { $ } from "bun";
10
10
  import chalk from "chalk";
@@ -184,10 +184,10 @@ async function scanProjectTreeWithGlob(root: string): Promise<ProjectTreeScan |
184
184
  const timeoutSignal = AbortSignal.timeout(GLOB_TIMEOUT_MS);
185
185
  try {
186
186
  const result = await untilAborted(timeoutSignal, () =>
187
- wasmFind({
187
+ glob({
188
188
  pattern: "**/*",
189
189
  path: root,
190
- fileType: "file",
190
+ fileType: FileType.File,
191
191
  }),
192
192
  );
193
193
  entries = result.matches.map(match => match.path).filter(entry => entry.length > 0);
package/src/tools/find.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs/promises";
2
2
  import * as path from "node:path";
3
3
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
4
- import { type FindMatch, find as wasmFind } from "@oh-my-pi/pi-natives";
4
+ import { FileType, type GlobMatch, glob } from "@oh-my-pi/pi-natives";
5
5
  import type { Component } from "@oh-my-pi/pi-tui";
6
6
  import { Text } from "@oh-my-pi/pi-tui";
7
7
  import { isEnoent, untilAborted } from "@oh-my-pi/pi-utils";
@@ -75,6 +75,11 @@ function parsePatternPath(pattern: string): { basePath: string; globPattern: str
75
75
  return { basePath, globPattern };
76
76
  }
77
77
 
78
+ function hasGlobChars(pattern: string): boolean {
79
+ const globChars = ["*", "?", "[", "{"];
80
+ return globChars.some(char => pattern.includes(char));
81
+ }
82
+
78
83
  export interface FindToolDetails {
79
84
  truncation?: TruncationResult;
80
85
  resultLimitReached?: number;
@@ -94,6 +99,10 @@ export interface FindToolDetails {
94
99
  export interface FindOperations {
95
100
  /** Check if path exists */
96
101
  exists: (absolutePath: string) => Promise<boolean> | boolean;
102
+ /** Optional stat for distinguishing files vs directories. */
103
+ stat?: (
104
+ absolutePath: string,
105
+ ) => Promise<{ isFile(): boolean; isDirectory(): boolean }> | { isFile(): boolean; isDirectory(): boolean };
97
106
  /** Find files matching glob pattern. Returns relative paths. */
98
107
  glob: (pattern: string, cwd: string, options: { ignore: string[]; limit: number }) => Promise<string[]> | string[];
99
108
  }
@@ -136,6 +145,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
136
145
  throw new ToolError("Pattern must not be empty");
137
146
  }
138
147
 
148
+ const hasGlob = hasGlobChars(normalizedPattern);
139
149
  const { basePath, globPattern } = parsePatternPath(normalizedPattern);
140
150
  const searchPath = resolveToCwd(basePath, this.session.cwd);
141
151
 
@@ -161,6 +171,20 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
161
171
  throw new ToolError(`Path not found: ${searchPath}`);
162
172
  }
163
173
 
174
+ if (!hasGlob && this.customOps.stat) {
175
+ const stat = await this.customOps.stat(searchPath);
176
+ if (stat.isFile()) {
177
+ const files = [scopePath];
178
+ const details: FindToolDetails = {
179
+ scopePath,
180
+ fileCount: 1,
181
+ files,
182
+ truncated: false,
183
+ };
184
+ return toolResult(details).text(files.join("\n")).done();
185
+ }
186
+ }
187
+
164
188
  const results = await this.customOps.glob(globPattern, searchPath, {
165
189
  ignore: ["**/node_modules/**", "**/.git/**"],
166
190
  limit: effectiveLimit,
@@ -213,11 +237,22 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
213
237
  }
214
238
  throw err;
215
239
  }
240
+
241
+ if (!hasGlob && searchStat.isFile()) {
242
+ const files = [scopePath];
243
+ const details: FindToolDetails = {
244
+ scopePath,
245
+ fileCount: 1,
246
+ files,
247
+ truncated: false,
248
+ };
249
+ return toolResult(details).text(files.join("\n")).done();
250
+ }
216
251
  if (!searchStat.isDirectory()) {
217
252
  throw new ToolError(`Path is not a directory: ${searchPath}`);
218
253
  }
219
254
 
220
- let matches: Awaited<ReturnType<typeof wasmFind>>["matches"];
255
+ let matches: Awaited<ReturnType<typeof glob>>["matches"];
221
256
  const onUpdateMatches: string[] = [];
222
257
  const updateIntervalMs = 200;
223
258
  let lastUpdate = 0;
@@ -238,11 +273,11 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
238
273
  });
239
274
  };
240
275
  const onMatch = onUpdate
241
- ? (match: FindMatch | null) => {
276
+ ? (match: GlobMatch | null) => {
242
277
  if (signal?.aborted || !match) return;
243
278
  let relativePath = match.path;
244
279
  if (!relativePath) return;
245
- if (match.fileType === "dir" && !relativePath.endsWith("/")) {
280
+ if (match.fileType === FileType.Dir && !relativePath.endsWith("/")) {
246
281
  relativePath += "/";
247
282
  }
248
283
  onUpdateMatches.push(relativePath);
@@ -253,11 +288,11 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
253
288
  const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
254
289
  try {
255
290
  const result = await untilAborted(combinedSignal, () =>
256
- wasmFind(
291
+ glob(
257
292
  {
258
293
  pattern: globPattern,
259
294
  path: searchPath,
260
- fileType: "file",
295
+ fileType: FileType.File,
261
296
  hidden: includeHidden,
262
297
  maxResults: effectiveLimit,
263
298
  sortByMtime: true,
@@ -293,7 +328,7 @@ export class FindTool implements AgentTool<typeof findSchema, FindToolDetails> {
293
328
  const hadTrailingSlash = line.endsWith("/") || line.endsWith("\\");
294
329
  let relativePath = line;
295
330
 
296
- const isDirectory = match.fileType === "dir";
331
+ const isDirectory = match.fileType === FileType.Dir;
297
332
 
298
333
  if ((isDirectory || hadTrailingSlash) && !relativePath.endsWith("/")) {
299
334
  relativePath += "/";
package/src/tools/read.ts CHANGED
@@ -3,7 +3,7 @@ import * as os from "node:os";
3
3
  import path from "node:path";
4
4
  import type { AgentTool, AgentToolContext, AgentToolResult, AgentToolUpdateCallback } from "@oh-my-pi/pi-agent-core";
5
5
  import type { ImageContent, TextContent } from "@oh-my-pi/pi-ai";
6
- import { find as wasmFind } from "@oh-my-pi/pi-natives";
6
+ import { FileType, glob } from "@oh-my-pi/pi-natives";
7
7
  import type { Component } from "@oh-my-pi/pi-tui";
8
8
  import { Text } from "@oh-my-pi/pi-tui";
9
9
  import { ptree, untilAborted } from "@oh-my-pi/pi-utils";
@@ -361,10 +361,10 @@ async function listCandidateFiles(
361
361
  const combinedSignal = signal ? AbortSignal.any([signal, timeoutSignal]) : timeoutSignal;
362
362
  try {
363
363
  const result = await untilAborted(combinedSignal, () =>
364
- wasmFind({
364
+ glob({
365
365
  pattern: "**/*",
366
366
  path: searchRoot,
367
- fileType: "file",
367
+ fileType: FileType.File,
368
368
  hidden: true,
369
369
  }),
370
370
  );