omnirush 0.8.6 → 0.9.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/assets/CHANGELOG.md +85 -0
- package/assets/extensions/omnirush/agents-lib.ts +134 -13
- package/assets/extensions/omnirush/agents.ts +51 -107
- package/assets/extensions/omnirush/bgshell-lib.ts +400 -0
- package/assets/extensions/omnirush/bgshell.ts +392 -0
- package/assets/extensions/omnirush/collector.ts +72 -11
- package/assets/extensions/omnirush/commands.ts +2 -0
- package/assets/extensions/omnirush/deliveries.ts +145 -0
- package/assets/extensions/omnirush/guard/UPSTREAM +2 -0
- package/assets/extensions/omnirush/guard/git-command-policy.ts +885 -0
- package/assets/extensions/omnirush/guard-lib.ts +230 -0
- package/assets/extensions/omnirush/guard.ts +340 -0
- package/assets/extensions/omnirush/index.ts +12 -0
- package/assets/extensions/omnirush/pi-engine.ts +65 -1
- package/assets/extensions/omnirush/sota.ts +52 -4
- package/assets/extensions/omnirush/status-lib.ts +3 -0
- package/assets/extensions/omnirush/subagents-lib.ts +623 -0
- package/assets/extensions/omnirush/subagents.ts +305 -0
- package/assets/extensions/omnirush/swarm-lib.ts +142 -0
- package/assets/extensions/omnirush/swarm.ts +95 -0
- package/assets/extensions/omnirush/voice/capture.ts +502 -0
- package/assets/extensions/omnirush/voice/core/UPSTREAM +16 -0
- package/assets/extensions/omnirush/voice/core/file-source.ts +70 -0
- package/assets/extensions/omnirush/voice/core/index.ts +21 -0
- package/assets/extensions/omnirush/voice/core/keyterms.ts +117 -0
- package/assets/extensions/omnirush/voice/core/resample.ts +63 -0
- package/assets/extensions/omnirush/voice/core/segmenter.ts +231 -0
- package/assets/extensions/omnirush/voice/core/session.ts +403 -0
- package/assets/extensions/omnirush/voice/core/text.ts +81 -0
- package/assets/extensions/omnirush/voice/core/transcriber.ts +135 -0
- package/assets/extensions/omnirush/voice/core/types.ts +102 -0
- package/assets/extensions/omnirush/voice/core/wav.ts +95 -0
- package/assets/extensions/omnirush/voice/keys.ts +435 -0
- package/assets/extensions/omnirush/voice/kitty.ts +64 -0
- package/assets/extensions/omnirush/voice/pvrecorder-worker.cjs +43 -0
- package/assets/extensions/omnirush/voice/settings.ts +67 -0
- package/assets/extensions/omnirush/voice.ts +838 -0
- package/assets/extensions/omnirush/yolo-lib.ts +80 -0
- package/assets/extensions/omnirush/yolo.ts +85 -0
- package/package.json +7 -3
- package/scripts/brand-engine.js +526 -0
- package/scripts/build-all-packages.py +29 -1
- package/scripts/smoke-packages.py +32 -1
- package/src/bin.js +205 -33
- package/src/compat.js +272 -0
- package/src/lib.js +64 -0
- package/scripts/patch-pi-branding.js +0 -251
package/src/bin.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
// omnirush — launcher for the Omnirush coding agent
|
|
2
|
+
// omnirush — launcher for the Omnirush coding agent.
|
|
3
3
|
//
|
|
4
4
|
// The CLI is a thin client of the Omnirush manager (a single public
|
|
5
5
|
// HTTPS origin, real cert — no helper proxy, no custom CA):
|
|
@@ -8,15 +8,18 @@
|
|
|
8
8
|
// stored 0600 in ~/.omnirush/auth.json)
|
|
9
9
|
// omnirush logout forget the stored tokens
|
|
10
10
|
// omnirush whoami show the signed-in device identity
|
|
11
|
-
// omnirush doctor check auth (or pending login), manager,
|
|
11
|
+
// omnirush doctor check auth (or pending login), manager, agent config
|
|
12
12
|
// omnirush collect one-shot workspace snapshot upload
|
|
13
|
-
// omnirush
|
|
13
|
+
// omnirush update update omnirush from npm
|
|
14
|
+
// omnirush run the agent (default; forwards to the agent core)
|
|
14
15
|
//
|
|
15
16
|
// Environment:
|
|
16
17
|
// OMNIRUSH_ORIGIN manager origin override (default
|
|
17
18
|
// https://omnirush.ai/omnirush)
|
|
18
19
|
// OMNIRUSH_GATEWAY_URL provider base URL override ({origin}/v1)
|
|
19
20
|
// OMNIRUSH_DIR state dir override (default ~/.omnirush)
|
|
21
|
+
// OMNIRUSH_AGENT_DIR agent data dir override (default <state dir>/agent;
|
|
22
|
+
// OMNIRUSH_PI_AGENT_DIR is the older spelling)
|
|
20
23
|
// OMNIRUSH_MODEL model override for plain runs (default
|
|
21
24
|
// gpt-6-astra; gpt-6-sol, gpt-5.6-sol and the Muse
|
|
22
25
|
// models also available; user
|
|
@@ -47,8 +50,11 @@ import {
|
|
|
47
50
|
withModelDefaults,
|
|
48
51
|
withDefaultThinkingLevel,
|
|
49
52
|
resolveModelDefaults,
|
|
53
|
+
resolveYolo,
|
|
54
|
+
yoloBanner,
|
|
50
55
|
} from "./lib.js";
|
|
51
56
|
import { installManagedTools } from "./tools.js";
|
|
57
|
+
import { engineEnvAliases, migrateAgentDir, projectConfigDirName, resolveAgentDir } from "./compat.js";
|
|
52
58
|
import { ensureRuntime, managedBunPath } from "../scripts/postinstall.js";
|
|
53
59
|
import {
|
|
54
60
|
clearAuth,
|
|
@@ -70,14 +76,43 @@ import {
|
|
|
70
76
|
const require = createRequire(import.meta.url);
|
|
71
77
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
72
78
|
const OMNI_DIR = omniDir();
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
79
|
+
process.title = "omnirush";
|
|
80
|
+
// The agent core's data (settings, models.json, provider auth, sessions,
|
|
81
|
+
// extensions): <state dir>/agent, or OMNIRUSH_AGENT_DIR. Releases up to
|
|
82
|
+
// 0.8.x used the core's upstream default folder; ensureDirs() copies it
|
|
83
|
+
// over once (src/compat.js) and falls back to it for a run whose copy failed.
|
|
84
|
+
const AGENT = resolveAgentDir(process.env, { stateDir: OMNI_DIR });
|
|
85
|
+
let PI_AGENT_DIR, PI_MODELS_JSON, PI_SETTINGS_JSON, PI_EXT_DIR;
|
|
86
|
+
function useAgentDir(dir) {
|
|
87
|
+
PI_AGENT_DIR = dir;
|
|
88
|
+
PI_MODELS_JSON = path.join(dir, "models.json");
|
|
89
|
+
PI_SETTINGS_JSON = path.join(dir, "settings.json");
|
|
90
|
+
PI_EXT_DIR = path.join(dir, "extensions", "omnirush");
|
|
91
|
+
}
|
|
92
|
+
useAgentDir(AGENT.dir);
|
|
93
|
+
|
|
94
|
+
/** One-time copy of the pre-0.9 agent data into the new folder (see src/compat.js). */
|
|
95
|
+
function migrateLegacyAgentData() {
|
|
96
|
+
if (!AGENT.legacyDir) return;
|
|
97
|
+
const result = migrateAgentDir({
|
|
98
|
+
from: AGENT.legacyDir,
|
|
99
|
+
to: AGENT.dir,
|
|
100
|
+
onStart: () => console.error(`omnirush: copying your sessions and agent settings to ${AGENT.dir} (one time)...`),
|
|
101
|
+
});
|
|
102
|
+
if (result.status === "migrated") {
|
|
103
|
+
console.error(
|
|
104
|
+
`omnirush: done (${result.copied} files copied${result.merged ? `, ${result.merged} merged` : ""}; ` +
|
|
105
|
+
"the previous folder was left as it was).",
|
|
106
|
+
);
|
|
107
|
+
} else if (result.status === "failed") {
|
|
108
|
+
// Never lose data: run on the previous folder and retry next launch.
|
|
109
|
+
console.error(
|
|
110
|
+
`omnirush: could not copy your agent data to ${AGENT.dir} (${result.error?.message ?? result.error}); ` +
|
|
111
|
+
"using the previous folder for this run, retrying next time.",
|
|
112
|
+
);
|
|
113
|
+
useAgentDir(AGENT.legacyDir);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
81
116
|
|
|
82
117
|
function managerOrigin() {
|
|
83
118
|
return (
|
|
@@ -111,11 +146,12 @@ function piPackage() {
|
|
|
111
146
|
function piEntry() {
|
|
112
147
|
const found = piPackage();
|
|
113
148
|
if (found?.pkg?.bin?.pi) return path.join(found.dir, found.pkg.bin.pi);
|
|
114
|
-
throw new Error("could not locate the pinned
|
|
149
|
+
throw new Error("could not locate the pinned agent core (broken install). Fix: npm install -g omnirush@latest");
|
|
115
150
|
}
|
|
116
151
|
|
|
117
152
|
function ensureDirs() {
|
|
118
153
|
fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
|
|
154
|
+
migrateLegacyAgentData();
|
|
119
155
|
fs.mkdirSync(PI_AGENT_DIR, { recursive: true });
|
|
120
156
|
fs.mkdirSync(PI_EXT_DIR, { recursive: true });
|
|
121
157
|
try {
|
|
@@ -233,7 +269,7 @@ function installExtensions() {
|
|
|
233
269
|
fs.mkdirSync(to, { recursive: true });
|
|
234
270
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
235
271
|
if (entry.isDirectory()) copy(path.join(from, entry.name), path.join(to, entry.name));
|
|
236
|
-
else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js")) {
|
|
272
|
+
else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js") || entry.name.endsWith(".cjs")) {
|
|
237
273
|
fs.copyFileSync(path.join(from, entry.name), path.join(to, entry.name));
|
|
238
274
|
}
|
|
239
275
|
}
|
|
@@ -729,6 +765,8 @@ function childEnv(auth) {
|
|
|
729
765
|
const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
|
|
730
766
|
gateway.replace(/\/+$/, "").replace(/\/v1$/, ""); // provider base -> manager origin
|
|
731
767
|
return {
|
|
768
|
+
// OMNIRUSH_* spellings of the core's own settings (PI_* still work).
|
|
769
|
+
...engineEnvAliases(process.env),
|
|
732
770
|
// Bootstrap values only: the agent (and every sub-agent it spawns)
|
|
733
771
|
// re-reads the shared auth file in OMNIRUSH_DIR for each request, so
|
|
734
772
|
// these never go stale in a way that matters.
|
|
@@ -738,15 +776,22 @@ function childEnv(auth) {
|
|
|
738
776
|
OMNIRUSH_GATEWAY_URL: gateway,
|
|
739
777
|
OMNIRUSH_DIR: OMNI_DIR,
|
|
740
778
|
// /status shows the CLI version; extensions have no package.json of
|
|
741
|
-
// their own in
|
|
779
|
+
// their own in the agent dir, so the launcher hands it over.
|
|
742
780
|
OMNIRUSH_VERSION: ownVersion(),
|
|
743
781
|
// The collector reports it as the environment's engine_version.
|
|
744
782
|
OMNIRUSH_ENGINE_VERSION: engineVersion(),
|
|
783
|
+
// Where this package lives: extensions (copied to the agent dir) load optional
|
|
784
|
+
// native dependencies from here (voice: @picovoice/pvrecorder-node).
|
|
785
|
+
OMNIRUSH_PACKAGE_ROOT: path.resolve(__dirname, ".."),
|
|
745
786
|
// The agent resolves its config dir from <APP>_CODING_AGENT_DIR. The
|
|
746
787
|
// branding patch renames the app to omnirush, so set the omnirush
|
|
747
788
|
// spelling; the pi spelling stays for unpatched cores.
|
|
748
789
|
OMNIRUSH_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
749
790
|
PI_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
791
|
+
// Project settings live in <project>/.omnirush; a project that only
|
|
792
|
+
// has the pre-0.9 folder keeps using that one (the branding patch
|
|
793
|
+
// makes the core read this variable).
|
|
794
|
+
OMNIRUSH_PROJECT_CONFIG_DIR: projectConfigDirName(process.cwd()),
|
|
750
795
|
// The agent core is a fork/rebrand: never phone home to pi.dev.
|
|
751
796
|
// PI_OFFLINE kills startup update checks (the "Update Available —
|
|
752
797
|
// pi.dev/changelog" banner), package updates, and install/update
|
|
@@ -804,6 +849,32 @@ function spawnAgent(cmd, args, opts) {
|
|
|
804
849
|
return child;
|
|
805
850
|
}
|
|
806
851
|
|
|
852
|
+
/** The saved /yolo setting ("yolo": true in <state dir>/settings.json). */
|
|
853
|
+
function yoloSetting() {
|
|
854
|
+
try {
|
|
855
|
+
return JSON.parse(fs.readFileSync(path.join(OMNI_DIR, "settings.json"), "utf8"))?.yolo === true;
|
|
856
|
+
} catch {
|
|
857
|
+
return false;
|
|
858
|
+
}
|
|
859
|
+
}
|
|
860
|
+
|
|
861
|
+
/**
|
|
862
|
+
* Yolo mode for an agent launch: strips --yolo/--no-yolo, adds the core's
|
|
863
|
+
* --approve (project trusted for the run) and hands the state to the
|
|
864
|
+
* extensions (OMNIRUSH_YOLO). Non-interactive runs get the banner on
|
|
865
|
+
* stderr here; the TUI shows it itself (yolo.ts).
|
|
866
|
+
*/
|
|
867
|
+
function launchYolo(args) {
|
|
868
|
+
const resolved = resolveYolo(args, { env: process.env, setting: yoloSetting() });
|
|
869
|
+
const env = { OMNIRUSH_YOLO: resolved.yolo ? "1" : "0", OMNIRUSH_YOLO_SOURCE: resolved.source };
|
|
870
|
+
if (resolved.yolo) {
|
|
871
|
+
env.OMNIRUSH_YOLO_TRUSTED = resolved.trusted ? "1" : "0";
|
|
872
|
+
const headless = resolved.args.some((a) => a === "-p" || a === "--print" || a === "--mode") || !process.stdout.isTTY;
|
|
873
|
+
if (headless) console.error(`omnirush: ${yoloBanner(resolved)}`);
|
|
874
|
+
}
|
|
875
|
+
return { args: resolved.args, env };
|
|
876
|
+
}
|
|
877
|
+
|
|
807
878
|
async function cmdRun() {
|
|
808
879
|
ensureDirs();
|
|
809
880
|
const auth = requireAuth();
|
|
@@ -813,11 +884,13 @@ async function cmdRun() {
|
|
|
813
884
|
const { cmd, args } = await runtimeFor(entry);
|
|
814
885
|
// Plain `omnirush` always runs the SOTA model (charter rule 1); user
|
|
815
886
|
// flags win when present.
|
|
816
|
-
|
|
887
|
+
// `omnirush run` takes no other arguments; only yolo's --approve passes.
|
|
888
|
+
const yolo = launchYolo(process.argv.slice(3).filter((a) => a === "--yolo" || a === "--no-yolo"));
|
|
889
|
+
const piArgs = withModelDefaults(yolo.args, resolveModelDefaults(process.env));
|
|
817
890
|
spawnAgent(cmd, [...args, ...piArgs], {
|
|
818
891
|
stdio: "inherit",
|
|
819
892
|
// The pair may have rotated during launch (model catalog refresh).
|
|
820
|
-
env: { ...process.env, ...childEnv(loadAuth(OMNI_DIR) ?? auth) },
|
|
893
|
+
env: { ...process.env, ...childEnv(loadAuth(OMNI_DIR) ?? auth), ...yolo.env },
|
|
821
894
|
});
|
|
822
895
|
}
|
|
823
896
|
|
|
@@ -854,8 +927,18 @@ Options:
|
|
|
854
927
|
gpt-5.6-sol, meta-muse-spark, muse-spark-1.1;
|
|
855
928
|
effort: minimal..max)
|
|
856
929
|
--thinking <level> reasoning effort override
|
|
930
|
+
--subagent-model <id|same> model the sub-agents run on for this run
|
|
931
|
+
(one of your account's models; default: the
|
|
932
|
+
one picked with /subagents, else the main model)
|
|
933
|
+
--subagent-effort <level|same> sub-agent effort for this run (limited to
|
|
934
|
+
that model's levels)
|
|
857
935
|
--strict-sota exit non-zero if the served model is not
|
|
858
936
|
the requested SOTA model
|
|
937
|
+
--yolo yolo mode for this run: nothing asks, the
|
|
938
|
+
project folder is trusted, every command is
|
|
939
|
+
allowed (default: guarded mode, where git
|
|
940
|
+
writes, destructive commands and sudo ask
|
|
941
|
+
first; --no-yolo turns a saved /yolo on off)
|
|
859
942
|
--session <file> | --continue session options (forwarded to the agent)
|
|
860
943
|
-p, --print "<prompt>" print-mode: answer once and exit
|
|
861
944
|
-h, --help show this help
|
|
@@ -865,6 +948,15 @@ Session commands (inside the agent):
|
|
|
865
948
|
/usage detailed usage from the manager (counters + devices)
|
|
866
949
|
/status session status (identity, model, grant, collector)
|
|
867
950
|
/mcp MCP server state (/mcp reconnect [server])
|
|
951
|
+
/subagents model and effort of the sub-agents (/subagents model <id|same>,
|
|
952
|
+
/subagents effort <level|same>; saved for the next runs)
|
|
953
|
+
/agents sub-agents of this session (/agents cancel <id|all>)
|
|
954
|
+
/jobs background shell commands (/jobs kill <id|all>)
|
|
955
|
+
/voice dictate prompts: hold Space to talk (/voice hold|tap|off|status)
|
|
956
|
+
/mode approval mode: guarded (asks before git writes and destructive
|
|
957
|
+
commands) or yolo (/mode guarded|yolo; saved); /approvals reset
|
|
958
|
+
forgets this session's "allow for this session" answers
|
|
959
|
+
/yolo yolo mode: no prompts, project trusted (/yolo on|off; saved)
|
|
868
960
|
/verify model attestation (arrives with the gateway lane)
|
|
869
961
|
|
|
870
962
|
Environment:
|
|
@@ -872,11 +964,106 @@ Environment:
|
|
|
872
964
|
OMNIRUSH_MODEL default model override (e.g. gpt-6-sol)
|
|
873
965
|
OMNIRUSH_STATIC_MODELS=1 use the shipped model list (no GET /v1/models)
|
|
874
966
|
OMNIRUSH_DIR state dir override (default ~/.omnirush)
|
|
967
|
+
OMNIRUSH_AGENT_DIR agent data dir override (default ~/.omnirush/agent)
|
|
875
968
|
OMNIRUSH_DEBUG=1 verbose diagnostics (raw gateway errors on failure)
|
|
969
|
+
OMNIRUSH_SUBAGENT_MODEL / OMNIRUSH_SUBAGENT_EFFORT
|
|
970
|
+
sub-agent model / effort for this run (as the flags)
|
|
971
|
+
OMNIRUSH_YOLO=1 yolo mode (as --yolo; OMNIRUSH_YOLO=0 turns it off)
|
|
972
|
+
OMNIRUSH_BASH_FOREGROUND_SECONDS
|
|
973
|
+
how long a shell command runs in the foreground before
|
|
974
|
+
it continues in the background (default 300)
|
|
975
|
+
OMNIRUSH_<SETTING> the agent core's own settings (e.g. OMNIRUSH_CACHE_RETENTION;
|
|
976
|
+
the older PI_<SETTING> names still work)
|
|
876
977
|
|
|
877
978
|
Docs: https://omnirush.ai/omnirush — state is stored 0600 in ~/.omnirush`);
|
|
878
979
|
}
|
|
879
980
|
|
|
981
|
+
/**
|
|
982
|
+
* `omnirush update` updates omnirush itself from npm. The agent core's own
|
|
983
|
+
* self-update would install its stock upstream package instead, so only
|
|
984
|
+
* package and model-catalog updates (--extensions, --extension <source>,
|
|
985
|
+
* --models, a package source) are handed to the core; --all does both.
|
|
986
|
+
*/
|
|
987
|
+
async function cmdUpdate(args) {
|
|
988
|
+
if (args.includes("-h") || args.includes("--help")) return runCore(["update", ...args]);
|
|
989
|
+
const coreArgs = [];
|
|
990
|
+
let self = false;
|
|
991
|
+
let coreTarget = false;
|
|
992
|
+
for (let i = 0; i < args.length; i++) {
|
|
993
|
+
const arg = args[i];
|
|
994
|
+
// "pi" is the core's own alias for self: never let it reach the core.
|
|
995
|
+
if (arg === "--self" || arg === "self" || arg === "omnirush" || arg === "pi") self = true;
|
|
996
|
+
else if (arg === "--all") {
|
|
997
|
+
self = true;
|
|
998
|
+
coreTarget = true;
|
|
999
|
+
coreArgs.push("--extensions");
|
|
1000
|
+
} else if (arg === "--force") continue;
|
|
1001
|
+
else {
|
|
1002
|
+
if (arg === "--extensions" || arg === "--models" || arg === "--extension" || !arg.startsWith("-")) coreTarget = true;
|
|
1003
|
+
coreArgs.push(arg);
|
|
1004
|
+
if (arg === "--extension" && i + 1 < args.length) coreArgs.push(args[++i]);
|
|
1005
|
+
}
|
|
1006
|
+
}
|
|
1007
|
+
if (!coreTarget) self = true;
|
|
1008
|
+
if (self) {
|
|
1009
|
+
const status = selfUpdate();
|
|
1010
|
+
if (status !== 0) process.exit(status);
|
|
1011
|
+
}
|
|
1012
|
+
if (coreTarget) await runCore(["update", ...coreArgs]);
|
|
1013
|
+
}
|
|
1014
|
+
|
|
1015
|
+
function selfUpdate() {
|
|
1016
|
+
const { spawnSync } = require("node:child_process");
|
|
1017
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1018
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
1019
|
+
let globalRoot = "";
|
|
1020
|
+
try {
|
|
1021
|
+
globalRoot = String(
|
|
1022
|
+
spawnSync(npm, ["root", "-g"], { encoding: "utf8", shell: process.platform === "win32", timeout: 30_000 }).stdout || "",
|
|
1023
|
+
).trim();
|
|
1024
|
+
} catch {
|
|
1025
|
+
/* npm missing: handled below */
|
|
1026
|
+
}
|
|
1027
|
+
const npmManaged =
|
|
1028
|
+
!!globalRoot && path.relative(path.resolve(globalRoot), packageRoot) === "omnirush";
|
|
1029
|
+
if (!npmManaged) {
|
|
1030
|
+
console.error(
|
|
1031
|
+
`omnirush: this copy (${packageRoot}) was not installed with \`npm install -g omnirush\`. ` +
|
|
1032
|
+
"Update it the way you installed it, or switch to the npm package: npm install -g omnirush@latest",
|
|
1033
|
+
);
|
|
1034
|
+
return 1;
|
|
1035
|
+
}
|
|
1036
|
+
console.log(`Updating omnirush ${ownVersion()}: npm install -g omnirush@latest`);
|
|
1037
|
+
const result = spawnSync(npm, ["install", "-g", "omnirush@latest"], {
|
|
1038
|
+
stdio: "inherit",
|
|
1039
|
+
shell: process.platform === "win32",
|
|
1040
|
+
});
|
|
1041
|
+
if (result.status !== 0) {
|
|
1042
|
+
console.error("omnirush update failed. Run it yourself: npm install -g omnirush@latest");
|
|
1043
|
+
return result.status || 1;
|
|
1044
|
+
}
|
|
1045
|
+
return 0;
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
// Forward everything else (including argv[0] — e.g. `-p prompt`,
|
|
1049
|
+
// `--provider x`, package subcommands) to the agent core, defaulting the
|
|
1050
|
+
// model the same way `omnirush run` does; user-specified --provider/--model
|
|
1051
|
+
// are never duplicated and core subcommands pass through untouched.
|
|
1052
|
+
async function runCore(args) {
|
|
1053
|
+
ensureDirs();
|
|
1054
|
+
const auth = requireAuth();
|
|
1055
|
+
installModelsJson(agentGatewayUrl(auth), await launchModels(agentGatewayUrl(auth), auth.accessToken));
|
|
1056
|
+
installExtensions();
|
|
1057
|
+
const entry = piEntry();
|
|
1058
|
+
const { cmd: bin, args: runtimeArgs } = await runtimeFor(entry);
|
|
1059
|
+
const yolo = launchYolo(args);
|
|
1060
|
+
const piArgs = withModelDefaults(yolo.args, resolveModelDefaults(process.env));
|
|
1061
|
+
spawnAgent(bin, [...runtimeArgs, ...piArgs], {
|
|
1062
|
+
stdio: "inherit",
|
|
1063
|
+
env: { ...process.env, ...childEnv(auth), ...yolo.env },
|
|
1064
|
+
});
|
|
1065
|
+
}
|
|
1066
|
+
|
|
880
1067
|
const [, , ...argv] = process.argv;
|
|
881
1068
|
const cmd = argv[0];
|
|
882
1069
|
if (cmd === "-h" || cmd === "--help" || cmd === "help") printHelp();
|
|
@@ -887,20 +1074,5 @@ else if (cmd === "doctor") await cmdDoctor();
|
|
|
887
1074
|
else if (cmd === "--version" || cmd === "-v") console.log(`omnirush ${ownVersion()}`);
|
|
888
1075
|
else if (cmd === "collect") await cmdCollect(argv.slice(1));
|
|
889
1076
|
else if (cmd === undefined || cmd === "run") await cmdRun();
|
|
890
|
-
else
|
|
891
|
-
|
|
892
|
-
// `--provider x`) to pi, defaulting the model the same way
|
|
893
|
-
// `omnirush run` does; user-specified --provider/--model are never
|
|
894
|
-
// duplicated and pi admin subcommands pass through untouched.
|
|
895
|
-
ensureDirs();
|
|
896
|
-
const auth = requireAuth();
|
|
897
|
-
installModelsJson(agentGatewayUrl(auth), await launchModels(agentGatewayUrl(auth), auth.accessToken));
|
|
898
|
-
installExtensions();
|
|
899
|
-
const entry = piEntry();
|
|
900
|
-
const { cmd: bin, args } = await runtimeFor(entry);
|
|
901
|
-
const piArgs = withModelDefaults(argv, resolveModelDefaults(process.env));
|
|
902
|
-
spawnAgent(bin, [...args, ...piArgs], {
|
|
903
|
-
stdio: "inherit",
|
|
904
|
-
env: { ...process.env, ...childEnv(auth) },
|
|
905
|
-
});
|
|
906
|
-
}
|
|
1077
|
+
else if (cmd === "update") await cmdUpdate(argv.slice(1));
|
|
1078
|
+
else await runCore(argv);
|
package/src/compat.js
ADDED
|
@@ -0,0 +1,272 @@
|
|
|
1
|
+
// Compatibility with installs made before the 0.9.0 rebrand.
|
|
2
|
+
//
|
|
3
|
+
// Up to 0.8.x the agent core kept its data (settings.json, models.json,
|
|
4
|
+
// provider auth.json, sessions/, installed packages) in its upstream
|
|
5
|
+
// default folder, ~/.pi/agent, and read project settings from <project>/.pi.
|
|
6
|
+
// From 0.9.0 everything the user sees is named omnirush:
|
|
7
|
+
//
|
|
8
|
+
// agent data ~/.omnirush/agent (inside the state dir, so
|
|
9
|
+
// OMNIRUSH_DIR moves it too)
|
|
10
|
+
// project config <project>/.omnirush
|
|
11
|
+
// env variables OMNIRUSH_* (the core's PI_* names still work)
|
|
12
|
+
//
|
|
13
|
+
// This module holds the pure pieces the launcher uses to get there
|
|
14
|
+
// without losing anything:
|
|
15
|
+
//
|
|
16
|
+
// resolveAgentDir where the agent data lives this run (and the
|
|
17
|
+
// legacy folder to migrate from)
|
|
18
|
+
// migrateAgentDir one-time COPY of the legacy folder into the new
|
|
19
|
+
// one (the old folder is left untouched, so an
|
|
20
|
+
// older omnirush keeps working); never overwrites,
|
|
21
|
+
// merges the three JSON config files key by key,
|
|
22
|
+
// writes a marker so it runs once, resumes after a
|
|
23
|
+
// crash, and reports failure so the launcher can
|
|
24
|
+
// fall back to the legacy folder for that run
|
|
25
|
+
// projectConfigDirName the project config folder name for a cwd: a
|
|
26
|
+
// project that only has a legacy .pi folder keeps
|
|
27
|
+
// using it
|
|
28
|
+
// engineEnvAliases OMNIRUSH_* spellings of the core's PI_* settings
|
|
29
|
+
|
|
30
|
+
import fs from "node:fs";
|
|
31
|
+
import os from "node:os";
|
|
32
|
+
import path from "node:path";
|
|
33
|
+
|
|
34
|
+
/** Marker written into the new agent dir once the legacy data was copied. */
|
|
35
|
+
export const MIGRATION_MARKER = ".migrated.json";
|
|
36
|
+
|
|
37
|
+
/** Project config folder (the core's configDir after the branding patch). */
|
|
38
|
+
export const PROJECT_CONFIG_DIR = ".omnirush";
|
|
39
|
+
const LEGACY_PROJECT_CONFIG_DIR = ".pi";
|
|
40
|
+
/** Entries that make a project config folder live (the core's trust-gated resources). */
|
|
41
|
+
const PROJECT_RESOURCES = ["settings.json", "extensions", "skills", "prompts", "themes", "SYSTEM.md", "APPEND_SYSTEM.md"];
|
|
42
|
+
|
|
43
|
+
/** Top-level JSON files merged key by key when both folders have them (the new folder's keys win). */
|
|
44
|
+
const MERGED_JSON = new Set(["settings.json", "models.json", "auth.json"]);
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* The core's own PI_* settings a user may set, accepted as OMNIRUSH_<NAME>
|
|
48
|
+
* (the launcher hands them to the core under the name it reads). The
|
|
49
|
+
* original PI_<NAME> keeps working unchanged. PI_OFFLINE / PI_TELEMETRY
|
|
50
|
+
* are always forced by the launcher and are not listed.
|
|
51
|
+
*/
|
|
52
|
+
export const ENGINE_ENV_NAMES = [
|
|
53
|
+
"CACHE_RETENTION",
|
|
54
|
+
"CLEAR_ON_SHRINK",
|
|
55
|
+
"EXPERIMENTAL",
|
|
56
|
+
"HARDWARE_CURSOR",
|
|
57
|
+
"HYPERLINKS",
|
|
58
|
+
"IMAGE_PROTOCOL",
|
|
59
|
+
"OAUTH_CALLBACK_HOST",
|
|
60
|
+
"PACKAGE_DIR",
|
|
61
|
+
"RADIUS_GATEWAY",
|
|
62
|
+
"SHARE_VIEWER_URL",
|
|
63
|
+
"SKIP_VERSION_CHECK",
|
|
64
|
+
"STARTUP_BENCHMARK",
|
|
65
|
+
"TIMING",
|
|
66
|
+
"TRUE_COLOR",
|
|
67
|
+
"TUI_DEBUG",
|
|
68
|
+
"TUI_DEBUG_REDRAW",
|
|
69
|
+
"TUI_ESC_TIMEOUT",
|
|
70
|
+
"TUI_WRITE_LOG",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Where the agent data lives: OMNIRUSH_AGENT_DIR (or its pre-0.9 spelling
|
|
75
|
+
* OMNIRUSH_PI_AGENT_DIR, or the core's own OMNIRUSH_CODING_AGENT_DIR, which
|
|
76
|
+
* nested runs inherit) when set — then nothing is migrated — else
|
|
77
|
+
* <stateDir>/agent, with ~/.pi/agent as the legacy folder to migrate from.
|
|
78
|
+
*/
|
|
79
|
+
export function resolveAgentDir(env = process.env, { stateDir, home = os.homedir() } = {}) {
|
|
80
|
+
const override = String(env.OMNIRUSH_AGENT_DIR || env.OMNIRUSH_PI_AGENT_DIR || env.OMNIRUSH_CODING_AGENT_DIR || "").trim();
|
|
81
|
+
if (override) return { dir: path.resolve(override), legacyDir: null };
|
|
82
|
+
const base = stateDir || path.join(home, ".omnirush");
|
|
83
|
+
return { dir: path.join(base, "agent"), legacyDir: path.join(home, ".pi", "agent") };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function isInside(parent, child, pathImpl) {
|
|
87
|
+
const relative = pathImpl.relative(parent, child);
|
|
88
|
+
return relative === "" || (!!relative && !relative.startsWith("..") && !pathImpl.isAbsolute(relative));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
function writeAtomic(fsImpl, dest, data, mode) {
|
|
92
|
+
const tmp = `${dest}.${process.pid}.migrating`;
|
|
93
|
+
fsImpl.writeFileSync(tmp, data, mode === undefined ? undefined : { mode });
|
|
94
|
+
fsImpl.renameSync(tmp, dest);
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function readJson(fsImpl, file) {
|
|
98
|
+
try {
|
|
99
|
+
const value = JSON.parse(fsImpl.readFileSync(file, "utf8"));
|
|
100
|
+
return value && typeof value === "object" && !Array.isArray(value) ? value : null;
|
|
101
|
+
} catch {
|
|
102
|
+
return null;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/** Old keys fill the gaps of the new file; models.json merges its providers map the same way. */
|
|
107
|
+
function mergeJson(name, older, newer) {
|
|
108
|
+
const merged = { ...older, ...newer };
|
|
109
|
+
if (name === "models.json" && older.providers && typeof older.providers === "object") {
|
|
110
|
+
merged.providers = { ...older.providers, ...(newer.providers || {}) };
|
|
111
|
+
}
|
|
112
|
+
return merged;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A session file's header names its parent session by absolute path; a
|
|
117
|
+
* copied session points at the copied parent instead.
|
|
118
|
+
*/
|
|
119
|
+
export function rebaseSessionHeader(content, from, to, pathImpl = path) {
|
|
120
|
+
const newline = content.indexOf("\n");
|
|
121
|
+
const first = newline === -1 ? content : content.slice(0, newline);
|
|
122
|
+
let header;
|
|
123
|
+
try {
|
|
124
|
+
header = JSON.parse(first);
|
|
125
|
+
} catch {
|
|
126
|
+
return content;
|
|
127
|
+
}
|
|
128
|
+
if (!header || header.type !== "session" || typeof header.parentSession !== "string") return content;
|
|
129
|
+
if (!isInside(from, header.parentSession, pathImpl)) return content;
|
|
130
|
+
header.parentSession = pathImpl.join(to, pathImpl.relative(from, header.parentSession));
|
|
131
|
+
return JSON.stringify(header) + (newline === -1 ? "" : content.slice(newline));
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Copy the legacy agent folder into the new one, once. Never overwrites a
|
|
136
|
+
* file the new folder already has (the three JSON config files are merged
|
|
137
|
+
* instead), keeps modes and timestamps (the core orders sessions by them),
|
|
138
|
+
* skips lock files and our own extension folder (reinstalled at every
|
|
139
|
+
* launch), and writes MIGRATION_MARKER last: an interrupted copy resumes
|
|
140
|
+
* on the next start, a finished one never runs again.
|
|
141
|
+
*
|
|
142
|
+
* Returns { status: "migrated" | "already" | "none" | "failed", copied,
|
|
143
|
+
* merged, skipped, error? }.
|
|
144
|
+
*/
|
|
145
|
+
export function migrateAgentDir({ from, to, fsImpl = fs, pathImpl = path, now = () => new Date(), onStart } = {}) {
|
|
146
|
+
const result = { status: "none", copied: 0, merged: 0, skipped: 0 };
|
|
147
|
+
const marker = pathImpl.join(to, MIGRATION_MARKER);
|
|
148
|
+
if (fsImpl.existsSync(marker)) return { ...result, status: "already" };
|
|
149
|
+
try {
|
|
150
|
+
fsImpl.mkdirSync(to, { recursive: true });
|
|
151
|
+
let legacy = null;
|
|
152
|
+
try {
|
|
153
|
+
legacy = from && pathImpl.resolve(from) !== pathImpl.resolve(to) ? fsImpl.statSync(from) : null;
|
|
154
|
+
} catch {
|
|
155
|
+
legacy = null;
|
|
156
|
+
}
|
|
157
|
+
if (legacy?.isDirectory()) {
|
|
158
|
+
onStart?.();
|
|
159
|
+
copyTree(from, to, "");
|
|
160
|
+
result.status = "migrated";
|
|
161
|
+
}
|
|
162
|
+
const record = { version: 1, from: legacy?.isDirectory() ? from : null, at: now().toISOString(), copied: result.copied, merged: result.merged, skipped: result.skipped };
|
|
163
|
+
writeAtomic(fsImpl, marker, JSON.stringify(record, null, 2) + "\n");
|
|
164
|
+
return result;
|
|
165
|
+
} catch (error) {
|
|
166
|
+
return { ...result, status: "failed", error };
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function copyTree(srcDir, destDir, rel) {
|
|
170
|
+
for (const entry of fsImpl.readdirSync(srcDir, { withFileTypes: true })) {
|
|
171
|
+
const name = entry.name;
|
|
172
|
+
const relPath = rel ? `${rel}/${name}` : name;
|
|
173
|
+
if (name === MIGRATION_MARKER || name.endsWith(".lock") || name.endsWith(".migrating")) continue;
|
|
174
|
+
if (relPath === "extensions/omnirush") continue;
|
|
175
|
+
const src = pathImpl.join(srcDir, name);
|
|
176
|
+
const dest = pathImpl.join(destDir, name);
|
|
177
|
+
const stat = fsImpl.lstatSync(src);
|
|
178
|
+
if (stat.isDirectory()) {
|
|
179
|
+
fsImpl.mkdirSync(dest, { recursive: true });
|
|
180
|
+
copyTree(src, dest, relPath);
|
|
181
|
+
continue;
|
|
182
|
+
}
|
|
183
|
+
const exists = (() => {
|
|
184
|
+
try {
|
|
185
|
+
fsImpl.lstatSync(dest);
|
|
186
|
+
return true;
|
|
187
|
+
} catch {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
})();
|
|
191
|
+
if (stat.isSymbolicLink()) {
|
|
192
|
+
if (exists) {
|
|
193
|
+
result.skipped++;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
try {
|
|
197
|
+
fsImpl.symlinkSync(fsImpl.readlinkSync(src), dest);
|
|
198
|
+
result.copied++;
|
|
199
|
+
} catch {
|
|
200
|
+
result.skipped++; // e.g. Windows without symlink rights
|
|
201
|
+
}
|
|
202
|
+
continue;
|
|
203
|
+
}
|
|
204
|
+
if (!stat.isFile()) continue;
|
|
205
|
+
if (exists) {
|
|
206
|
+
if (!rel && MERGED_JSON.has(name)) {
|
|
207
|
+
const older = readJson(fsImpl, src);
|
|
208
|
+
const newer = readJson(fsImpl, dest);
|
|
209
|
+
if (older && newer) {
|
|
210
|
+
const merged = mergeJson(name, older, newer);
|
|
211
|
+
if (JSON.stringify(merged) !== JSON.stringify(newer)) {
|
|
212
|
+
writeAtomic(fsImpl, dest, JSON.stringify(merged, null, 2) + "\n", fsImpl.statSync(dest).mode & 0o777);
|
|
213
|
+
result.merged++;
|
|
214
|
+
continue;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
result.skipped++;
|
|
219
|
+
continue;
|
|
220
|
+
}
|
|
221
|
+
const tmp = `${dest}.${process.pid}.migrating`;
|
|
222
|
+
if (relPath.startsWith("sessions/") && name.endsWith(".jsonl")) {
|
|
223
|
+
const content = fsImpl.readFileSync(src, "utf8");
|
|
224
|
+
fsImpl.writeFileSync(tmp, rebaseSessionHeader(content, from, to, pathImpl));
|
|
225
|
+
} else {
|
|
226
|
+
fsImpl.copyFileSync(src, tmp);
|
|
227
|
+
}
|
|
228
|
+
try {
|
|
229
|
+
fsImpl.chmodSync(tmp, stat.mode & 0o777);
|
|
230
|
+
} catch {
|
|
231
|
+
/* modes are advisory on Windows */
|
|
232
|
+
}
|
|
233
|
+
fsImpl.utimesSync(tmp, stat.atime, stat.mtime);
|
|
234
|
+
fsImpl.renameSync(tmp, dest);
|
|
235
|
+
result.copied++;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
/**
|
|
241
|
+
* The project config folder the core should read in `cwd`: .omnirush, or
|
|
242
|
+
* the legacy .pi when the project has one with settings or resources and
|
|
243
|
+
* no .omnirush yet (the launcher passes it as OMNIRUSH_PROJECT_CONFIG_DIR).
|
|
244
|
+
*/
|
|
245
|
+
export function projectConfigDirName(cwd, fsImpl = fs) {
|
|
246
|
+
try {
|
|
247
|
+
if (fsImpl.existsSync(path.join(cwd, PROJECT_CONFIG_DIR))) return PROJECT_CONFIG_DIR;
|
|
248
|
+
const legacy = path.join(cwd, LEGACY_PROJECT_CONFIG_DIR);
|
|
249
|
+
if (PROJECT_RESOURCES.some((entry) => fsImpl.existsSync(path.join(legacy, entry)))) return LEGACY_PROJECT_CONFIG_DIR;
|
|
250
|
+
} catch {
|
|
251
|
+
/* unreadable cwd: the default */
|
|
252
|
+
}
|
|
253
|
+
return PROJECT_CONFIG_DIR;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/**
|
|
257
|
+
* The core-facing variables for the OMNIRUSH_* spellings the user set:
|
|
258
|
+
* OMNIRUSH_<NAME> -> PI_<NAME> (the OMNIRUSH_ value wins over a PI_ one),
|
|
259
|
+
* and the pre-0.9 PI_CODING_AGENT_SESSION_DIR -> the OMNIRUSH_ name the
|
|
260
|
+
* rebranded core reads.
|
|
261
|
+
*/
|
|
262
|
+
export function engineEnvAliases(env = process.env) {
|
|
263
|
+
const out = {};
|
|
264
|
+
for (const name of ENGINE_ENV_NAMES) {
|
|
265
|
+
const value = env[`OMNIRUSH_${name}`];
|
|
266
|
+
if (value !== undefined && value !== "") out[`PI_${name}`] = value;
|
|
267
|
+
}
|
|
268
|
+
if (env.PI_CODING_AGENT_SESSION_DIR && !env.OMNIRUSH_CODING_AGENT_SESSION_DIR) {
|
|
269
|
+
out.OMNIRUSH_CODING_AGENT_SESSION_DIR = env.PI_CODING_AGENT_SESSION_DIR;
|
|
270
|
+
}
|
|
271
|
+
return out;
|
|
272
|
+
}
|