symbio 0.7.0 → 0.7.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/CHANGELOG.md CHANGED
@@ -5,6 +5,15 @@ versions follow [Semantic Versioning](https://semver.org/).
5
5
 
6
6
  ## [Unreleased]
7
7
 
8
+ ## [0.7.1]
9
+
10
+ - On a Node.js older than 22, `sym` says what to install instead of Symbio crashing at start
11
+ (npm installs it anyway and only warns).
12
+ - Automatic updates and `sym update` don't install a version that needs a newer Node.js than the
13
+ machine has.
14
+ - The image library loads when an agent first views an image, so a problem with it affects only
15
+ `view_image`, not Symbio's start.
16
+
8
17
  ## [0.7.0]
9
18
 
10
19
  - Updates: `sym update` installs the latest version and restarts Symbio once the team is idle
package/bin/symbio.js CHANGED
@@ -19,6 +19,18 @@ import { fileURLToPath, pathToFileURL } from "node:url";
19
19
 
20
20
  const root = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..");
21
21
  const pkg = JSON.parse(readFileSync(path.join(root, "package.json"), "utf8"));
22
+
23
+ // npm only warns when Node is older than package.json's "engines"; without this, Symbio would
24
+ // crash on start with a syntax error from a dependency (seen with Node 18).
25
+ const MIN_NODE = Number(/\d+/.exec(pkg.engines?.node ?? "22")?.[0] ?? 22);
26
+ if (Number(process.versions.node.split(".")[0]) < MIN_NODE && !["-v", "--version", "version", "-h", "--help", "help"].includes(process.argv[2])) {
27
+ console.error(
28
+ `Symbio ${pkg.version} needs Node.js ${MIN_NODE} or newer, and this is Node.js ${process.versions.node}.\n` +
29
+ `Install the current LTS (https://nodejs.org, or on Windows: winget install OpenJS.NodeJS.LTS), open a new terminal,\n` +
30
+ `then reinstall Symbio on it: npm install -g symbio`,
31
+ );
32
+ process.exit(1);
33
+ }
22
34
  const PORT = Number(process.env.SYMBIO_PORT ?? 4310);
23
35
  const HOME = process.env.SYMBIO_HOME ?? path.join(os.homedir(), ".symbio");
24
36
  const LOG = path.join(HOME, "logs", "symbio.log");
@@ -208,15 +220,20 @@ async function update() {
208
220
  console.log(`This Symbio runs from a development folder (${install.root}). Update it there: git pull, npm install, npm run build.`);
209
221
  return;
210
222
  }
211
- let latest;
223
+ let release;
212
224
  try {
213
- latest = await self.latestVersion();
225
+ release = await self.latestRelease();
214
226
  } catch (err) {
215
227
  console.error(`Couldn't check for updates: ${err.message}`);
216
228
  process.exit(1);
217
229
  }
230
+ const latest = release.version;
218
231
  if (self.compareVersions(latest, pkg.version) <= 0) return console.log(`Symbio ${pkg.version} is up to date.`);
219
232
  console.log(`Symbio ${latest} is available (you have ${pkg.version}).`);
233
+ if (!self.nodeSatisfies(release.node)) {
234
+ console.error(`It needs Node.js ${release.node}, and this is Node.js ${process.versions.node}. Update Node.js first (https://nodejs.org), then run "sym update".`);
235
+ process.exit(1);
236
+ }
220
237
  const resultFile = path.join(HOME, "update-result.json");
221
238
  const before = existsSync(resultFile) ? readFileSync(resultFile, "utf8") : "";
222
239
 
@@ -3,11 +3,22 @@
3
3
  // big to send at all. A crop shows one region at full resolution when details matter.
4
4
  import { existsSync, promises as fs } from "node:fs";
5
5
  import path from "node:path";
6
- import sharp from "sharp";
7
6
  import { IMAGE_EXTENSIONS, isSecretPath } from "./guard.js";
8
7
  export const DEFAULT_IMAGE_SIZE = 1024;
9
8
  const MAX_IMAGE_SIZE = 2048;
10
9
  const MAX_BYTES = 1_500_000; // what we send back, after scaling
10
+ /**
11
+ * The image library is native and loaded on first use, so a problem with it (a platform without
12
+ * prebuilt binaries, an old Node) costs only view_image, not Symbio's startup.
13
+ */
14
+ async function loadSharp() {
15
+ try {
16
+ return (await import("sharp")).default;
17
+ }
18
+ catch (err) {
19
+ throw new Error(`Images can't be viewed on this machine: the image library didn't load (${err.message.split("\n")[0]}).`);
20
+ }
21
+ }
11
22
  export async function viewImage(folder, input) {
12
23
  const given = String(input.path ?? "").trim();
13
24
  if (!given)
@@ -21,6 +32,7 @@ export async function viewImage(folder, input) {
21
32
  if (!existsSync(file))
22
33
  throw new Error(`No such file: ${file}`);
23
34
  const size = Math.min(MAX_IMAGE_SIZE, Math.max(64, Math.round(Number(input.max_size) || DEFAULT_IMAGE_SIZE)));
35
+ const sharp = await loadSharp();
24
36
  let image = sharp(await fs.readFile(file), { animated: false }).rotate(); // apply EXIF orientation
25
37
  const meta = await image.metadata();
26
38
  const width = meta.autoOrient?.width ?? meta.width ?? 0;
@@ -45,15 +45,26 @@ export function compareVersions(a, b) {
45
45
  }
46
46
  // Overridable for tests (a local registry and package file); never set in normal use.
47
47
  const REGISTRY = (process.env.SYMBIO_UPDATE_REGISTRY ?? "https://registry.npmjs.org").replace(/\/+$/, "");
48
- /** The version npm installs as `symbio@latest`. */
49
- export async function latestVersion() {
48
+ /** The version npm installs as `symbio@latest`, and the Node.js it needs. */
49
+ export async function latestRelease() {
50
50
  const res = await fetch(`${REGISTRY}/symbio/latest`, { signal: AbortSignal.timeout(15_000), headers: { accept: "application/json" } });
51
51
  if (!res.ok)
52
52
  throw new Error(`npm registry answered ${res.status}`);
53
53
  const data = (await res.json());
54
54
  if (!data.version)
55
55
  throw new Error("npm registry gave no version");
56
- return data.version;
56
+ return { version: data.version, node: data.engines?.node ?? null };
57
+ }
58
+ export async function latestVersion() {
59
+ return (await latestRelease()).version;
60
+ }
61
+ /**
62
+ * Whether this Node.js meets a release's requirement (">=22", ">=20.9.0"; anything fancier is
63
+ * assumed met). npm only warns about it, and the new version would then fail to start.
64
+ */
65
+ export function nodeSatisfies(range, version = process.versions.node) {
66
+ const m = range && /^\s*>=\s*v?(\d+(?:\.\d+){0,2})\s*$/.exec(range);
67
+ return !m || compareVersions(version, m[1].split(".").concat(["0", "0"]).slice(0, 3).join(".")) >= 0;
57
68
  }
58
69
  /** Changelog entries newer than `current`, from the latest version's CHANGELOG.md (best effort). */
59
70
  export async function changesSince(current, latest) {
@@ -88,6 +99,8 @@ const FAILED_PAUSE_MS = 24 * 60 * 60_000; // after a failed update, don't retry
88
99
  export class SelfUpdate {
89
100
  host;
90
101
  latest = null;
102
+ /** The Node.js the latest version needs (its package.json "engines"). */
103
+ latestNode = null;
91
104
  checkedAt = null;
92
105
  error = null;
93
106
  notes = [];
@@ -158,7 +171,9 @@ export class SelfUpdate {
158
171
  if (this.mode === "off" && this.checkedAt)
159
172
  return this.state();
160
173
  try {
161
- this.latest = await latestVersion();
174
+ const release = await latestRelease();
175
+ this.latest = release.version;
176
+ this.latestNode = release.node;
162
177
  this.error = null;
163
178
  this.notes = compareVersions(this.latest, this.host.version) > 0 ? await changesSince(this.host.version, this.latest) : [];
164
179
  }
@@ -172,6 +187,9 @@ export class SelfUpdate {
172
187
  blocker(forced) {
173
188
  if (this.install.kind === "dev")
174
189
  return "This Symbio runs from a development folder; update it with git pull, npm install and npm run build.";
190
+ if (!nodeSatisfies(this.latestNode)) {
191
+ return `Symbio ${this.latest} needs Node.js ${this.latestNode}, and this machine has ${process.versions.node}: update Node.js first (https://nodejs.org), then update Symbio.`;
192
+ }
175
193
  if (this.applying)
176
194
  return "Already updating.";
177
195
  if (!forced && this.mode === "notify")
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "symbio",
3
- "version": "0.7.0",
3
+ "version": "0.7.1",
4
4
  "description": "Run a team of OpenCode agents on a project from your browser",
5
5
  "license": "MIT",
6
6
  "author": "LinKzy",