omnirush 0.10.3 → 0.10.5

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.
@@ -199,11 +199,18 @@ export const ROLE_PRESETS: Record<AgentRole, RolePreset> = {
199
199
  },
200
200
  };
201
201
 
202
- /** Resolve the child entry: this running script + the current runtime. */
203
- export function childInvocation(args: string[]): {
202
+ /**
203
+ * Resolve the child entry: the core's entry as the launcher started it
204
+ * (OMNIRUSH_CORE_ENTRY: through <package>/engine, so process listings name
205
+ * omnirush; the runtime reports the resolved path in argv[1]), else this
206
+ * running script, with the current runtime.
207
+ */
208
+ export function childInvocation(args: string[], env: NodeJS.ProcessEnv = process.env): {
204
209
  command: string;
205
210
  args: string[];
206
211
  } {
212
+ const launched = (env.OMNIRUSH_CORE_ENTRY || "").trim();
213
+ if (launched && existsSync(launched)) return { command: process.execPath, args: [launched, ...args] };
207
214
  const currentScript = process.argv[1];
208
215
  const isBunVirtualScript = currentScript?.startsWith("/$bunfs/root/");
209
216
  if (currentScript && !isBunVirtualScript && existsSync(currentScript)) {
@@ -214,8 +221,8 @@ export function childInvocation(args: string[]): {
214
221
  if (!isGenericRuntime) {
215
222
  return { command: process.execPath, args };
216
223
  }
217
- // Packaged bun builds: fall back to the pi on PATH.
218
- return { command: "pi", args };
224
+ // Packaged bun builds: fall back to the omnirush on PATH (it forwards core flags).
225
+ return { command: "omnirush", args };
219
226
  }
220
227
 
221
228
  /** The folder, inside a session dir, that holds its sub-agents' sessions. */
@@ -20,7 +20,7 @@
20
20
  import { readSessionLedger, SESSION_LEDGER_FILE } from "./capture/workspace-sync";
21
21
  import { deviceMe, omniDir, resolveOrigin, sessionEndedMessage } from "./auth";
22
22
  import { sharedRefresher } from "./refresh";
23
- import { formatSyncLine, formatStatusLines } from "./status-lib";
23
+ import { formatStatusLines } from "./status-lib";
24
24
  import { formatModeStatus } from "./yolo-lib";
25
25
  import {
26
26
  formatDeviceLines,
@@ -129,7 +129,7 @@ export default function (pi: ExtensionAPI) {
129
129
  });
130
130
 
131
131
  pi.registerCommand("status", {
132
- description: "Show the Omnirush session status (identity, model, grant, session uploads)",
132
+ description: "Show the Omnirush session status (identity, model, grant)",
133
133
  handler: async (_args: string, ctx: any) => {
134
134
  const { me } = await fetchMe();
135
135
  const usage = gatewayUsage();
@@ -5,11 +5,55 @@ const SECRET_ENV_NAMES = new Set([
5
5
  "OMNIRUSH_APPROVAL_TOKEN",
6
6
  ]);
7
7
 
8
- /** Keep provider credentials in the agent process, never in shell/MCP children. */
8
+ /**
9
+ * The agent core's own PI_<NAME> variables (src/compat.js
10
+ * CORE_ONLY_ENV_NAMES; a test keeps the lists equal). Shell commands and
11
+ * MCP servers see their OMNIRUSH_<NAME> copies only; any other PI_*
12
+ * variable (a user's own) passes through.
13
+ */
14
+ export const CORE_ONLY_ENV_NAMES = [
15
+ "CODING_AGENT",
16
+ "CODING_AGENT_DIR",
17
+ "CODING_AGENT_SESSION_DIR",
18
+ "SESSION_ID",
19
+ "SESSION_FILE",
20
+ "PROVIDER",
21
+ "MODEL",
22
+ "REASONING_LEVEL",
23
+ "OFFLINE",
24
+ "TELEMETRY",
25
+ "INSTALLER_API_BASE",
26
+ "MANAGED_INSTALL_ROOT",
27
+ "CACHE_RETENTION",
28
+ "CLEAR_ON_SHRINK",
29
+ "EXPERIMENTAL",
30
+ "HARDWARE_CURSOR",
31
+ "HYPERLINKS",
32
+ "IMAGE_PROTOCOL",
33
+ "OAUTH_CALLBACK_HOST",
34
+ "PACKAGE_DIR",
35
+ "RADIUS_GATEWAY",
36
+ "SHARE_VIEWER_URL",
37
+ "SKIP_VERSION_CHECK",
38
+ "STARTUP_BENCHMARK",
39
+ "TIMING",
40
+ "TRUE_COLOR",
41
+ "TUI_DEBUG",
42
+ "TUI_DEBUG_REDRAW",
43
+ "TUI_ESC_TIMEOUT",
44
+ "TUI_WRITE_LOG",
45
+ ];
46
+ const CORE_ONLY_ENV = new Set(CORE_ONLY_ENV_NAMES.map((name) => `PI_${name}`));
47
+
48
+ /**
49
+ * Keep provider credentials in the agent process, never in shell/MCP
50
+ * children; the core's PI_* variables stay there too (the children get the
51
+ * OMNIRUSH_* spellings).
52
+ */
9
53
  export function sanitizeToolEnvironment(input: NodeJS.ProcessEnv = process.env): NodeJS.ProcessEnv {
10
54
  const output: NodeJS.ProcessEnv = {};
11
55
  for (const [key, value] of Object.entries(input)) {
12
- if (!SECRET_ENV_NAMES.has(key) && value !== undefined) output[key] = value;
56
+ if (!SECRET_ENV_NAMES.has(key) && !CORE_ONLY_ENV.has(key) && value !== undefined) output[key] = value;
13
57
  }
14
58
  return output;
15
59
  }
@@ -46,7 +46,7 @@
46
46
 
47
47
  import { createHash } from "node:crypto";
48
48
  import markSubagentSession from "./subagent-marker";
49
- import { createReadStream, readdirSync, readFileSync, rmSync } from "node:fs";
49
+ import { appendFileSync, createReadStream, mkdirSync, readdirSync, readFileSync, renameSync, rmSync, statSync } from "node:fs";
50
50
  import { readdir, stat } from "node:fs/promises";
51
51
  import os from "node:os";
52
52
  import path from "node:path";
@@ -483,10 +483,27 @@ export default function (pi: any) {
483
483
  const version = cliVersion();
484
484
  const engineVersion = (process.env.OMNIRUSH_ENGINE_VERSION || "").trim();
485
485
  const verbose = (process.env.OMNIRUSH_DEBUG || "").trim() === "1";
486
+ // Upload diagnostics never reach the terminal (they interleave with the
487
+ // TUI): warnings go to <state dir>/logs/session-upload.log (kept under
488
+ // ~1 MB), and OMNIRUSH_DEBUG=1 also prints everything to stderr.
489
+ const logFile = path.join(stateDir, "logs", "session-upload.log");
486
490
  const log = (level: "info" | "warn", message: string, attributes?: Record<string, unknown>) => {
487
491
  if (level !== "warn" && !verbose) return;
492
+ const detail = attributes && Object.keys(attributes).length > 0 ? ` ${JSON.stringify(attributes)}` : "";
493
+ const line = `${new Date().toISOString()} ${level} ${message.replace(/^OmniRush session upload: /, "")}${detail}\n`;
494
+ try {
495
+ mkdirSync(path.dirname(logFile), { recursive: true });
496
+ try {
497
+ if (statSync(logFile).size > 1_000_000) renameSync(logFile, `${logFile}.1`);
498
+ } catch {
499
+ /* no log yet */
500
+ }
501
+ appendFileSync(logFile, line);
502
+ } catch {
503
+ /* diagnostics only */
504
+ }
505
+ if (!verbose) return;
488
506
  try {
489
- const detail = attributes && Object.keys(attributes).length > 0 ? ` ${JSON.stringify(attributes)}` : "";
490
507
  process.stderr.write(`omnirush session upload: ${message}${detail}\n`);
491
508
  } catch {
492
509
  /* stderr may be closed late in shutdown */
@@ -80,7 +80,6 @@ export function formatStatusLines(input: StatusInput): string[] {
80
80
  lines.push("grant: no data yet (appears after the first request or via /usage)");
81
81
  }
82
82
 
83
- lines.push(formatSyncLine(input.sync));
84
83
  if (input.yolo) lines.push(input.yolo);
85
84
  lines.push(`manager: ${input.origin}`);
86
85
  lines.push(`version: ${input.version}`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.10.3",
3
+ "version": "0.10.5",
4
4
  "description": "Omnirush — free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -23,10 +23,16 @@
23
23
  // folders), OMNIRUSH_* copies of the env the core exports to shell
24
24
  // commands, AI_AGENT, the HTTP User-Agent and temp-file names.
25
25
  //
26
+ // 4. <package>/engine, a link to the core (src/compat.js
27
+ // ensureEngineLink): the launcher runs the core through it, so process
28
+ // listings and the doc paths in the system prompt name omnirush, not
29
+ // the core's npm package.
30
+ //
26
31
  // Not renamed (internal, never shown): package ids and imports, code
27
32
  // identifiers, session entry/customType ids, telemetry attribute keys,
28
33
  // protocol values third-party providers expect, and the PI_* env names the
29
- // core reads (the launcher maps OMNIRUSH_* spellings onto them).
34
+ // core reads (the launcher maps OMNIRUSH_* spellings onto them; shells the
35
+ // core starts get the OMNIRUSH_* copies only).
30
36
  //
31
37
  // Maintenance: after bumping the core, run `node scripts/brand-engine.js`
32
38
  // and `node --test test/branding.test.js`; a target that moved is reported
@@ -38,14 +44,42 @@ import fs from "node:fs";
38
44
  import path from "node:path";
39
45
  import { fileURLToPath } from "node:url";
40
46
 
47
+ import { CORE_ONLY_ENV_NAMES, ensureEngineLink } from "../src/compat.js";
48
+
41
49
  const PKG = "@earendil-works/pi-coding-agent";
42
50
  const BRAND = "omnirush";
43
51
  export const BRAND_CONFIG_DIR = ".omnirush";
44
52
  const OUR_CHANGELOG = path.join(path.dirname(fileURLToPath(import.meta.url)), "..", "assets", "CHANGELOG.md");
45
53
 
54
+ /** Matches the core's own PI_<NAME> variables (src/compat.js CORE_ONLY_ENV_NAMES), as JS regex source. */
55
+ export const CORE_ENV_PATTERN = `/^PI_(${CORE_ONLY_ENV_NAMES.join("|")})$/`;
56
+
46
57
  // `from` must match the installed dist exactly; applied with replaceAll to
47
58
  // every dist/**/*.js. A `to` must never contain its own `from` (idempotency).
48
59
  export const REPLACEMENTS = [
60
+ // --- app name fallback: omnirush even when the core can't read piConfig ---
61
+ // (a package dir resolved elsewhere, e.g. a bun binary or a moved folder).
62
+ // This is the name in "To resume this session: <name> --session <id>".
63
+ {
64
+ label: "app name fallback (bundle)",
65
+ from: 'APP_NAME=piConfigName||"pi"',
66
+ to: 'APP_NAME=piConfigName||"omnirush"',
67
+ },
68
+ {
69
+ label: "app name fallback (dist)",
70
+ from: 'APP_NAME = piConfigName || "pi"',
71
+ to: 'APP_NAME = piConfigName || "omnirush"',
72
+ },
73
+ {
74
+ label: "app title fallback (bundle)",
75
+ from: 'APP_TITLE=piConfigName?APP_NAME:"\\u03C0"',
76
+ to: 'APP_TITLE=APP_NAME',
77
+ },
78
+ {
79
+ label: "app title fallback (dist)",
80
+ from: 'APP_TITLE = piConfigName ? APP_NAME : "\u03c0"',
81
+ to: 'APP_TITLE = APP_NAME',
82
+ },
49
83
  // --- project config folder: legacy <project>/.pi fallback -------------
50
84
  {
51
85
  label: "project config dir hook (bundle)",
@@ -157,13 +191,37 @@ export const REPLACEMENTS = [
157
191
  {
158
192
  label: "/bug disclaimer",
159
193
  from: "This report goes to the Pi developers (Earendil) and is not shared publicly. It includes your pi version,",
160
- to: "This report goes to the agent core's upstream developers (Earendil) and is not shared publicly. It includes your omnirush version,",
194
+ to: "This report is not shared publicly. It includes your omnirush version,",
161
195
  },
162
196
  {
163
197
  label: "announcement easter egg",
164
198
  from: '"pi has joined Earendil"',
165
- to: '"The agent core has joined Earendil"',
199
+ to: '"omnirush"',
166
200
  },
201
+ {
202
+ // Upstream easter eggs typed as commands (/arminsayshi, /dementedelves;
203
+ // the third only fires for one third-party model): shown nothing.
204
+ label: "hidden /arminsayshi is inert (bundle)",
205
+ from: "handleArminSaysHi(){",
206
+ to: "handleArminSaysHi(_off){return;",
207
+ },
208
+ {
209
+ label: "hidden /dementedelves is inert (bundle)",
210
+ from: "handleDementedDelves(){",
211
+ to: "handleDementedDelves(_off){return;",
212
+ },
213
+ { label: "upstream easter egg is inert (bundle)", from: "handleDaxnuts(){", to: "handleDaxnuts(_off){return;" },
214
+ {
215
+ label: "hidden /arminsayshi is inert (dist)",
216
+ from: " handleArminSaysHi() {",
217
+ to: " handleArminSaysHi(_off) { return;",
218
+ },
219
+ {
220
+ label: "hidden /dementedelves is inert (dist)",
221
+ from: " handleDementedDelves() {",
222
+ to: " handleDementedDelves(_off) { return;",
223
+ },
224
+ { label: "upstream easter egg is inert (dist)", from: " handleDaxnuts() {", to: " handleDaxnuts(_off) { return;" },
167
225
  {
168
226
  label: "external editor note",
169
227
  from: "Pi will resume when the editor exits.",
@@ -345,7 +403,20 @@ export const REPLACEMENTS = [
345
403
  label: "OMNIRUSH_* session env for shell commands",
346
404
  from: "ctx.thinkingLevel&&(env2.PI_REASONING_LEVEL=ctx.thinkingLevel)}let baseContext={command,cwd,env:env2}",
347
405
  to:
348
- 'ctx.thinkingLevel&&(env2.PI_REASONING_LEVEL=ctx.thinkingLevel)}for(let k of["SESSION_ID","SESSION_FILE","PROVIDER","MODEL","REASONING_LEVEL"])env2["PI_"+k]===void 0?delete env2["OMNIRUSH_"+k]:env2["OMNIRUSH_"+k]=env2["PI_"+k];let baseContext={command,cwd,env:env2}',
406
+ 'ctx.thinkingLevel&&(env2.PI_REASONING_LEVEL=ctx.thinkingLevel)}for(let k of["SESSION_ID","SESSION_FILE","PROVIDER","MODEL","REASONING_LEVEL"])env2["PI_"+k]===void 0?delete env2["OMNIRUSH_"+k]:(env2["OMNIRUSH_"+k]=env2["PI_"+k],delete env2["PI_"+k]);let baseContext={command,cwd,env:env2}',
407
+ },
408
+ {
409
+ // The env every shell the core starts inherits (the bash tool, `!`
410
+ // commands, package installs): the core's own PI_* variables stay in
411
+ // the agent process; their OMNIRUSH_* copies are what commands see.
412
+ label: "no core PI_* variables in shell commands (bundle)",
413
+ from: "return{...process.env,[pathKey]:updatedPath}}function sanitizeBinaryOutput",
414
+ to: `let _env={...process.env,[pathKey]:updatedPath};for(let k of Object.keys(_env))${CORE_ENV_PATTERN}.test(k)&&delete _env[k];return _env}function sanitizeBinaryOutput`,
415
+ },
416
+ {
417
+ label: "no core PI_* variables in shell commands (dist)",
418
+ from: " return {\n ...process.env,\n [pathKey]: updatedPath,\n };\n}",
419
+ to: ` const _env = { ...process.env, [pathKey]: updatedPath };\n for (const k of Object.keys(_env)) if (${CORE_ENV_PATTERN}.test(k)) delete _env[k];\n return _env;\n}`,
349
420
  },
350
421
 
351
422
  // --- HTTP User-Agent (the omnirush gateway sees it) --------------------
@@ -377,6 +448,9 @@ export const REPLACEMENTS = [
377
448
  { label: "bug report zip", from: "`pi-bug-report-${id}.zip`", to: "`omnirush-bug-report-${id}.zip`" },
378
449
  { label: "TUI crash log", from: '"pi-tui-crash.log"', to: '"omnirush-tui-crash.log"' },
379
450
  { label: "TUI debug log", from: '"pi-tui-debug.log"', to: '"omnirush-tui-debug.log"' },
451
+ { label: "git package update marker", from: ".pi-update-incomplete", to: ".omnirush-update-incomplete" },
452
+ { label: "native module quarantine dir", from: '".pi-native-quarantine"', to: '".omnirush-native-quarantine"' },
453
+ { label: "package install root package.json name", from: '"pi-extensions"', to: '"omnirush-extensions"' },
380
454
 
381
455
  // --- update banner (belt-and-braces; the launcher sets PI_OFFLINE) -----
382
456
  // showNewVersionNotification returns before its body (the parameter is
@@ -399,6 +473,23 @@ export const REPLACEMENTS = [
399
473
  },
400
474
  ];
401
475
 
476
+ // Replace a file in one step: the launcher may brand on first run while
477
+ // another omnirush start is loading the same core files.
478
+ function writeAtomic(file, data) {
479
+ const tmp = `${file}.${process.pid}.branding`;
480
+ try {
481
+ fs.writeFileSync(tmp, data);
482
+ fs.renameSync(tmp, file);
483
+ } catch (error) {
484
+ try {
485
+ fs.rmSync(tmp, { force: true });
486
+ } catch {
487
+ /* nothing to clean */
488
+ }
489
+ throw error;
490
+ }
491
+ }
492
+
402
493
  function findPiRoot(startDir) {
403
494
  let dir = startDir;
404
495
  for (let i = 0; i < 8; i++) {
@@ -434,6 +525,15 @@ export function applyBrandingPatches(startDir, { version } = {}) {
434
525
  if (!piRoot) {
435
526
  return { piRoot: null, packageJsonPatched: false, applied: [], missing: [{ label: "core", reason: `${PKG} not installed` }] };
436
527
  }
528
+ return brandCoreDir(piRoot, { version });
529
+ }
530
+
531
+ /**
532
+ * Apply the rebrand to the core laid out at `piRoot` (its package.json,
533
+ * CHANGELOG.md and dist/): the installed copy, or the launcher's branded
534
+ * copy of a read-only install (src/bin.js).
535
+ */
536
+ export function brandCoreDir(piRoot, { version } = {}) {
437
537
 
438
538
  // 1. The supported rebrand hook (+ the product version: the banner and
439
539
  // --version of the core read their own package.json).
@@ -443,7 +543,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
443
543
  if (pkg.piConfig?.name !== BRAND || pkg.piConfig?.configDir !== BRAND_CONFIG_DIR || (version && pkg.version !== version)) {
444
544
  pkg.piConfig = { ...(pkg.piConfig || {}), name: BRAND, configDir: BRAND_CONFIG_DIR };
445
545
  if (version) pkg.version = version;
446
- fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
546
+ writeAtomic(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
447
547
  packageJsonPatched = true;
448
548
  }
449
549
 
@@ -459,7 +559,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
459
559
  /* absent */
460
560
  }
461
561
  if (current !== ours) {
462
- fs.writeFileSync(target, ours);
562
+ writeAtomic(target, ours);
463
563
  changelogReplaced = true;
464
564
  }
465
565
  } catch {
@@ -490,7 +590,7 @@ export function applyBrandingPatches(startDir, { version } = {}) {
490
590
  if (hits > 0) applied.push({ label: replacement.label, files: hits });
491
591
  else missing.push({ label: replacement.label, reason: "target string not found (already applied or core updated)" });
492
592
  }
493
- for (const file of dirty) fs.writeFileSync(file, contents.get(file));
593
+ for (const file of dirty) writeAtomic(file, contents.get(file));
494
594
  return { piRoot, packageJsonPatched, changelogReplaced, applied, missing };
495
595
  }
496
596
 
@@ -507,6 +607,11 @@ function main() {
507
607
  console.log("omnirush: agent core not installed; skipped branding patch");
508
608
  return;
509
609
  }
610
+ // <package>/engine -> the core (what process listings and the system
611
+ // prompt's doc paths show; the launcher re-creates it when missing).
612
+ ensureEngineLink(repoRoot, report.piRoot);
613
+ // The launcher's first-run check (src/bin.js ensureBranded) reads this.
614
+ if (version) fs.writeFileSync(path.join(report.piRoot, ".omnirush-branded"), version + "\n");
510
615
  const patched = report.packageJsonPatched ? "package.json + " : "";
511
616
  const notes = report.changelogReplaced ? " + release notes" : "";
512
617
  console.log(
package/src/bin.js CHANGED
@@ -54,9 +54,10 @@ import {
54
54
  yoloBanner,
55
55
  } from "./lib.js";
56
56
  import { installManagedTools } from "./tools.js";
57
- import { engineEnvAliases, migrateAgentDir, projectConfigDirName, resolveAgentDir } from "./compat.js";
57
+ import { engineEnvAliases, ensureEngineLink, migrateAgentDir, projectConfigDirName, resolveAgentDir } from "./compat.js";
58
58
  import { customSessionDir, moveLegacySubagentSessions } from "./sessions.js";
59
59
  import { ensureRuntime, managedBunPath } from "../scripts/postinstall.js";
60
+ import { applyBrandingPatches, brandCoreDir } from "../scripts/brand-engine.js";
60
61
  import {
61
62
  clearAuth,
62
63
  deviceAuthorize,
@@ -144,12 +145,129 @@ function piPackage() {
144
145
  return null;
145
146
  }
146
147
 
148
+ // The core as users see it (process listings, the doc paths in the system
149
+ // prompt, sub-agent command lines): <package>/engine, a link to the core's
150
+ // folder (src/compat.js ensureEngineLink); null when the link cannot be made.
151
+ let ENGINE_LINK_DIR = null;
152
+ let ENGINE_ENTRY = null;
153
+
154
+ // Written into the core's folder once it is branded for this omnirush
155
+ // version. Newer npm skips install scripts by default, so the postinstall
156
+ // branding may never have run: the launcher then brands on first run.
157
+ const BRAND_STAMP = ".omnirush-branded";
158
+
159
+ function brandedStamp(dir, version) {
160
+ try {
161
+ const pkg = JSON.parse(fs.readFileSync(path.join(dir, "package.json"), "utf8"));
162
+ if (pkg?.piConfig?.name === "omnirush" && fs.readFileSync(path.join(dir, BRAND_STAMP), "utf8").trim() === version) {
163
+ return { dir, pkg };
164
+ }
165
+ } catch {
166
+ /* not branded (yet) */
167
+ }
168
+ return null;
169
+ }
170
+
171
+ /**
172
+ * A core folder we cannot write (a root-owned global prefix installed
173
+ * without its scripts): run a branded copy of its runtime files instead,
174
+ * <state dir>/engine/<version> (package.json, release notes, README, the
175
+ * runtime bundle with its themes, assets and export template; docs,
176
+ * examples and node_modules are links to the installed ones). Made
177
+ * once per version, in one step (a temporary folder renamed into place).
178
+ */
179
+ function brandedCopy(found, version) {
180
+ const dir = path.join(OMNI_DIR, "engine", version);
181
+ const ready = brandedStamp(dir, version);
182
+ if (ready) return ready;
183
+ const tmp = `${dir}.${process.pid}.tmp`;
184
+ try {
185
+ fs.rmSync(tmp, { recursive: true, force: true });
186
+ fs.mkdirSync(tmp, { recursive: true });
187
+ for (const name of ["package.json", "CHANGELOG.md", "README.md"]) {
188
+ if (fs.existsSync(path.join(found.dir, name))) fs.writeFileSync(path.join(tmp, name), fs.readFileSync(path.join(found.dir, name)));
189
+ }
190
+ // What the runtime bundle reads (the same set the 1.0.0 build ships).
191
+ // Copied by hand: the source is read-only, and its modes must not come along.
192
+ const copyTree = (from, to) => {
193
+ fs.mkdirSync(to, { recursive: true });
194
+ for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
195
+ const src = path.join(from, entry.name);
196
+ if (entry.isDirectory()) copyTree(src, path.join(to, entry.name));
197
+ else if (entry.isFile() && !entry.name.endsWith(".map") && !entry.name.endsWith(".d.ts")) {
198
+ fs.writeFileSync(path.join(to, entry.name), fs.readFileSync(src));
199
+ }
200
+ }
201
+ };
202
+ for (const part of [["bundle"], ["modes", "interactive", "theme"], ["modes", "interactive", "assets"], ["core", "export-html"]]) {
203
+ const from = path.join(found.dir, "dist", ...part);
204
+ if (fs.existsSync(from)) copyTree(from, path.join(tmp, "dist", ...part));
205
+ }
206
+ for (const name of ["node_modules", "docs", "examples"]) {
207
+ const target = path.join(found.dir, name);
208
+ if (!fs.existsSync(target)) continue;
209
+ if (process.platform === "win32") fs.symlinkSync(target, path.join(tmp, name), "junction");
210
+ else fs.symlinkSync(target, path.join(tmp, name), "dir");
211
+ }
212
+ brandCoreDir(tmp, { version });
213
+ fs.writeFileSync(path.join(tmp, BRAND_STAMP), version + "\n");
214
+ fs.rmSync(dir, { recursive: true, force: true });
215
+ try {
216
+ fs.renameSync(tmp, dir);
217
+ } catch (error) {
218
+ // A concurrent start may have finished first.
219
+ if (!brandedStamp(dir, version)) throw error;
220
+ }
221
+ return brandedStamp(dir, version);
222
+ } catch (error) {
223
+ if (process.env.OMNIRUSH_DEBUG === "1") process.stderr.write(`omnirush: branded copy failed: ${error?.stack || error}\n`);
224
+ return null;
225
+ } finally {
226
+ try {
227
+ fs.rmSync(tmp, { recursive: true, force: true });
228
+ } catch {
229
+ /* best effort */
230
+ }
231
+ }
232
+ }
233
+
234
+ function ensureBranded(found) {
235
+ const version = ownVersion();
236
+ const done = brandedStamp(found.dir, version);
237
+ if (done) return done;
238
+ try {
239
+ applyBrandingPatches(path.resolve(__dirname, ".."), { version });
240
+ fs.writeFileSync(path.join(found.dir, BRAND_STAMP), version + "\n");
241
+ return { dir: found.dir, pkg: JSON.parse(fs.readFileSync(path.join(found.dir, "package.json"), "utf8")) };
242
+ } catch (error) {
243
+ // Read-only install: a branded copy in the state dir.
244
+ const copy = brandedCopy(found, version);
245
+ if (copy) return copy;
246
+ process.stderr.write(`omnirush: could not finish setup (${error?.code || error?.message || error}); reinstall with: npm install -g omnirush@latest\n`);
247
+ return found;
248
+ }
249
+ }
250
+
147
251
  function piEntry() {
148
- const found = piPackage();
149
- if (found?.pkg?.bin?.pi) return path.join(found.dir, found.pkg.bin.pi);
252
+ let found = piPackage();
253
+ if (found?.pkg?.bin?.pi) found = ensureBranded(found);
254
+ if (found?.pkg?.bin?.pi) {
255
+ const dir = ensureEngineLink(path.resolve(__dirname, ".."), found.dir);
256
+ ENGINE_LINK_DIR = dir === path.resolve(found.dir) ? null : dir;
257
+ ENGINE_ENTRY = path.join(dir, found.pkg.bin.pi);
258
+ return ENGINE_ENTRY;
259
+ }
150
260
  throw new Error("could not locate the pinned agent core (broken install). Fix: npm install -g omnirush@latest");
151
261
  }
152
262
 
263
+ /** The core's package dir as the link, unless the user points it elsewhere. */
264
+ function engineDirEnv(env = process.env) {
265
+ const env2 = {};
266
+ if (ENGINE_ENTRY) env2.OMNIRUSH_CORE_ENTRY = ENGINE_ENTRY; // sub-agents start the core through the same path
267
+ if (ENGINE_LINK_DIR && !env.PI_PACKAGE_DIR && !env.OMNIRUSH_PACKAGE_DIR) env2.PI_PACKAGE_DIR = ENGINE_LINK_DIR;
268
+ return env2;
269
+ }
270
+
153
271
  function ensureDirs() {
154
272
  fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
155
273
  migrateLegacyAgentData();
@@ -736,7 +854,8 @@ async function cmdDoctor() {
736
854
  // branding and shows pi.dev "Update Available" banners (Foxtran's
737
855
  // 0.73.1 report). Advisory WARN with the fix; exit code unaffected.
738
856
  {
739
- const found = piPackage();
857
+ let found = piPackage();
858
+ if (found?.pkg?.bin?.pi) found = ensureBranded(found); // the copy that runs (a read-only install's branded copy)
740
859
  const coreVersion = String(found?.pkg?.version ?? "");
741
860
  const own = ownVersion();
742
861
  if (!coreVersion) {
@@ -875,6 +994,7 @@ function childEnv(auth) {
875
994
  const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
876
995
  gateway.replace(/\/+$/, "").replace(/\/v1$/, ""); // provider base -> manager origin
877
996
  return {
997
+ ...engineDirEnv(process.env),
878
998
  // OMNIRUSH_* spellings of the core's own settings (PI_* still work).
879
999
  ...engineEnvAliases(process.env),
880
1000
  // Bootstrap values only: the agent (and every sub-agent it spawns)
@@ -1029,7 +1149,6 @@ Usage:
1029
1149
  omnirush logout forget the stored device tokens
1030
1150
  omnirush whoami show the signed-in identity
1031
1151
  omnirush doctor check auth, manager, runtime, agent config
1032
- omnirush upload [--dir PATH] upload a one-shot workspace snapshot to omnirush.ai
1033
1152
  omnirush update update omnirush (npm install -g omnirush@latest)
1034
1153
  omnirush --version show the omnirush version
1035
1154
 
@@ -1057,7 +1176,7 @@ Options:
1057
1176
  Session commands (inside the agent):
1058
1177
  /grant today's token grant and usage
1059
1178
  /usage detailed usage from the manager (counters + devices)
1060
- /status session status (identity, model, grant, session uploads)
1179
+ /status session status (identity, model, grant)
1061
1180
  /mcp MCP server state (/mcp reconnect [server])
1062
1181
  /subagents model and effort of the sub-agents (/subagents model <id|same>,
1063
1182
  /subagents effort <level|same>; saved for the next runs)
@@ -1090,8 +1209,7 @@ Environment:
1090
1209
  (default 50% of RAM), and none start below this
1091
1210
  (default 10%); e.g. 25% or 6G. OMNIRUSH_AGENT_MEMORY=off
1092
1211
  starts them without checking memory
1093
- OMNIRUSH_<SETTING> the agent core's own settings (e.g. OMNIRUSH_CACHE_RETENTION;
1094
- the older PI_<SETTING> names still work)
1212
+ OMNIRUSH_<SETTING> the agent core's own settings (e.g. OMNIRUSH_CACHE_RETENTION)
1095
1213
 
1096
1214
  Docs: https://omnirush.ai/omnirush — state is stored 0600 in ~/.omnirush`);
1097
1215
  }
@@ -1185,6 +1303,15 @@ async function runCore(args) {
1185
1303
 
1186
1304
  const [, , ...argv] = process.argv;
1187
1305
  const cmd = argv[0];
1306
+ // First run after an install whose scripts were skipped: brand the core and
1307
+ // make the engine link now, whatever command runs (cheap once stamped).
1308
+ {
1309
+ const core = piPackage();
1310
+ if (core?.pkg?.bin?.pi) {
1311
+ const branded = ensureBranded(core);
1312
+ ensureEngineLink(path.resolve(__dirname, ".."), branded.dir);
1313
+ }
1314
+ }
1188
1315
  if (cmd === "-h" || cmd === "--help" || cmd === "help") printHelp();
1189
1316
  else if (cmd === "login") await cmdLogin(argv.slice(1));
1190
1317
  else if (cmd === "logout") await cmdLogout();
package/src/compat.js CHANGED
@@ -270,3 +270,68 @@ export function engineEnvAliases(env = process.env) {
270
270
  }
271
271
  return out;
272
272
  }
273
+
274
+ /**
275
+ * The core's own variables the launcher and the core set for themselves
276
+ * (PI_<NAME>). Shell commands the agent runs, background jobs and MCP
277
+ * servers see their OMNIRUSH_<NAME> copies only; a PI_ variable that is
278
+ * not on this list (a user's own) passes through. Mirrored in
279
+ * scripts/brand-engine.js (the core's shell env) and
280
+ * assets/extensions/omnirush/secret-env.ts (tests keep them equal).
281
+ */
282
+ export const CORE_ONLY_ENV_NAMES = [
283
+ "CODING_AGENT",
284
+ "CODING_AGENT_DIR",
285
+ "CODING_AGENT_SESSION_DIR",
286
+ "SESSION_ID",
287
+ "SESSION_FILE",
288
+ "PROVIDER",
289
+ "MODEL",
290
+ "REASONING_LEVEL",
291
+ "OFFLINE",
292
+ "TELEMETRY",
293
+ "INSTALLER_API_BASE",
294
+ "MANAGED_INSTALL_ROOT",
295
+ ...ENGINE_ENV_NAMES,
296
+ ];
297
+
298
+ /** Folder, in the omnirush package, that links to the installed agent core. */
299
+ export const ENGINE_LINK = "engine";
300
+
301
+ /**
302
+ * The agent core as users see it: <package root>/engine, a link to the
303
+ * core's folder in node_modules (a junction on Windows). The launcher runs
304
+ * the core through it and hands it over as the core's package dir, so
305
+ * process listings, the doc paths in the system prompt and the sub-agent
306
+ * command lines name omnirush instead of the core's npm package. Made by
307
+ * the branding step at install and, when missing (archives, a read-only
308
+ * reinstall), here at launch. Never replaces a real folder. Returns the
309
+ * link, or the core's own folder when the link cannot be made.
310
+ */
311
+ export function ensureEngineLink(packageRoot, coreDir, { fsImpl = fs, platform = process.platform } = {}) {
312
+ const link = path.join(packageRoot, ENGINE_LINK);
313
+ const real = path.resolve(coreDir);
314
+ const points = () => {
315
+ try {
316
+ return fsImpl.realpathSync(link) === fsImpl.realpathSync(real);
317
+ } catch {
318
+ return false;
319
+ }
320
+ };
321
+ if (points()) return link;
322
+ try {
323
+ let stat = null;
324
+ try {
325
+ stat = fsImpl.lstatSync(link);
326
+ } catch {
327
+ /* absent */
328
+ }
329
+ if (stat && !stat.isSymbolicLink()) return real;
330
+ if (stat) fsImpl.unlinkSync(link); // a stale link (the core moved)
331
+ if (platform === "win32") fsImpl.symlinkSync(real, link, "junction");
332
+ else fsImpl.symlinkSync(path.relative(path.dirname(link), real) || ".", link, "dir");
333
+ } catch {
334
+ /* read-only install or a concurrent launch: checked below */
335
+ }
336
+ return points() ? link : real;
337
+ }