@oh-my-pi/pi-coding-agent 15.2.1 → 15.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,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [15.2.2] - 2026-05-22
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `RULES.md` not being injected. The documented sticky-rules file at `~/.omp/agent/RULES.md` and `<repo>/.omp/RULES.md` was never read by any discovery provider; only `.omp/rules/*.md` was scanned. The native provider now loads both as always-apply rules so they re-attach every turn as documented ([#1266](https://github.com/can1357/oh-my-pi/issues/1266)).
10
+
5
11
  ## [15.2.1] - 2026-05-21
6
12
 
7
13
  ### Fixed
@@ -48,6 +48,8 @@ export interface UserAgentSession {
48
48
  override: UserAgentOverride;
49
49
  browserSession: CDPSession | null;
50
50
  }
51
+ /** Builds the browser-page stealth bootstrap source for regression tests. */
52
+ export declare function buildStealthInjectionScriptForTest(scripts?: readonly string[]): string;
51
53
  /** Apply stealth patches + UA override to a headless page. Idempotent within a tab. */
52
54
  export declare function applyStealthPatches(browser: Browser, page: Page, state: {
53
55
  browserSession: CDPSession | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-coding-agent",
4
- "version": "15.2.1",
4
+ "version": "15.2.2",
5
5
  "description": "Coding agent CLI with read, bash, edit, write tools and session management",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -47,12 +47,12 @@
47
47
  "@agentclientprotocol/sdk": "0.21.0",
48
48
  "@babel/parser": "^7.29.3",
49
49
  "@mozilla/readability": "^0.6.0",
50
- "@oh-my-pi/omp-stats": "15.2.1",
51
- "@oh-my-pi/pi-agent-core": "15.2.1",
52
- "@oh-my-pi/pi-ai": "15.2.1",
53
- "@oh-my-pi/pi-natives": "15.2.1",
54
- "@oh-my-pi/pi-tui": "15.2.1",
55
- "@oh-my-pi/pi-utils": "15.2.1",
50
+ "@oh-my-pi/omp-stats": "15.2.2",
51
+ "@oh-my-pi/pi-agent-core": "15.2.2",
52
+ "@oh-my-pi/pi-ai": "15.2.2",
53
+ "@oh-my-pi/pi-natives": "15.2.2",
54
+ "@oh-my-pi/pi-tui": "15.2.2",
55
+ "@oh-my-pi/pi-utils": "15.2.2",
56
56
  "@puppeteer/browsers": "^2.13.0",
57
57
  "@types/turndown": "5.0.6",
58
58
  "@xterm/headless": "^6.0.0",
@@ -346,9 +346,39 @@ async function loadRules(ctx: LoadContext): Promise<LoadResult<Rule>> {
346
346
  if (result.warnings) warnings.push(...result.warnings);
347
347
  }
348
348
 
349
+ // Top-level RULES.md is a sticky always-apply rule. Documented in
350
+ // https://omp.sh/docs/context-files as the file that gets "re-injected near
351
+ // the current turn so they keep hold across long conversations".
352
+ // User scope: ~/.omp/agent/RULES.md
353
+ // Project scope: nearest .omp/RULES.md walking up from cwd to repoRoot
354
+ const userRulesFile = path.join(ctx.home, PATHS.userAgent, "RULES.md");
355
+ const userRule = await loadStickyRulesFile(userRulesFile, "user");
356
+ if (userRule) items.push(userRule);
357
+
358
+ const nearestProjectConfigDir = await findNearestProjectConfigDir(ctx.cwd, ctx.repoRoot);
359
+ if (nearestProjectConfigDir) {
360
+ const projectRulesFile = path.join(nearestProjectConfigDir.dir, "RULES.md");
361
+ const projectRule = await loadStickyRulesFile(projectRulesFile, "project");
362
+ if (projectRule) items.push(projectRule);
363
+ }
364
+
349
365
  return { items, warnings };
350
366
  }
351
367
 
368
+ /**
369
+ * Read a top-level `RULES.md` and synthesize an always-apply rule.
370
+ * Returns null when the file is absent or empty so callers can short-circuit.
371
+ */
372
+ async function loadStickyRulesFile(filePath: string, level: "user" | "project"): Promise<Rule | null> {
373
+ const content = await readFile(filePath);
374
+ if (!content) return null;
375
+ const source = createSourceMeta(PROVIDER_ID, filePath, level);
376
+ const rule = buildRuleFromMarkdown("RULES.md", content, filePath, source, { ruleName: "RULES" });
377
+ // Force alwaysApply regardless of frontmatter — the whole point of RULES.md
378
+ // is to be reattached every turn.
379
+ return { ...rule, alwaysApply: true };
380
+ }
381
+
352
382
  registerProvider<Rule>(ruleCapability.id, {
353
383
  id: PROVIDER_ID,
354
384
  displayName: DISPLAY_NAME,
@@ -76,6 +76,7 @@ import {
76
76
  } from "@oh-my-pi/pi-ai";
77
77
  import { MacOSPowerAssertion } from "@oh-my-pi/pi-natives";
78
78
  import {
79
+ extractRetryHint,
79
80
  getAgentDbPath,
80
81
  isEnoent,
81
82
  isUnexpectedSocketCloseMessage,
@@ -6949,6 +6950,11 @@ export class AgentSession {
6949
6950
  }
6950
6951
  }
6951
6952
 
6953
+ const retryHintMs = extractRetryHint(undefined, errorMessage);
6954
+ if (retryHintMs !== undefined) {
6955
+ return retryHintMs;
6956
+ }
6957
+
6952
6958
  const resetMsMatch = /x-ratelimit-reset-ms\s*[:=]\s*(\d+)/i.exec(errorMessage);
6953
6959
  if (resetMsMatch) {
6954
6960
  const resetMs = Number(resetMsMatch[1]);
@@ -540,24 +540,24 @@ async function withSoftTimeout<T>(promise: Promise<T>, timeoutMs: number, label:
540
540
  }
541
541
  }
542
542
 
543
- async function injectStealthScripts(page: Page): Promise<void> {
544
- const scripts = [
545
- stealthTamperingScript,
546
- stealthActivityScript,
547
- stealthHairlineScript,
548
- stealthBotdScript,
549
- stealthIframeScript,
550
- stealthWebglScript,
551
- stealthScreenScript,
552
- stealthFontsScript,
553
- stealthAudioScript,
554
- stealthLocaleScript,
555
- stealthPluginsScript,
556
- stealthHardwareScript,
557
- stealthCodecsScript,
558
- stealthWorkerScript,
559
- ];
543
+ const STEALTH_PATCH_SCRIPTS = [
544
+ stealthTamperingScript,
545
+ stealthActivityScript,
546
+ stealthHairlineScript,
547
+ stealthBotdScript,
548
+ stealthIframeScript,
549
+ stealthWebglScript,
550
+ stealthScreenScript,
551
+ stealthFontsScript,
552
+ stealthAudioScript,
553
+ stealthLocaleScript,
554
+ stealthPluginsScript,
555
+ stealthHardwareScript,
556
+ stealthCodecsScript,
557
+ stealthWorkerScript,
558
+ ];
560
559
 
560
+ function buildStealthInjectionScript(scripts: readonly string[] = STEALTH_PATCH_SCRIPTS): string {
561
561
  const joint = scripts
562
562
  .map(
563
563
  script => `
@@ -568,43 +568,55 @@ async function injectStealthScripts(page: Page): Promise<void> {
568
568
  )
569
569
  .join(";\n");
570
570
 
571
- await page.evaluateOnNewDocument(`(() => {
571
+ return `(() => {
572
572
  // Native function cache - captured before any tampering
573
573
  const iframe = document.createElement("iframe");
574
574
  iframe.style.display = "none";
575
- document.head.appendChild(iframe);
576
- const nativeWindow = iframe.contentWindow;
577
- if (!nativeWindow) return;
578
-
579
- // Cache pristine native functions
580
- const Function_toString = nativeWindow.Function.prototype.toString;
581
- const Object_getOwnPropertyDescriptor = nativeWindow.Object.getOwnPropertyDescriptor;
582
- const Object_getOwnPropertyDescriptors = nativeWindow.Object.getOwnPropertyDescriptors;
583
- const Object_getPrototypeOf = nativeWindow.Object.getPrototypeOf;
584
- const Object_defineProperty = nativeWindow.Object.defineProperty;
585
- const Object_getOwnPropertyDescriptorOriginal = nativeWindow.Object.getOwnPropertyDescriptor;
586
- const Object_create = nativeWindow.Object.create;
587
- const Object_keys = nativeWindow.Object.keys;
588
- const Object_getOwnPropertyNames = nativeWindow.Object.getOwnPropertyNames;
589
- const Object_entries = nativeWindow.Object.entries;
590
- const Object_setPrototypeOf = nativeWindow.Object.setPrototypeOf;
591
- const Object_assign = nativeWindow.Object.assign;
592
- const Window_setTimeout = nativeWindow.setTimeout;
593
- const Math_random = nativeWindow.Math.random;
594
- const Math_floor = nativeWindow.Math.floor;
595
- const Math_max = nativeWindow.Math.max;
596
- const Math_min = nativeWindow.Math.min;
597
- const Window_Event = nativeWindow.Event;
598
- const Promise_resolve = nativeWindow.Promise.resolve.bind(nativeWindow.Promise);
599
- const Window_Blob = nativeWindow.Blob;
600
- const Window_Proxy = nativeWindow.Proxy;
601
- const Intl_DateTimeFormat = nativeWindow.Intl.DateTimeFormat;
602
- const Date_constructor = nativeWindow.Date;
603
-
604
-
605
- ${joint}
606
-
607
- document.head.removeChild(iframe);})();`);
575
+ const container = document.head ?? document.documentElement;
576
+ if (!container) return;
577
+ container.appendChild(iframe);
578
+ try {
579
+ const nativeWindow = iframe.contentWindow;
580
+ if (!nativeWindow) return;
581
+
582
+ // Cache pristine native functions
583
+ const Function_toString = nativeWindow.Function.prototype.toString;
584
+ const Object_getOwnPropertyDescriptor = nativeWindow.Object.getOwnPropertyDescriptor;
585
+ const Object_getOwnPropertyDescriptors = nativeWindow.Object.getOwnPropertyDescriptors;
586
+ const Object_getPrototypeOf = nativeWindow.Object.getPrototypeOf;
587
+ const Object_defineProperty = nativeWindow.Object.defineProperty;
588
+ const Object_getOwnPropertyDescriptorOriginal = nativeWindow.Object.getOwnPropertyDescriptor;
589
+ const Object_create = nativeWindow.Object.create;
590
+ const Object_keys = nativeWindow.Object.keys;
591
+ const Object_getOwnPropertyNames = nativeWindow.Object.getOwnPropertyNames;
592
+ const Object_entries = nativeWindow.Object.entries;
593
+ const Object_setPrototypeOf = nativeWindow.Object.setPrototypeOf;
594
+ const Object_assign = nativeWindow.Object.assign;
595
+ const Window_setTimeout = nativeWindow.setTimeout;
596
+ const Math_random = nativeWindow.Math.random;
597
+ const Math_floor = nativeWindow.Math.floor;
598
+ const Math_max = nativeWindow.Math.max;
599
+ const Math_min = nativeWindow.Math.min;
600
+ const Window_Event = nativeWindow.Event;
601
+ const Promise_resolve = nativeWindow.Promise.resolve.bind(nativeWindow.Promise);
602
+ const Window_Blob = nativeWindow.Blob;
603
+ const Window_Proxy = nativeWindow.Proxy;
604
+ const Intl_DateTimeFormat = nativeWindow.Intl.DateTimeFormat;
605
+ const Date_constructor = nativeWindow.Date;
606
+
607
+ ${joint}
608
+ } finally {
609
+ if (iframe.parentNode) iframe.parentNode.removeChild(iframe);
610
+ }})();`;
611
+ }
612
+
613
+ async function injectStealthScripts(page: Page): Promise<void> {
614
+ await page.evaluateOnNewDocument(buildStealthInjectionScript());
615
+ }
616
+
617
+ /** Builds the browser-page stealth bootstrap source for regression tests. */
618
+ export function buildStealthInjectionScriptForTest(scripts: readonly string[] = STEALTH_PATCH_SCRIPTS): string {
619
+ return buildStealthInjectionScript(scripts);
608
620
  }
609
621
 
610
622
  /** Apply stealth patches + UA override to a headless page. Idempotent within a tab. */