@oh-my-pi/pi-coding-agent 14.5.14 → 14.6.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.
Files changed (70) hide show
  1. package/CHANGELOG.md +39 -0
  2. package/package.json +7 -7
  3. package/src/autoresearch/command-resume.md +5 -8
  4. package/src/autoresearch/git.ts +41 -51
  5. package/src/autoresearch/helpers.ts +43 -359
  6. package/src/autoresearch/index.ts +281 -273
  7. package/src/autoresearch/prompt-setup.md +43 -0
  8. package/src/autoresearch/prompt.md +52 -193
  9. package/src/autoresearch/resume-message.md +2 -8
  10. package/src/autoresearch/state.ts +59 -166
  11. package/src/autoresearch/storage.ts +687 -0
  12. package/src/autoresearch/tools/init-experiment.ts +201 -290
  13. package/src/autoresearch/tools/log-experiment.ts +304 -517
  14. package/src/autoresearch/tools/run-experiment.ts +117 -296
  15. package/src/autoresearch/tools/update-notes.ts +116 -0
  16. package/src/autoresearch/types.ts +16 -66
  17. package/src/config/settings-schema.ts +1 -1
  18. package/src/config/settings.ts +20 -1
  19. package/src/cursor.ts +1 -1
  20. package/src/edit/index.ts +9 -31
  21. package/src/edit/line-hash.ts +70 -43
  22. package/src/edit/modes/hashline.lark +26 -0
  23. package/src/edit/modes/hashline.ts +898 -1099
  24. package/src/edit/modes/patch.ts +0 -7
  25. package/src/edit/modes/replace.ts +0 -4
  26. package/src/edit/renderer.ts +22 -20
  27. package/src/edit/streaming.ts +8 -28
  28. package/src/eval/eval.lark +24 -30
  29. package/src/eval/js/context-manager.ts +5 -162
  30. package/src/eval/js/prelude.txt +0 -12
  31. package/src/eval/parse.ts +129 -129
  32. package/src/eval/py/prelude.py +1 -219
  33. package/src/export/html/template.generated.ts +1 -1
  34. package/src/export/html/template.js +2 -2
  35. package/src/internal-urls/docs-index.generated.ts +1 -1
  36. package/src/modes/components/session-observer-overlay.ts +5 -2
  37. package/src/modes/components/status-line/segments.ts +1 -1
  38. package/src/modes/components/status-line.ts +3 -5
  39. package/src/modes/components/tree-selector.ts +4 -5
  40. package/src/modes/components/welcome.ts +11 -1
  41. package/src/modes/controllers/command-controller.ts +2 -6
  42. package/src/modes/controllers/event-controller.ts +1 -2
  43. package/src/modes/controllers/extension-ui-controller.ts +3 -15
  44. package/src/modes/controllers/input-controller.ts +0 -1
  45. package/src/modes/controllers/selector-controller.ts +1 -1
  46. package/src/modes/interactive-mode.ts +5 -7
  47. package/src/prompts/system/system-prompt.md +14 -38
  48. package/src/prompts/tools/ast-edit.md +8 -8
  49. package/src/prompts/tools/ast-grep.md +10 -10
  50. package/src/prompts/tools/eval.md +13 -31
  51. package/src/prompts/tools/find.md +2 -1
  52. package/src/prompts/tools/hashline.md +66 -57
  53. package/src/prompts/tools/search.md +2 -2
  54. package/src/session/session-manager.ts +17 -13
  55. package/src/tools/ast-edit.ts +141 -44
  56. package/src/tools/ast-grep.ts +112 -36
  57. package/src/tools/eval.ts +2 -53
  58. package/src/tools/find.ts +16 -15
  59. package/src/tools/path-utils.ts +36 -196
  60. package/src/tools/search.ts +56 -35
  61. package/src/utils/edit-mode.ts +2 -11
  62. package/src/utils/file-display-mode.ts +1 -1
  63. package/src/utils/git.ts +17 -0
  64. package/src/utils/session-color.ts +0 -12
  65. package/src/utils/title-generator.ts +22 -38
  66. package/src/autoresearch/apply-contract-to-state.ts +0 -24
  67. package/src/autoresearch/contract.ts +0 -288
  68. package/src/edit/modes/atom.lark +0 -29
  69. package/src/edit/modes/atom.ts +0 -1773
  70. package/src/prompts/tools/atom.md +0 -150
@@ -1717,13 +1717,6 @@ export async function executePatchSingle(
1717
1717
  const resolvedPath = resolvePlanPath(session, path);
1718
1718
  const resolvedRename = rename ? resolvePlanPath(session, rename) : undefined;
1719
1719
 
1720
- if (path.endsWith(".ipynb")) {
1721
- throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
1722
- }
1723
- if (rename?.endsWith(".ipynb")) {
1724
- throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
1725
- }
1726
-
1727
1720
  await assertEditableFile(resolvedPath, path);
1728
1721
 
1729
1722
  const input: PatchInput = { path: resolvedPath, op, rename: resolvedRename, diff };
@@ -1026,10 +1026,6 @@ export async function executeReplaceSingle(
1026
1026
 
1027
1027
  enforcePlanModeWrite(session, path);
1028
1028
 
1029
- if (path.endsWith(".ipynb")) {
1030
- throw new Error("Cannot edit Jupyter notebooks with the Edit tool. Use the NotebookEdit tool instead.");
1031
- }
1032
-
1033
1029
  if (old_text.length === 0) {
1034
1030
  throw new Error("old_text must not be empty.");
1035
1031
  }
@@ -106,7 +106,7 @@ type EditRenderEntry = {
106
106
  op?: Operation;
107
107
  };
108
108
 
109
- interface AtomRenderSummary {
109
+ interface HashlineInputRenderSummary {
110
110
  entries: Array<{ path: string }>;
111
111
  }
112
112
 
@@ -309,9 +309,9 @@ function getCallPreview(
309
309
  }
310
310
 
311
311
  const MISSING_APPLY_PATCH_END_ERROR = "The last line of the patch must be '*** End Patch'";
312
- const ATOM_HEADER_PREFIX = "---";
312
+ const HASHLINE_INPUT_HEADER_PREFIX = "@";
313
313
 
314
- function normalizeAtomPreviewPath(rawPath: string): string {
314
+ function normalizeHashlineInputPreviewPath(rawPath: string): string {
315
315
  const trimmed = rawPath.trim();
316
316
  if (trimmed.length < 2) return trimmed;
317
317
  const first = trimmed[0];
@@ -322,30 +322,32 @@ function normalizeAtomPreviewPath(rawPath: string): string {
322
322
  return trimmed;
323
323
  }
324
324
 
325
- function parseAtomPreviewHeader(line: string): string | null {
326
- if (!line.startsWith(ATOM_HEADER_PREFIX)) return null;
327
- let body = line.slice(ATOM_HEADER_PREFIX.length);
328
- if (body.startsWith(" ")) body = body.slice(1);
329
- const previewPath = normalizeAtomPreviewPath(body);
325
+ function parseHashlineInputPreviewHeader(line: string): string | null {
326
+ if (!line.startsWith(HASHLINE_INPUT_HEADER_PREFIX)) return null;
327
+ const body = line.slice(HASHLINE_INPUT_HEADER_PREFIX.length).trim();
328
+ const previewPath = normalizeHashlineInputPreviewPath(body);
330
329
  return previewPath.length > 0 ? previewPath : null;
331
330
  }
332
331
 
333
- function getAtomInputPaths(input: string): string[] {
332
+ function getHashlineInputPaths(input: string): string[] {
334
333
  const stripped = input.startsWith("\uFEFF") ? input.slice(1) : input;
335
334
  const paths: string[] = [];
336
335
  for (const rawLine of stripped.split("\n")) {
337
336
  const line = rawLine.replace(/\r$/, "");
338
- const path = parseAtomPreviewHeader(line);
337
+ const path = parseHashlineInputPreviewHeader(line);
339
338
  if (path) paths.push(path);
340
339
  }
341
340
  return paths;
342
341
  }
343
342
 
344
- function getAtomRenderSummary(args: EditRenderArgs, editMode: EditMode | undefined): AtomRenderSummary | undefined {
345
- if (editMode !== "atom" || typeof args.input !== "string") {
343
+ function getHashlineInputRenderSummary(
344
+ args: EditRenderArgs,
345
+ editMode: EditMode | undefined,
346
+ ): HashlineInputRenderSummary | undefined {
347
+ if (editMode !== "hashline" || typeof args.input !== "string") {
346
348
  return undefined;
347
349
  }
348
- return { entries: getAtomInputPaths(args.input).map(path => ({ path })) };
350
+ return { entries: getHashlineInputPaths(args.input).map(path => ({ path })) };
349
351
  }
350
352
 
351
353
  function getApplyPatchRenderSummary(
@@ -447,10 +449,10 @@ export const editToolRenderer = {
447
449
  }
448
450
 
449
451
  const editArgs = args as EditRenderArgs;
450
- const atomSummary = getAtomRenderSummary(editArgs, renderContext?.editMode);
452
+ const hashlineInputSummary = getHashlineInputRenderSummary(editArgs, renderContext?.editMode);
451
453
  const applyPatchSummary = getApplyPatchRenderSummary(editArgs, options.isPartial, renderContext?.editMode);
452
454
  const firstApplyPatchEntry = applyPatchSummary?.entries[0];
453
- const firstAtomEntry = atomSummary?.entries[0];
455
+ const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
454
456
  // Extract path from first edit entry when top-level path is absent (new schema)
455
457
  const firstEdit = Array.isArray(editArgs.edits) && editArgs.edits.length > 0 ? editArgs.edits[0] : undefined;
456
458
  const rawPath =
@@ -458,7 +460,7 @@ export const editToolRenderer = {
458
460
  editArgs.path ||
459
461
  filePathFromEditEntry(firstEdit?.path) ||
460
462
  getPartialJsonEditPath(editArgs) ||
461
- firstAtomEntry?.path ||
463
+ firstHashlineInputEntry?.path ||
462
464
  firstApplyPatchEntry?.path ||
463
465
  "";
464
466
  const rename = editArgs.rename || firstEdit?.rename || firstEdit?.move || firstApplyPatchEntry?.rename;
@@ -468,7 +470,7 @@ export const editToolRenderer = {
468
470
  options?.spinnerFrame !== undefined ? formatStatusIcon("running", uiTheme, options.spinnerFrame) : "";
469
471
  let text = `${formatTitle(getOperationTitle(op), uiTheme)} ${spinner ? `${spinner} ` : ""}${description}`;
470
472
  // Show file count hint for multi-file edits
471
- let fileCount = atomSummary?.entries.length ?? applyPatchSummary?.entries.length ?? 0;
473
+ let fileCount = hashlineInputSummary?.entries.length ?? applyPatchSummary?.entries.length ?? 0;
472
474
  if (Array.isArray(editArgs.edits)) {
473
475
  fileCount = countEditFiles(editArgs.edits);
474
476
  }
@@ -519,14 +521,14 @@ function renderSingleFileResult(
519
521
  const details = result.details;
520
522
  const isError = result.isError ?? (details && "isError" in details ? details.isError : false);
521
523
  const firstEdit = args?.edits?.[0];
522
- const atomSummary = getAtomRenderSummary(args ?? {}, options.renderContext?.editMode);
523
- const firstAtomEntry = atomSummary?.entries[0];
524
+ const hashlineInputSummary = getHashlineInputRenderSummary(args ?? {}, options.renderContext?.editMode);
525
+ const firstHashlineInputEntry = hashlineInputSummary?.entries[0];
524
526
  const rawPath =
525
527
  args?.file_path ||
526
528
  args?.path ||
527
529
  filePathFromEditEntry(firstEdit?.path) ||
528
530
  (details && "path" in details ? details.path : "") ||
529
- firstAtomEntry?.path ||
531
+ firstHashlineInputEntry?.path ||
530
532
  "";
531
533
  const op = args?.op || firstEdit?.op || details?.op;
532
534
  const rename = args?.rename || firstEdit?.rename || firstEdit?.move || details?.move;
@@ -16,7 +16,7 @@ import type { Theme } from "../modes/theme/theme";
16
16
  import { type EditMode, resolveEditMode } from "../utils/edit-mode";
17
17
  import { computeEditDiff, type DiffError, type DiffResult } from "./diff";
18
18
  import { type ApplyPatchEntry, expandApplyPatchToEntries, expandApplyPatchToPreviewEntries } from "./modes/apply-patch";
19
- import { computeHashlineDiff, type HashlineToolEdit } from "./modes/hashline";
19
+ import { computeHashlineDiff } from "./modes/hashline";
20
20
  import { computePatchDiff, type PatchEditEntry } from "./modes/patch";
21
21
  import type { ReplaceEditEntry } from "./modes/replace";
22
22
 
@@ -210,22 +210,22 @@ const patchStrategy: EditStreamingStrategy<PatchArgs> = {
210
210
  };
211
211
 
212
212
  interface HashlineArgs {
213
+ input?: string;
213
214
  path?: string;
214
- edits?: HashlineToolEdit[];
215
215
  __partialJson?: string;
216
216
  }
217
217
 
218
218
  const hashlineStrategy: EditStreamingStrategy<HashlineArgs> = {
219
- extractCompleteEdits(args, partialJson) {
220
- if (!args?.edits) return args;
221
- return { ...args, edits: dropIncompleteLastEdit(args.edits, partialJson, "edits") };
219
+ extractCompleteEdits(args) {
220
+ return args;
222
221
  },
223
222
  async computeDiffPreview(args, ctx) {
224
- if (!args.path || !args.edits?.length) return null;
223
+ if (typeof args.input !== "string" || args.input.length === 0) return null;
225
224
  ctx.signal.throwIfAborted();
226
- const result = await computeHashlineDiff({ path: args.path, edits: args.edits }, ctx.cwd);
225
+ const result = await computeHashlineDiff({ input: args.input, path: args.path }, ctx.cwd);
227
226
  ctx.signal.throwIfAborted();
228
- return [toPerFilePreview(args.path, result)];
227
+ if ("error" in result && !args.path) return [{ path: "", error: result.error }];
228
+ return [toPerFilePreview(args.path ?? "", result)];
229
229
  },
230
230
  renderStreamingFallback() {
231
231
  return "";
@@ -289,32 +289,12 @@ const vimStrategy: EditStreamingStrategy<unknown> = {
289
289
  },
290
290
  };
291
291
 
292
- interface AtomArgs {
293
- input?: string;
294
- __partialJson?: string;
295
- }
296
-
297
- const atomStrategy: EditStreamingStrategy<AtomArgs> = {
298
- extractCompleteEdits(args) {
299
- return args;
300
- },
301
- async computeDiffPreview() {
302
- // Atom edits can target file headers plus compact diff statements.
303
- // We intentionally avoid speculative parsing while args are partial.
304
- return null;
305
- },
306
- renderStreamingFallback() {
307
- return "";
308
- },
309
- };
310
-
311
292
  export const EDIT_MODE_STRATEGIES: Record<EditMode, EditStreamingStrategy<unknown>> = {
312
293
  replace: replaceStrategy as EditStreamingStrategy<unknown>,
313
294
  patch: patchStrategy as EditStreamingStrategy<unknown>,
314
295
  hashline: hashlineStrategy as EditStreamingStrategy<unknown>,
315
296
  apply_patch: applyPatchStrategy as EditStreamingStrategy<unknown>,
316
297
  vim: vimStrategy,
317
- atom: atomStrategy as EditStreamingStrategy<unknown>,
318
298
  };
319
299
 
320
300
  export { resolveEditMode };
@@ -1,43 +1,37 @@
1
1
  %import common.LF
2
2
  %import common.WS_INLINE
3
3
 
4
- // Strict canonical surface for the eval tool. Callers MUST emit exactly this
5
- // form. The runtime parser accepts additional lenient shapes (positional
6
- // title/duration, alias keys, long-form lang tokens, mixed casing, fence
7
- // runs of any length ≥ 3, etc.) but those are fallback only and MUST NOT
8
- // be relied on.
4
+ // Canonical Eval input. Each cell is introduced by a header line:
9
5
  //
10
- // Each cell is a fenced code block opened and closed by exactly three
11
- // (or exactly five) backticks or tildes — five lets callers nest a 3-char
12
- // fence inside a cell verbatim. The opening fence carries an optional info
13
- // string with up to four parts, IN THIS ORDER:
6
+ // ===== <info> =====
14
7
  //
15
- // lang? id_attr? t_attr? rst_attr?
8
+ // where each side is at least 5 equal signs. The info between the bars is
9
+ // a list of space-separated tokens, all optional, in any order:
16
10
  //
17
- // where:
18
- // lang = "py" | "js" | "ts"
19
- // id_attr = id="..." (double-quoted cell id)
20
- // t_attr = t=<duration> (bare integer with optional ms/s/m unit)
21
- // rst_attr= rst=0|1 (per-language kernel reset for this cell)
11
+ // py | js | ts language for this cell
12
+ // py:"..." | js:"..." | ts:"..." language plus title shorthand
13
+ // id:"..." cell title (when language unchanged)
14
+ // t:<digits>(ms|s|m)? per-cell timeout (default 30s)
15
+ // rst reset this language's kernel before running
16
+ //
17
+ // Everything between one header line and the next (or end of input) is
18
+ // the cell's code, verbatim. The runtime additionally accepts content
19
+ // before the first header as an implicit default-language cell, but that
20
+ // is lenient fallback only and MUST NOT be relied on.
22
21
 
23
22
  start: cell+
23
+ cell: header LF code_line*
24
24
 
25
- cell: backtick_cell | tilde_cell
26
-
27
- backtick_cell: BACKTICKS info? LF code_line* BACKTICKS LF
28
- tilde_cell: TILDES info? LF code_line* TILDES LF
29
-
30
- info: lang (WS_INLINE id_attr)? (WS_INLINE t_attr)? (WS_INLINE rst_attr)?
31
- | id_attr (WS_INLINE t_attr)? (WS_INLINE rst_attr)?
32
- | t_attr (WS_INLINE rst_attr)?
33
- | rst_attr
25
+ header: BAR (WS_INLINE attr)+ WS_INLINE BAR
26
+ | BAR WS_INLINE? BAR
34
27
 
35
- lang: "py" | "js" | "ts"
36
- id_attr: "id=" /"[^"\r\n]*"/
37
- t_attr: "t=" /\d+(ms|s|m)?/
38
- rst_attr: "rst=" /[01]/
28
+ attr: LANG_TITLE | LANG | ID_ATTR | T_ATTR | RST_FLAG
39
29
 
40
30
  code_line: /[^\r\n]*/ LF
41
31
 
42
- BACKTICKS: "```" | "`````"
43
- TILDES: "~~~" | "~~~~~"
32
+ BAR: /={5,}/
33
+ LANG: "py" | "js" | "ts"
34
+ LANG_TITLE: ("py" | "js" | "ts") ":\"" /[^"\r\n]*/ "\""
35
+ ID_ATTR: "id:\"" /[^"\r\n]*/ "\""
36
+ T_ATTR: "t:" /\d+(ms|s|m)?/
37
+ RST_FLAG: "rst"
@@ -24,12 +24,8 @@ interface VmHelperOptions {
24
24
  path?: string;
25
25
  hidden?: boolean;
26
26
  maxDepth?: number;
27
- ignoreCase?: boolean;
28
- literal?: boolean;
29
27
  limit?: number;
30
28
  offset?: number;
31
- globPattern?: string;
32
- flags?: string;
33
29
  reverse?: boolean;
34
30
  unique?: boolean;
35
31
  count?: boolean;
@@ -169,50 +165,6 @@ function clearTrackedTimeout(state: VmContextState, repeat: boolean, timer: Node
169
165
  state.timers.delete(timer);
170
166
  }
171
167
 
172
- async function listFiles(
173
- state: VmContextState,
174
- pattern: string,
175
- searchPath: string,
176
- options: VmHelperOptions,
177
- ): Promise<string[]> {
178
- const resolved = resolvePath(state, searchPath);
179
- const hasRecursivePattern = pattern.includes("**");
180
- const normalizedPattern = hasRecursivePattern ? pattern : `**/${pattern}`;
181
- const matches = await Array.fromAsync(
182
- new Bun.Glob(normalizedPattern).scan({
183
- cwd: resolved,
184
- dot: options.hidden ?? false,
185
- absolute: true,
186
- onlyFiles: false,
187
- }),
188
- );
189
- const limited = matches.slice(0, options.limit ?? 1000).map(match => path.normalize(match));
190
- return limited.sort();
191
- }
192
-
193
- async function grepFile(
194
- filePath: string,
195
- pattern: string,
196
- options: VmHelperOptions,
197
- ): Promise<Array<{ line: number; text: string }>> {
198
- const content = await Bun.file(filePath).text();
199
- const lines = content.split(/\r?\n/);
200
- const matcher = options.literal
201
- ? (line: string) =>
202
- options.ignoreCase ? line.toLowerCase().includes(pattern.toLowerCase()) : line.includes(pattern)
203
- : (line: string) => new RegExp(pattern, options.flags ?? (options.ignoreCase ? "i" : "")).test(line);
204
- const hits: Array<{ line: number; text: string }> = [];
205
- for (let index = 0; index < lines.length; index++) {
206
- if (matcher(lines[index])) {
207
- hits.push({ line: index + 1, text: lines[index] });
208
- if (hits.length >= (options.limit ?? 200)) {
209
- break;
210
- }
211
- }
212
- }
213
- return hits;
214
- }
215
-
216
168
  async function createHelpers(state: VmContextState) {
217
169
  return {
218
170
  read: async (rawPath: string, options: VmHelperOptions = {}): Promise<string> => {
@@ -258,103 +210,6 @@ async function createHelpers(state: VmContextState) {
258
210
  });
259
211
  return target;
260
212
  },
261
- stat: async (
262
- rawPath: string,
263
- ): Promise<{ path: string; size: number; is_file: boolean; is_dir: boolean; mtime: string }> => {
264
- const target = resolvePath(state, rawPath);
265
- const info = await Bun.file(target).stat();
266
- const result = {
267
- path: target,
268
- size: info.size,
269
- is_file: info.isFile(),
270
- is_dir: info.isDirectory(),
271
- mtime: new Date(info.mtimeMs).toISOString(),
272
- };
273
- emitStatus(state, { op: "stat", path: target, size: result.size, is_dir: result.is_dir, mtime: result.mtime });
274
- return result;
275
- },
276
- find: async (pattern: string, searchPath = ".", options: VmHelperOptions = {}): Promise<string[]> => {
277
- const matches = await listFiles(state, pattern, searchPath, options);
278
- emitStatus(state, {
279
- op: "find",
280
- pattern,
281
- path: resolvePath(state, searchPath),
282
- count: matches.length,
283
- matches: matches.slice(0, 20),
284
- });
285
- return matches;
286
- },
287
- glob: async (pattern: string, searchPath = ".", options: VmHelperOptions = {}): Promise<string[]> => {
288
- const resolved = resolvePath(state, searchPath);
289
- const matches = await Array.fromAsync(
290
- new Bun.Glob(pattern).scan({
291
- cwd: resolved,
292
- dot: options.hidden ?? false,
293
- absolute: true,
294
- onlyFiles: false,
295
- }),
296
- );
297
- const limited = matches
298
- .slice(0, options.limit ?? 1000)
299
- .map(match => path.normalize(match))
300
- .sort();
301
- emitStatus(state, {
302
- op: "glob",
303
- pattern,
304
- path: resolved,
305
- count: limited.length,
306
- matches: limited.slice(0, 20),
307
- });
308
- return limited;
309
- },
310
- grep: async (
311
- pattern: string,
312
- rawPath: string,
313
- options: VmHelperOptions = {},
314
- ): Promise<Array<{ line: number; text: string }>> => {
315
- const filePath = resolvePath(state, rawPath);
316
- const hits = await grepFile(filePath, pattern, options);
317
- emitStatus(state, { op: "grep", pattern, path: filePath, count: hits.length, hits: hits.slice(0, 10) });
318
- return hits;
319
- },
320
- rgrep: async (
321
- pattern: string,
322
- searchPath = ".",
323
- options: VmHelperOptions = {},
324
- ): Promise<Array<{ file: string; line: number; text: string }>> => {
325
- const files = await listFiles(state, options.globPattern ?? "*", searchPath, {
326
- ...options,
327
- limit: options.limit ?? 100,
328
- });
329
- const hits: Array<{ file: string; line: number; text: string }> = [];
330
- for (const file of files) {
331
- const fileStat = await Bun.file(file)
332
- .stat()
333
- .catch(() => undefined);
334
- if (!fileStat || fileStat.isDirectory()) continue;
335
- for (const hit of await grepFile(file, pattern, options)) {
336
- hits.push({ file, line: hit.line, text: hit.text });
337
- if (hits.length >= (options.limit ?? 100)) {
338
- emitStatus(state, {
339
- op: "rgrep",
340
- pattern,
341
- path: resolvePath(state, searchPath),
342
- count: hits.length,
343
- hits: hits.slice(0, 10),
344
- });
345
- return hits;
346
- }
347
- }
348
- }
349
- emitStatus(state, {
350
- op: "rgrep",
351
- pattern,
352
- path: resolvePath(state, searchPath),
353
- count: hits.length,
354
- hits: hits.slice(0, 10),
355
- });
356
- return hits;
357
- },
358
213
  sortText: (text: string, options: VmHelperOptions = {}): string => {
359
214
  const lines = String(text).split(/\r?\n/);
360
215
  const deduped = options.unique ? Array.from(new Set(lines)) : lines;
@@ -403,22 +258,6 @@ async function createHelpers(state: VmContextState) {
403
258
  emitStatus(state, { op: "counter", unique: counts.size, total: values.length, top: limited.slice(0, 10) });
404
259
  return limited;
405
260
  },
406
- sed: async (
407
- rawPath: string,
408
- pattern: string,
409
- replacement: string,
410
- options: VmHelperOptions = {},
411
- ): Promise<number> => {
412
- const filePath = resolvePath(state, rawPath);
413
- const content = await Bun.file(filePath).text();
414
- const regex = new RegExp(pattern, options.flags ?? "g");
415
- const matches = content.match(regex);
416
- const updated = content.replace(regex, replacement);
417
- await Bun.write(filePath, updated);
418
- const count = matches?.length ?? 0;
419
- emitStatus(state, { op: "sed", path: filePath, count });
420
- return count;
421
- },
422
261
  diff: async (rawA: string, rawB: string): Promise<string> => {
423
262
  const fileA = resolvePath(state, rawA);
424
263
  const fileB = resolvePath(state, rawB);
@@ -590,7 +429,10 @@ async function createVmState(
590
429
  const context = vm.createContext(contextGlobals);
591
430
  context.globalThis = context;
592
431
  state.context = context;
593
- vm.runInContext(JAVASCRIPT_PRELUDE_SOURCE, context, { filename: "js-prelude.js" });
432
+ vm.runInContext(JAVASCRIPT_PRELUDE_SOURCE, context, {
433
+ filename: "js-prelude.js",
434
+ importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
435
+ });
594
436
  return state;
595
437
  }
596
438
 
@@ -691,6 +533,7 @@ export async function executeInVmContext(options: {
691
533
  const value = vm.runInContext(wrapped.source, state.context, {
692
534
  filename: options.filename,
693
535
  timeout: options.timeoutMs,
536
+ importModuleDynamically: vm.constants.USE_MAIN_CONTEXT_DEFAULT_LOADER,
694
537
  });
695
538
  const awaited = await awaitMaybePromise(value, options.runState.signal);
696
539
  displayValue(state, awaited);
@@ -7,15 +7,9 @@ if (!globalThis.__omp_js_prelude_loaded__) {
7
7
  const read = (path, opts = {}) => callHelper("read", path, toOptions(opts));
8
8
  const write = async (path, data) => callHelper("writeFile", path, data);
9
9
  const append = (path, content) => callHelper("append", path, content);
10
- const stat = path => callHelper("stat", path);
11
- const grep = (pattern, path, opts = {}) => callHelper("grep", pattern, path, toOptions(opts));
12
- const rgrep = (pattern, path = ".", opts = {}) => callHelper("rgrep", pattern, path, toOptions(opts));
13
- const find = (pattern, path = ".", opts = {}) => callHelper("find", pattern, path, toOptions(opts));
14
- const glob = (pattern, path = ".", opts = {}) => callHelper("glob", pattern, path, toOptions(opts));
15
10
  const sort = (text, opts = {}) => callHelper("sortText", text, toOptions(opts));
16
11
  const uniq = (text, opts = {}) => callHelper("uniqText", text, toOptions(opts));
17
12
  const counter = (items, opts = {}) => callHelper("counter", items, toOptions(opts));
18
- const sed = (path, pattern, repl, opts = {}) => callHelper("sed", path, pattern, repl, toOptions(opts));
19
13
  const diff = (a, b) => callHelper("diff", a, b);
20
14
  const tree = (path = ".", opts = {}) => callHelper("tree", path, toOptions(opts));
21
15
  const run = (cmd, opts = {}) => callHelper("run", cmd, toOptions(opts));
@@ -68,15 +62,9 @@ if (!globalThis.__omp_js_prelude_loaded__) {
68
62
  globalThis.read = read;
69
63
  globalThis.write = write;
70
64
  globalThis.append = append;
71
- globalThis.stat = stat;
72
- globalThis.grep = grep;
73
- globalThis.rgrep = rgrep;
74
- globalThis.find = find;
75
- globalThis.glob = glob;
76
65
  globalThis.sort = sort;
77
66
  globalThis.uniq = uniq;
78
67
  globalThis.counter = counter;
79
- globalThis.sed = sed;
80
68
  globalThis.diff = diff;
81
69
  globalThis.tree = tree;
82
70
  globalThis.run = run;