omnirush 0.8.6 → 0.9.1
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 +120 -0
- package/assets/extensions/omnirush/agents-lib.ts +501 -63
- package/assets/extensions/omnirush/agents.ts +140 -115
- package/assets/extensions/omnirush/bgshell-lib.ts +432 -0
- package/assets/extensions/omnirush/bgshell.ts +392 -0
- package/assets/extensions/omnirush/capture/workspace-collector.ts +86 -22
- package/assets/extensions/omnirush/collector.ts +275 -43
- 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 +32 -6
- package/assets/extensions/omnirush/mcp.ts +103 -8
- package/assets/extensions/omnirush/memory-lib.ts +516 -0
- package/assets/extensions/omnirush/pi-engine.ts +115 -3
- package/assets/extensions/omnirush/plan-lib.ts +38 -9
- package/assets/extensions/omnirush/plan.ts +85 -30
- package/assets/extensions/omnirush/sota.ts +52 -4
- package/assets/extensions/omnirush/status-lib.ts +3 -0
- package/assets/extensions/omnirush/subagent-marker.ts +21 -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 +33 -1
- package/src/bin.js +232 -33
- package/src/compat.js +272 -0
- package/src/lib.js +64 -0
- package/src/sessions.js +222 -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,12 @@ 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";
|
|
58
|
+
import { customSessionDir, moveLegacySubagentSessions } from "./sessions.js";
|
|
52
59
|
import { ensureRuntime, managedBunPath } from "../scripts/postinstall.js";
|
|
53
60
|
import {
|
|
54
61
|
clearAuth,
|
|
@@ -70,14 +77,43 @@ import {
|
|
|
70
77
|
const require = createRequire(import.meta.url);
|
|
71
78
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
72
79
|
const OMNI_DIR = omniDir();
|
|
73
|
-
|
|
74
|
-
//
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
80
|
+
process.title = "omnirush";
|
|
81
|
+
// The agent core's data (settings, models.json, provider auth, sessions,
|
|
82
|
+
// extensions): <state dir>/agent, or OMNIRUSH_AGENT_DIR. Releases up to
|
|
83
|
+
// 0.8.x used the core's upstream default folder; ensureDirs() copies it
|
|
84
|
+
// over once (src/compat.js) and falls back to it for a run whose copy failed.
|
|
85
|
+
const AGENT = resolveAgentDir(process.env, { stateDir: OMNI_DIR });
|
|
86
|
+
let PI_AGENT_DIR, PI_MODELS_JSON, PI_SETTINGS_JSON, PI_EXT_DIR;
|
|
87
|
+
function useAgentDir(dir) {
|
|
88
|
+
PI_AGENT_DIR = dir;
|
|
89
|
+
PI_MODELS_JSON = path.join(dir, "models.json");
|
|
90
|
+
PI_SETTINGS_JSON = path.join(dir, "settings.json");
|
|
91
|
+
PI_EXT_DIR = path.join(dir, "extensions", "omnirush");
|
|
92
|
+
}
|
|
93
|
+
useAgentDir(AGENT.dir);
|
|
94
|
+
|
|
95
|
+
/** One-time copy of the pre-0.9 agent data into the new folder (see src/compat.js). */
|
|
96
|
+
function migrateLegacyAgentData() {
|
|
97
|
+
if (!AGENT.legacyDir) return;
|
|
98
|
+
const result = migrateAgentDir({
|
|
99
|
+
from: AGENT.legacyDir,
|
|
100
|
+
to: AGENT.dir,
|
|
101
|
+
onStart: () => console.error(`omnirush: copying your sessions and agent settings to ${AGENT.dir} (one time)...`),
|
|
102
|
+
});
|
|
103
|
+
if (result.status === "migrated") {
|
|
104
|
+
console.error(
|
|
105
|
+
`omnirush: done (${result.copied} files copied${result.merged ? `, ${result.merged} merged` : ""}; ` +
|
|
106
|
+
"the previous folder was left as it was).",
|
|
107
|
+
);
|
|
108
|
+
} else if (result.status === "failed") {
|
|
109
|
+
// Never lose data: run on the previous folder and retry next launch.
|
|
110
|
+
console.error(
|
|
111
|
+
`omnirush: could not copy your agent data to ${AGENT.dir} (${result.error?.message ?? result.error}); ` +
|
|
112
|
+
"using the previous folder for this run, retrying next time.",
|
|
113
|
+
);
|
|
114
|
+
useAgentDir(AGENT.legacyDir);
|
|
115
|
+
}
|
|
116
|
+
}
|
|
81
117
|
|
|
82
118
|
function managerOrigin() {
|
|
83
119
|
return (
|
|
@@ -111,11 +147,12 @@ function piPackage() {
|
|
|
111
147
|
function piEntry() {
|
|
112
148
|
const found = piPackage();
|
|
113
149
|
if (found?.pkg?.bin?.pi) return path.join(found.dir, found.pkg.bin.pi);
|
|
114
|
-
throw new Error("could not locate the pinned
|
|
150
|
+
throw new Error("could not locate the pinned agent core (broken install). Fix: npm install -g omnirush@latest");
|
|
115
151
|
}
|
|
116
152
|
|
|
117
153
|
function ensureDirs() {
|
|
118
154
|
fs.mkdirSync(OMNI_DIR, { recursive: true, mode: 0o700 });
|
|
155
|
+
migrateLegacyAgentData();
|
|
119
156
|
fs.mkdirSync(PI_AGENT_DIR, { recursive: true });
|
|
120
157
|
fs.mkdirSync(PI_EXT_DIR, { recursive: true });
|
|
121
158
|
try {
|
|
@@ -126,6 +163,23 @@ function ensureDirs() {
|
|
|
126
163
|
installDefaultThinkingLevel();
|
|
127
164
|
}
|
|
128
165
|
|
|
166
|
+
/**
|
|
167
|
+
* Sub-agent sessions older releases wrote next to the user's are moved to
|
|
168
|
+
* their session dir's subagents/ folder before the core picks a session,
|
|
169
|
+
* so /resume lists only the user's sessions and --continue resumes the
|
|
170
|
+
* user's last one (src/sessions.js). Not on a run that fell back to the
|
|
171
|
+
* pre-0.9 folder, which is left as it was.
|
|
172
|
+
*/
|
|
173
|
+
function tidySubagentSessions(args) {
|
|
174
|
+
if (AGENT.legacyDir && PI_AGENT_DIR === AGENT.legacyDir) return;
|
|
175
|
+
try {
|
|
176
|
+
const custom = customSessionDir(args, process.env, PI_SETTINGS_JSON, os.homedir());
|
|
177
|
+
moveLegacySubagentSessions({ agentDir: PI_AGENT_DIR, extraDirs: custom ? [custom] : [] });
|
|
178
|
+
} catch {
|
|
179
|
+
/* best effort */
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
129
183
|
// Product default effort (P2): xhigh. Fills an ABSENT
|
|
130
184
|
// defaultThinkingLevel in the agent settings only — an explicit user
|
|
131
185
|
// choice always wins; the models' thinkingLevelMap is untouched.
|
|
@@ -233,7 +287,7 @@ function installExtensions() {
|
|
|
233
287
|
fs.mkdirSync(to, { recursive: true });
|
|
234
288
|
for (const entry of fs.readdirSync(from, { withFileTypes: true })) {
|
|
235
289
|
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")) {
|
|
290
|
+
else if (entry.name.endsWith(".ts") || entry.name.endsWith(".js") || entry.name.endsWith(".cjs")) {
|
|
237
291
|
fs.copyFileSync(path.join(from, entry.name), path.join(to, entry.name));
|
|
238
292
|
}
|
|
239
293
|
}
|
|
@@ -729,6 +783,8 @@ function childEnv(auth) {
|
|
|
729
783
|
const origin = (process.env.OMNIRUSH_ORIGIN || "").trim().replace(/\/+$/, "") ||
|
|
730
784
|
gateway.replace(/\/+$/, "").replace(/\/v1$/, ""); // provider base -> manager origin
|
|
731
785
|
return {
|
|
786
|
+
// OMNIRUSH_* spellings of the core's own settings (PI_* still work).
|
|
787
|
+
...engineEnvAliases(process.env),
|
|
732
788
|
// Bootstrap values only: the agent (and every sub-agent it spawns)
|
|
733
789
|
// re-reads the shared auth file in OMNIRUSH_DIR for each request, so
|
|
734
790
|
// these never go stale in a way that matters.
|
|
@@ -738,15 +794,22 @@ function childEnv(auth) {
|
|
|
738
794
|
OMNIRUSH_GATEWAY_URL: gateway,
|
|
739
795
|
OMNIRUSH_DIR: OMNI_DIR,
|
|
740
796
|
// /status shows the CLI version; extensions have no package.json of
|
|
741
|
-
// their own in
|
|
797
|
+
// their own in the agent dir, so the launcher hands it over.
|
|
742
798
|
OMNIRUSH_VERSION: ownVersion(),
|
|
743
799
|
// The collector reports it as the environment's engine_version.
|
|
744
800
|
OMNIRUSH_ENGINE_VERSION: engineVersion(),
|
|
801
|
+
// Where this package lives: extensions (copied to the agent dir) load optional
|
|
802
|
+
// native dependencies from here (voice: @picovoice/pvrecorder-node).
|
|
803
|
+
OMNIRUSH_PACKAGE_ROOT: path.resolve(__dirname, ".."),
|
|
745
804
|
// The agent resolves its config dir from <APP>_CODING_AGENT_DIR. The
|
|
746
805
|
// branding patch renames the app to omnirush, so set the omnirush
|
|
747
806
|
// spelling; the pi spelling stays for unpatched cores.
|
|
748
807
|
OMNIRUSH_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
749
808
|
PI_CODING_AGENT_DIR: PI_AGENT_DIR,
|
|
809
|
+
// Project settings live in <project>/.omnirush; a project that only
|
|
810
|
+
// has the pre-0.9 folder keeps using that one (the branding patch
|
|
811
|
+
// makes the core read this variable).
|
|
812
|
+
OMNIRUSH_PROJECT_CONFIG_DIR: projectConfigDirName(process.cwd()),
|
|
750
813
|
// The agent core is a fork/rebrand: never phone home to pi.dev.
|
|
751
814
|
// PI_OFFLINE kills startup update checks (the "Update Available —
|
|
752
815
|
// pi.dev/changelog" banner), package updates, and install/update
|
|
@@ -804,6 +867,32 @@ function spawnAgent(cmd, args, opts) {
|
|
|
804
867
|
return child;
|
|
805
868
|
}
|
|
806
869
|
|
|
870
|
+
/** The saved /yolo setting ("yolo": true in <state dir>/settings.json). */
|
|
871
|
+
function yoloSetting() {
|
|
872
|
+
try {
|
|
873
|
+
return JSON.parse(fs.readFileSync(path.join(OMNI_DIR, "settings.json"), "utf8"))?.yolo === true;
|
|
874
|
+
} catch {
|
|
875
|
+
return false;
|
|
876
|
+
}
|
|
877
|
+
}
|
|
878
|
+
|
|
879
|
+
/**
|
|
880
|
+
* Yolo mode for an agent launch: strips --yolo/--no-yolo, adds the core's
|
|
881
|
+
* --approve (project trusted for the run) and hands the state to the
|
|
882
|
+
* extensions (OMNIRUSH_YOLO). Non-interactive runs get the banner on
|
|
883
|
+
* stderr here; the TUI shows it itself (yolo.ts).
|
|
884
|
+
*/
|
|
885
|
+
function launchYolo(args) {
|
|
886
|
+
const resolved = resolveYolo(args, { env: process.env, setting: yoloSetting() });
|
|
887
|
+
const env = { OMNIRUSH_YOLO: resolved.yolo ? "1" : "0", OMNIRUSH_YOLO_SOURCE: resolved.source };
|
|
888
|
+
if (resolved.yolo) {
|
|
889
|
+
env.OMNIRUSH_YOLO_TRUSTED = resolved.trusted ? "1" : "0";
|
|
890
|
+
const headless = resolved.args.some((a) => a === "-p" || a === "--print" || a === "--mode") || !process.stdout.isTTY;
|
|
891
|
+
if (headless) console.error(`omnirush: ${yoloBanner(resolved)}`);
|
|
892
|
+
}
|
|
893
|
+
return { args: resolved.args, env };
|
|
894
|
+
}
|
|
895
|
+
|
|
807
896
|
async function cmdRun() {
|
|
808
897
|
ensureDirs();
|
|
809
898
|
const auth = requireAuth();
|
|
@@ -813,11 +902,14 @@ async function cmdRun() {
|
|
|
813
902
|
const { cmd, args } = await runtimeFor(entry);
|
|
814
903
|
// Plain `omnirush` always runs the SOTA model (charter rule 1); user
|
|
815
904
|
// flags win when present.
|
|
816
|
-
|
|
905
|
+
// `omnirush run` takes no other arguments; only yolo's --approve passes.
|
|
906
|
+
const yolo = launchYolo(process.argv.slice(3).filter((a) => a === "--yolo" || a === "--no-yolo"));
|
|
907
|
+
tidySubagentSessions([]);
|
|
908
|
+
const piArgs = withModelDefaults(yolo.args, resolveModelDefaults(process.env));
|
|
817
909
|
spawnAgent(cmd, [...args, ...piArgs], {
|
|
818
910
|
stdio: "inherit",
|
|
819
911
|
// The pair may have rotated during launch (model catalog refresh).
|
|
820
|
-
env: { ...process.env, ...childEnv(loadAuth(OMNI_DIR) ?? auth) },
|
|
912
|
+
env: { ...process.env, ...childEnv(loadAuth(OMNI_DIR) ?? auth), ...yolo.env },
|
|
821
913
|
});
|
|
822
914
|
}
|
|
823
915
|
|
|
@@ -854,8 +946,18 @@ Options:
|
|
|
854
946
|
gpt-5.6-sol, meta-muse-spark, muse-spark-1.1;
|
|
855
947
|
effort: minimal..max)
|
|
856
948
|
--thinking <level> reasoning effort override
|
|
949
|
+
--subagent-model <id|same> model the sub-agents run on for this run
|
|
950
|
+
(one of your account's models; default: the
|
|
951
|
+
one picked with /subagents, else the main model)
|
|
952
|
+
--subagent-effort <level|same> sub-agent effort for this run (limited to
|
|
953
|
+
that model's levels)
|
|
857
954
|
--strict-sota exit non-zero if the served model is not
|
|
858
955
|
the requested SOTA model
|
|
956
|
+
--yolo yolo mode for this run: nothing asks, the
|
|
957
|
+
project folder is trusted, every command is
|
|
958
|
+
allowed (default: guarded mode, where git
|
|
959
|
+
writes, destructive commands and sudo ask
|
|
960
|
+
first; --no-yolo turns a saved /yolo on off)
|
|
859
961
|
--session <file> | --continue session options (forwarded to the agent)
|
|
860
962
|
-p, --print "<prompt>" print-mode: answer once and exit
|
|
861
963
|
-h, --help show this help
|
|
@@ -865,6 +967,16 @@ Session commands (inside the agent):
|
|
|
865
967
|
/usage detailed usage from the manager (counters + devices)
|
|
866
968
|
/status session status (identity, model, grant, collector)
|
|
867
969
|
/mcp MCP server state (/mcp reconnect [server])
|
|
970
|
+
/subagents model and effort of the sub-agents (/subagents model <id|same>,
|
|
971
|
+
/subagents effort <level|same>; saved for the next runs)
|
|
972
|
+
/agents sub-agents of this session (/agents cancel <id|all>,
|
|
973
|
+
/agents memory: the memory they may use)
|
|
974
|
+
/jobs background shell commands (/jobs kill <id|all>)
|
|
975
|
+
/voice dictate prompts: hold Space to talk (/voice hold|tap|off|status)
|
|
976
|
+
/mode approval mode: guarded (asks before git writes and destructive
|
|
977
|
+
commands) or yolo (/mode guarded|yolo; saved); /approvals reset
|
|
978
|
+
forgets this session's "allow for this session" answers
|
|
979
|
+
/yolo yolo mode: no prompts, project trusted (/yolo on|off; saved)
|
|
868
980
|
/verify model attestation (arrives with the gateway lane)
|
|
869
981
|
|
|
870
982
|
Environment:
|
|
@@ -872,11 +984,113 @@ Environment:
|
|
|
872
984
|
OMNIRUSH_MODEL default model override (e.g. gpt-6-sol)
|
|
873
985
|
OMNIRUSH_STATIC_MODELS=1 use the shipped model list (no GET /v1/models)
|
|
874
986
|
OMNIRUSH_DIR state dir override (default ~/.omnirush)
|
|
987
|
+
OMNIRUSH_AGENT_DIR agent data dir override (default ~/.omnirush/agent)
|
|
875
988
|
OMNIRUSH_DEBUG=1 verbose diagnostics (raw gateway errors on failure)
|
|
989
|
+
OMNIRUSH_SUBAGENT_MODEL / OMNIRUSH_SUBAGENT_EFFORT
|
|
990
|
+
sub-agent model / effort for this run (as the flags)
|
|
991
|
+
OMNIRUSH_YOLO=1 yolo mode (as --yolo; OMNIRUSH_YOLO=0 turns it off)
|
|
992
|
+
OMNIRUSH_BASH_FOREGROUND_SECONDS
|
|
993
|
+
how long a shell command runs in the foreground before
|
|
994
|
+
it continues in the background (default 300)
|
|
995
|
+
OMNIRUSH_AGENT_MIN_FREE / OMNIRUSH_AGENT_MEMORY_BUDGET / OMNIRUSH_AGENT_CRITICAL_FREE
|
|
996
|
+
sub-agents start while this much RAM stays free
|
|
997
|
+
(default 20%), all agents stay under this budget
|
|
998
|
+
(default 50% of RAM), and none start below this
|
|
999
|
+
(default 10%); e.g. 25% or 6G. OMNIRUSH_AGENT_MEMORY=off
|
|
1000
|
+
starts them without checking memory
|
|
1001
|
+
OMNIRUSH_<SETTING> the agent core's own settings (e.g. OMNIRUSH_CACHE_RETENTION;
|
|
1002
|
+
the older PI_<SETTING> names still work)
|
|
876
1003
|
|
|
877
1004
|
Docs: https://omnirush.ai/omnirush — state is stored 0600 in ~/.omnirush`);
|
|
878
1005
|
}
|
|
879
1006
|
|
|
1007
|
+
/**
|
|
1008
|
+
* `omnirush update` updates omnirush itself from npm. The agent core's own
|
|
1009
|
+
* self-update would install its stock upstream package instead, so only
|
|
1010
|
+
* package and model-catalog updates (--extensions, --extension <source>,
|
|
1011
|
+
* --models, a package source) are handed to the core; --all does both.
|
|
1012
|
+
*/
|
|
1013
|
+
async function cmdUpdate(args) {
|
|
1014
|
+
if (args.includes("-h") || args.includes("--help")) return runCore(["update", ...args]);
|
|
1015
|
+
const coreArgs = [];
|
|
1016
|
+
let self = false;
|
|
1017
|
+
let coreTarget = false;
|
|
1018
|
+
for (let i = 0; i < args.length; i++) {
|
|
1019
|
+
const arg = args[i];
|
|
1020
|
+
// "pi" is the core's own alias for self: never let it reach the core.
|
|
1021
|
+
if (arg === "--self" || arg === "self" || arg === "omnirush" || arg === "pi") self = true;
|
|
1022
|
+
else if (arg === "--all") {
|
|
1023
|
+
self = true;
|
|
1024
|
+
coreTarget = true;
|
|
1025
|
+
coreArgs.push("--extensions");
|
|
1026
|
+
} else if (arg === "--force") continue;
|
|
1027
|
+
else {
|
|
1028
|
+
if (arg === "--extensions" || arg === "--models" || arg === "--extension" || !arg.startsWith("-")) coreTarget = true;
|
|
1029
|
+
coreArgs.push(arg);
|
|
1030
|
+
if (arg === "--extension" && i + 1 < args.length) coreArgs.push(args[++i]);
|
|
1031
|
+
}
|
|
1032
|
+
}
|
|
1033
|
+
if (!coreTarget) self = true;
|
|
1034
|
+
if (self) {
|
|
1035
|
+
const status = selfUpdate();
|
|
1036
|
+
if (status !== 0) process.exit(status);
|
|
1037
|
+
}
|
|
1038
|
+
if (coreTarget) await runCore(["update", ...coreArgs]);
|
|
1039
|
+
}
|
|
1040
|
+
|
|
1041
|
+
function selfUpdate() {
|
|
1042
|
+
const { spawnSync } = require("node:child_process");
|
|
1043
|
+
const npm = process.platform === "win32" ? "npm.cmd" : "npm";
|
|
1044
|
+
const packageRoot = path.resolve(__dirname, "..");
|
|
1045
|
+
let globalRoot = "";
|
|
1046
|
+
try {
|
|
1047
|
+
globalRoot = String(
|
|
1048
|
+
spawnSync(npm, ["root", "-g"], { encoding: "utf8", shell: process.platform === "win32", timeout: 30_000 }).stdout || "",
|
|
1049
|
+
).trim();
|
|
1050
|
+
} catch {
|
|
1051
|
+
/* npm missing: handled below */
|
|
1052
|
+
}
|
|
1053
|
+
const npmManaged =
|
|
1054
|
+
!!globalRoot && path.relative(path.resolve(globalRoot), packageRoot) === "omnirush";
|
|
1055
|
+
if (!npmManaged) {
|
|
1056
|
+
console.error(
|
|
1057
|
+
`omnirush: this copy (${packageRoot}) was not installed with \`npm install -g omnirush\`. ` +
|
|
1058
|
+
"Update it the way you installed it, or switch to the npm package: npm install -g omnirush@latest",
|
|
1059
|
+
);
|
|
1060
|
+
return 1;
|
|
1061
|
+
}
|
|
1062
|
+
console.log(`Updating omnirush ${ownVersion()}: npm install -g omnirush@latest`);
|
|
1063
|
+
const result = spawnSync(npm, ["install", "-g", "omnirush@latest"], {
|
|
1064
|
+
stdio: "inherit",
|
|
1065
|
+
shell: process.platform === "win32",
|
|
1066
|
+
});
|
|
1067
|
+
if (result.status !== 0) {
|
|
1068
|
+
console.error("omnirush update failed. Run it yourself: npm install -g omnirush@latest");
|
|
1069
|
+
return result.status || 1;
|
|
1070
|
+
}
|
|
1071
|
+
return 0;
|
|
1072
|
+
}
|
|
1073
|
+
|
|
1074
|
+
// Forward everything else (including argv[0] — e.g. `-p prompt`,
|
|
1075
|
+
// `--provider x`, package subcommands) to the agent core, defaulting the
|
|
1076
|
+
// model the same way `omnirush run` does; user-specified --provider/--model
|
|
1077
|
+
// are never duplicated and core subcommands pass through untouched.
|
|
1078
|
+
async function runCore(args) {
|
|
1079
|
+
ensureDirs();
|
|
1080
|
+
const auth = requireAuth();
|
|
1081
|
+
installModelsJson(agentGatewayUrl(auth), await launchModels(agentGatewayUrl(auth), auth.accessToken));
|
|
1082
|
+
installExtensions();
|
|
1083
|
+
const entry = piEntry();
|
|
1084
|
+
const { cmd: bin, args: runtimeArgs } = await runtimeFor(entry);
|
|
1085
|
+
const yolo = launchYolo(args);
|
|
1086
|
+
const piArgs = withModelDefaults(yolo.args, resolveModelDefaults(process.env));
|
|
1087
|
+
tidySubagentSessions(piArgs);
|
|
1088
|
+
spawnAgent(bin, [...runtimeArgs, ...piArgs], {
|
|
1089
|
+
stdio: "inherit",
|
|
1090
|
+
env: { ...process.env, ...childEnv(auth), ...yolo.env },
|
|
1091
|
+
});
|
|
1092
|
+
}
|
|
1093
|
+
|
|
880
1094
|
const [, , ...argv] = process.argv;
|
|
881
1095
|
const cmd = argv[0];
|
|
882
1096
|
if (cmd === "-h" || cmd === "--help" || cmd === "help") printHelp();
|
|
@@ -887,20 +1101,5 @@ else if (cmd === "doctor") await cmdDoctor();
|
|
|
887
1101
|
else if (cmd === "--version" || cmd === "-v") console.log(`omnirush ${ownVersion()}`);
|
|
888
1102
|
else if (cmd === "collect") await cmdCollect(argv.slice(1));
|
|
889
1103
|
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
|
-
}
|
|
1104
|
+
else if (cmd === "update") await cmdUpdate(argv.slice(1));
|
|
1105
|
+
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
|
+
}
|