omnirush 0.5.4 → 0.6.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.
@@ -1520,8 +1520,17 @@ export class WorkspaceCollector {
1520
1520
  }
1521
1521
  this.enqueue(state, async () => {
1522
1522
  if (state.budgetExhausted || state.finished) return;
1523
+ // The JOURNAL is what holds unshipped change records: a scan
1524
+ // captures edits and advances lastSignature in the same breath,
1525
+ // so by the time this debounced flush runs (it can queue behind a
1526
+ // long multi-part baseline) the workspace signature often equals
1527
+ // lastSignature again and a signature-only guard would skip
1528
+ // forever, stranding the journal until session end. Ship whenever
1529
+ // unacked journal records exist; the signature is only the
1530
+ // trigger for captures that have not been journalled yet.
1531
+ const unackedJournal = state.journalAppendedBytes > state.journalAckOffset;
1523
1532
  const signature = await workspaceSignature(state.root);
1524
- if (!signature || signature === state.lastSignature) return;
1533
+ if (!unackedJournal && (!signature || signature === state.lastSignature)) return;
1525
1534
  state.lastSignature = signature;
1526
1535
  await this.uploadWorkspace(state, "change");
1527
1536
  });
@@ -1859,12 +1868,31 @@ export class WorkspaceCollector {
1859
1868
  paths: string[],
1860
1869
  resumeFrom?: string | null,
1861
1870
  ): AsyncGenerator<CollectorFile> {
1871
+ // The cursor can name a slice or manifest virtual path
1872
+ // (`<path>.__agent_partN`, `<path>.__agent_manifest.json`) when the
1873
+ // acked part ended mid-file. Comparing a virtual cursor against the
1874
+ // real path list would never match and `path <= cursor` would
1875
+ // swallow the whole base file (silent data loss on resume), so
1876
+ // paths before the cursor's base file are skipped outright and the
1877
+ // base file's own entries are filtered against the cursor.
1878
+ const cursor = resumeFrom ?? null;
1879
+ const cursorBase = cursor
1880
+ ? cursor.replace(/\.__agent(?:_part\d+|_manifest\.json)$/, "")
1881
+ : null;
1862
1882
  for (const path of paths) {
1863
1883
  if (state.budgetExhausted) return;
1864
- if (resumeFrom && comparePaths(path, resumeFrom) <= 0) continue;
1865
- for await (const entry of iterateWorkspaceEntries(state.root, path)) {
1866
- yield entry;
1884
+ if (cursorBase) {
1885
+ const order = comparePaths(path, cursorBase);
1886
+ if (order < 0) continue;
1887
+ if (order === 0) {
1888
+ for await (const entry of iterateWorkspaceEntries(state.root, path)) {
1889
+ if (entry.path && comparePaths(entry.path, cursor!) <= 0) continue;
1890
+ yield entry;
1891
+ }
1892
+ continue;
1893
+ }
1867
1894
  }
1895
+ yield* iterateWorkspaceEntries(state.root, path);
1868
1896
  }
1869
1897
  }
1870
1898
 
@@ -2210,7 +2238,6 @@ export class WorkspaceCollector {
2210
2238
  size,
2211
2239
  mtimeMs,
2212
2240
  });
2213
- state.transcripts[filePath] = { size, mtimeMs };
2214
2241
  }
2215
2242
  if (artifacts.length === 0) {
2216
2243
  await this.persistSession(state).catch(() => undefined);
@@ -2223,13 +2250,31 @@ export class WorkspaceCollector {
2223
2250
  const payload = buildEnvelopePayload(bySequence(), "trace", batch);
2224
2251
  return { files: batch, payload, compressed: await compressZstd(payload) };
2225
2252
  },
2226
- upload: async (measured) => this.uploadEnvelope(state, "trace", measured.files, measured),
2253
+ upload: async (measured) => {
2254
+ // Mark a transcript seen only after its part is accepted — an
2255
+ // ack-before-success let a single 503 permanently drop the
2256
+ // transcript from every later flush (the ledger said captured).
2257
+ const ok = await this.uploadEnvelope(state, "trace", measured.files, measured);
2258
+ if (ok) {
2259
+ for (const artifact of measured.files) {
2260
+ state.transcripts[artifact.fileKey] = {
2261
+ size: artifact.size,
2262
+ mtimeMs: artifact.mtimeMs,
2263
+ };
2264
+ }
2265
+ }
2266
+ return ok;
2267
+ },
2227
2268
  drop: (artifact) => {
2228
2269
  this.log("warn", "OmniRush transcript artifact exceeds the upload limits; skipped", {
2229
2270
  sessionId: state.id,
2230
2271
  path: artifact.path,
2231
2272
  bytes: Buffer.byteLength(artifact.content),
2232
2273
  });
2274
+ state.transcripts[artifact.fileKey] = {
2275
+ size: artifact.size,
2276
+ mtimeMs: artifact.mtimeMs,
2277
+ };
2233
2278
  },
2234
2279
  }, { checkBytes: PART_SIDECAR_PLAIN_MAX, flushPlainBytes: PART_SIDECAR_PLAIN_MAX });
2235
2280
  await this.persistSession(state).catch(() => undefined);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "omnirush",
3
- "version": "0.5.4",
3
+ "version": "0.6.1",
4
4
  "description": "Omnirush \u2014 free daily tokens for the most powerful coding model on earth.",
5
5
  "license": "Apache-2.0",
6
6
  "type": "module",
@@ -8,11 +8,11 @@
8
8
  "omnirush": "src/bin.js"
9
9
  },
10
10
  "engines": {
11
- "node": ">=18"
11
+ "node": ">=22.19.0"
12
12
  },
13
13
  "scripts": {
14
14
  "test": "node --test",
15
- "test:bun": "if [ -x .runtime/bin/bun ]; then .runtime/bin/bun test test/; else bun test test/; fi",
15
+ "test:bun": "if [ -x .runtime/bin/bun ]; then .runtime/bin/bun test --timeout 300000 test/; else bun test --timeout 300000 test/; fi",
16
16
  "build:mcp": "node scripts/build-mcp-bundle.js",
17
17
  "postinstall": "node scripts/patch-pi-branding.js && node scripts/postinstall.js"
18
18
  },
@@ -49,10 +49,17 @@ def find_bin(root, names):
49
49
  return os.path.join(dirpath, f)
50
50
  return None
51
51
 
52
+ RELEASE_NAME = {"darwin-aarch64": "macos-arm64", "darwin-x64": "macos-x64"}
53
+
52
54
  for plat, cfg in PLATFORMS.items():
53
55
  if ONLY and plat not in ONLY:
54
56
  continue
55
- out_pre = f"omnirush-cli-{plat}.tar.gz" if plat == "linux-x64" else f"omnirush-cli-{plat}.zip"
57
+ # Release artifacts use the canonical names the attach list and the
58
+ # smoke expect (darwin-* must ship as macos-*).
59
+ rel = RELEASE_NAME.get(plat, plat)
60
+ out_pre = os.path.join(
61
+ WORK, f"omnirush-cli-{rel}.tar.gz" if plat.startswith("linux") else f"omnirush-cli-{rel}.zip"
62
+ )
56
63
  if os.path.exists(out_pre) and not ONLY:
57
64
  print("skip (built):", plat)
58
65
  continue
@@ -65,6 +72,21 @@ for plat, cfg in PLATFORMS.items():
65
72
  shutil.copytree(os.path.join(REPO, "src"), os.path.join(stage, "src"))
66
73
  shutil.copytree(os.path.join(REPO, "assets"), os.path.join(stage, "assets"))
67
74
  shutil.copy2(os.path.join(REPO, "package.json"), os.path.join(stage, "package.json"))
75
+ # Archives must run WITHOUT npm install: vendor the pinned agent
76
+ # (it carries its own bundled node_modules) plus the scripts the
77
+ # launcher imports, and apply the branding patch to the vendored
78
+ # tree at package time (postinstall never runs for archives).
79
+ nm_src = os.path.join(REPO, "node_modules", "@earendil-works")
80
+ if not os.path.isdir(nm_src):
81
+ raise SystemExit(f"missing vendored agent runtime: {nm_src} (run npm install first)")
82
+ shutil.copytree(nm_src, os.path.join(stage, "node_modules", "@earendil-works"), symlinks=True)
83
+ shutil.copytree(os.path.join(REPO, "scripts"), os.path.join(stage, "scripts"))
84
+ import subprocess
85
+
86
+ subprocess.run(
87
+ ["node", os.path.join(stage, "scripts", "patch-pi-branding.js")],
88
+ cwd=stage, check=True,
89
+ )
68
90
  exe = "bun.exe" if plat == "windows-x64" else "bun"
69
91
  bun_plat = plat.replace("arm64", "aarch64")
70
92
  bunzip = fetch(f"https://github.com/oven-sh/bun/releases/download/{BUN_VER}/bun-{bun_plat}.zip")
@@ -86,13 +108,13 @@ for plat, cfg in PLATFORMS.items():
86
108
  assert fb and rb, f"fd/rg not found for {plat}"
87
109
  shutil.copy2(fb, os.path.join(stage, ".runtime", "bin", os.path.basename(fb)))
88
110
  shutil.copy2(rb, os.path.join(stage, ".runtime", "bin", os.path.basename(rb)))
89
- if plat == "linux-x64":
90
- out = f"omnirush-cli-{plat}.tar.gz"
111
+ if plat.startswith("linux"):
112
+ out = out_pre
91
113
  with tarfile.open(out, "w:gz") as t:
92
114
  t.add(stage, arcname=os.path.basename(stage))
93
115
  else:
94
116
  import zipfile as zfmod
95
- with zfmod.ZipFile(f"omnirush-cli-{plat}.zip", "w", zfmod.ZIP_DEFLATED) as z:
117
+ with zfmod.ZipFile(out_pre, "w", zfmod.ZIP_DEFLATED) as z:
96
118
  for dirpath, _, files in os.walk(stage):
97
119
  for f in files:
98
120
  full = os.path.join(dirpath, f)
@@ -0,0 +1,127 @@
1
+ """Release smoke: every package must exist and the archive layout must
2
+ be runnable WITHOUT npm install (bin.js --version executes offline).
3
+
4
+ Run on the packaging host after build-all-packages.py:
5
+ python3 scripts/smoke-packages.py --dist "$PWD/dist"
6
+ """
7
+
8
+ import argparse
9
+ import json
10
+ import os
11
+ import shutil
12
+ import subprocess
13
+ import sys
14
+ import tarfile
15
+ import tempfile
16
+ import zipfile
17
+
18
+ EXPECTED = {
19
+ "omnirush-cli-linux-x64.tar.gz",
20
+ "omnirush-cli-linux-arm64.tar.gz",
21
+ "omnirush-cli-macos-arm64.zip",
22
+ "omnirush-cli-macos-x64.zip",
23
+ "omnirush-cli-windows-x64.zip",
24
+ }
25
+
26
+ REQUIRED_PATHS = [
27
+ "package.json",
28
+ "src/bin.js",
29
+ "src/lib.js",
30
+ "scripts/postinstall.js",
31
+ "scripts/patch-pi-branding.js",
32
+ "assets/extensions/omnirush/index.ts",
33
+ "node_modules/@earendil-works/pi-coding-agent/package.json",
34
+ "node_modules/@earendil-works/pi-coding-agent/dist",
35
+ ".runtime/bin/bun",
36
+ ]
37
+
38
+
39
+ def fail(message):
40
+ print(f"SMOKE FAIL: {message}")
41
+ sys.exit(1)
42
+
43
+
44
+ def archive_names(path, is_tgz):
45
+ names = set()
46
+ if is_tgz:
47
+ with tarfile.open(path, "r:gz") as t:
48
+ names = set(t.getnames())
49
+ else:
50
+ with zipfile.ZipFile(path) as z:
51
+ names = set(z.namelist())
52
+ root = min(names, key=len).split("/")[0]
53
+ return names, root
54
+
55
+
56
+ def has_entry(names, root, required):
57
+ """True if `required` exists as an entry OR any entry beneath it
58
+ (zips carry file paths only — no directory entries)."""
59
+ full = f"{root}/{required}"
60
+ return any(n == full or n == f"{full}/" or n.startswith(f"{full}/") for n in names)
61
+
62
+
63
+ def main():
64
+ ap = argparse.ArgumentParser()
65
+ ap.add_argument("--dist", default=os.path.join(os.getcwd(), "dist"))
66
+ args = ap.parse_args()
67
+ dist = os.path.abspath(args.dist)
68
+
69
+ for name in sorted(EXPECTED):
70
+ path = os.path.join(dist, name)
71
+ if not os.path.exists(path):
72
+ fail(f"missing artifact {name}")
73
+ size_mb = os.path.getsize(path) / 1e6
74
+ if size_mb < 40:
75
+ fail(f"{name} suspiciously small ({size_mb:.1f} MB)")
76
+ print(f"ok (size): {name} {size_mb:.0f} MB")
77
+
78
+ repo_version = json.load(open(os.path.join(dist, "..", "package.json")))["version"] \
79
+ if os.path.exists(os.path.join(dist, "..", "package.json")) else None
80
+
81
+ work = tempfile.mkdtemp(prefix="omnirush-smoke-")
82
+ try:
83
+ # linux-x64: full behavioral smoke — extract and run bin.js offline.
84
+ tgz = os.path.join(dist, "omnirush-cli-linux-x64.tar.gz")
85
+ names, root = archive_names(tgz, is_tgz=True)
86
+ for required in REQUIRED_PATHS:
87
+ if not has_entry(names, root, required):
88
+ fail(f"linux-x64 archive lacks {required}")
89
+ with tarfile.open(tgz, "r:gz") as t:
90
+ t.extractall(work)
91
+ extracted = os.path.join(work, root)
92
+ env = dict(os.environ)
93
+ env.update({"HOME": work, "OMNIRUSH_NO_UPDATE_CHECK": "1"})
94
+ node = shutil.which("node")
95
+ if not node:
96
+ fail("node not on PATH for the smoke run")
97
+ proc = subprocess.run(
98
+ [node, os.path.join(extracted, "src", "bin.js"), "--version"],
99
+ env=env, capture_output=True, text=True, timeout=120, cwd=work,
100
+ )
101
+ if proc.returncode != 0:
102
+ fail(f"bin.js --version failed ({proc.returncode}): {proc.stderr[-400:]}")
103
+ out = proc.stdout.strip()
104
+ if not out.startswith("omnirush "):
105
+ fail(f"unexpected --version output: {out[:120]}")
106
+ if repo_version and repo_version not in out:
107
+ fail(f"--version {out!r} does not carry the repo version {repo_version}")
108
+ print(f"ok (run): linux-x64 bin.js --version -> {out}")
109
+
110
+ # Remaining archives: structural smoke only (cross-packaged OSes).
111
+ for name in sorted(EXPECTED):
112
+ if name == "omnirush-cli-linux-x64.tar.gz":
113
+ continue
114
+ is_tgz = name.endswith(".tar.gz")
115
+ names, root = archive_names(os.path.join(dist, name), is_tgz=is_tgz)
116
+ bun_name = ".runtime/bin/bun.exe" if "windows" in name else ".runtime/bin/bun"
117
+ for required in REQUIRED_PATHS[:len(REQUIRED_PATHS) - 1] + [bun_name]:
118
+ if not has_entry(names, root, required):
119
+ fail(f"{name} lacks {required}")
120
+ print(f"ok (structure): {name}")
121
+ finally:
122
+ shutil.rmtree(work, ignore_errors=True)
123
+ print("SMOKE PASS")
124
+
125
+
126
+ if __name__ == "__main__":
127
+ main()