@witness-ai/opencode 0.7.0 → 0.7.2

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.
Files changed (2) hide show
  1. package/bin/model.js +62 -2
  2. package/package.json +3 -3
package/bin/model.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { spawn } from "node:child_process"
2
- import { createReadStream, createWriteStream, existsSync, mkdirSync, openSync, readdirSync, statSync, unlinkSync, closeSync, chmodSync, readFileSync, utimesSync, writeFileSync } from "node:fs"
2
+ import { accessSync, constants as fsConstants, createReadStream, createWriteStream, existsSync, mkdirSync, openSync, readdirSync, statSync, unlinkSync, closeSync, chmodSync, readFileSync, utimesSync, writeFileSync } from "node:fs"
3
3
  import { rename } from "node:fs/promises"
4
4
  import { createHash, randomUUID } from "node:crypto"
5
5
  import { get as httpGet } from "node:http"
@@ -286,6 +286,59 @@ export function restampLockOwner(lock, token) {
286
286
  } catch {}
287
287
  }
288
288
 
289
+ // resolveScriptRunner picks something that can actually EXECUTE download-model.js.
290
+ //
291
+ // It must not assume process.execPath is a JS runtime. When this module is loaded from the
292
+ // OpenCode plugin, execPath is the `opencode` Bun-compiled binary, which treats a script
293
+ // path as its own CLI argument: verified against real OpenCode 1.18.13, it prints its usage
294
+ // banner, exits, and the script NEVER runs. That silently left npm OpenCode users without
295
+ // the embedding model forever — and with no model, autoWorkerShouldStart refuses to start
296
+ // a worker whenever there is pending work, so distillation never happened at all. Nothing
297
+ // else covered for it either: the npm package ships no assets, and embed.go's error tells
298
+ // the user to run scripts/fetch-model.sh, which that package does not contain.
299
+ //
300
+ // Preference order:
301
+ // 1. `bun run <script>` when we are demonstrably under Bun (globalThis.Bun) — the plugin's
302
+ // own runtime, so no extra dependency. Bun.which finds the real bun binary; execPath is
303
+ // unusable precisely because it is the embedded opencode build.
304
+ // 2. a plain `node` on PATH — the ordinary `npx`/CLI case.
305
+ // 3. process.execPath ONLY when it is not the opencode binary (a genuine node/bun exec).
306
+ // Returns null when nothing can run it, so the caller releases the lock and reports
307
+ // "couldn't start" rather than leaving a lock behind for a child that never existed.
308
+ function resolveScriptRunner() {
309
+ const isOpenCodeExec = /(^|[\\/])opencode(\.exe)?$/i.test(process.execPath || "")
310
+ if (typeof globalThis.Bun !== "undefined") {
311
+ const bun = (() => {
312
+ try {
313
+ return globalThis.Bun.which("bun")
314
+ } catch {
315
+ return null
316
+ }
317
+ })()
318
+ if (bun) return { cmd: bun, args: ["run"] }
319
+ }
320
+ const node = whichSync("node")
321
+ if (node) return { cmd: node, args: [] }
322
+ if (process.execPath && !isOpenCodeExec) return { cmd: process.execPath, args: [] }
323
+ return null
324
+ }
325
+
326
+ // whichSync is a dependency-free PATH lookup (Bun.which is unavailable under plain node).
327
+ function whichSync(bin) {
328
+ const paths = (process.env.PATH || "").split(path.delimiter).filter(Boolean)
329
+ const exts = process.platform === "win32" ? [".exe", ".cmd", ".bat", ""] : [""]
330
+ for (const dir of paths) {
331
+ for (const ext of exts) {
332
+ const candidate = path.join(dir, bin + ext)
333
+ try {
334
+ accessSync(candidate, fsConstants.X_OK)
335
+ return candidate
336
+ } catch {}
337
+ }
338
+ }
339
+ return null
340
+ }
341
+
289
342
  export function startModelDownload(packageRoot, options = {}) {
290
343
  if (process.env.WITNESS_SKIP_MODEL_DOWNLOAD === "1" || modelReady(packageRoot)) return null
291
344
  const dir = modelDir(packageRoot)
@@ -306,9 +359,16 @@ export function startModelDownload(packageRoot, options = {}) {
306
359
  // PID so a hard-killed OpenCode process does not leave a long model download
307
360
  // running on battery; detached=true is reserved for explicit CLI use.
308
361
  if (options.detached !== true) env.WITNESS_MODEL_PARENT_PID = String(process.pid)
362
+ const runner = resolveScriptRunner()
363
+ if (!runner) {
364
+ try {
365
+ unlinkSync(lock.lock)
366
+ } catch {}
367
+ return null
368
+ }
309
369
  let child
310
370
  try {
311
- child = spawn(process.execPath, [scriptPath, "--foreground", packageRoot, lock.lock, lock.token], {
371
+ child = spawn(runner.cmd, [...runner.args, scriptPath, "--foreground", packageRoot, lock.lock, lock.token], {
312
372
  detached: options.detached === true,
313
373
  stdio: "ignore",
314
374
  env,
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@witness-ai/opencode",
3
3
  "mcpName": "io.github.IngTian/witness",
4
- "version": "0.7.0",
4
+ "version": "0.7.2",
5
5
  "description": "MCP memory / second brain for Claude Code & OpenCode — captures your coding sessions and distills how your knowledge and habits evolve over time, queryable by your agent.",
6
6
  "type": "module",
7
7
  "main": "./witness.js",
@@ -21,8 +21,8 @@
21
21
  "README.md"
22
22
  ],
23
23
  "optionalDependencies": {
24
- "@witness-ai/opencode-darwin-arm64": "0.7.0",
25
- "@witness-ai/opencode-linux-x64": "0.7.0"
24
+ "@witness-ai/opencode-darwin-arm64": "0.7.2",
25
+ "@witness-ai/opencode-linux-x64": "0.7.2"
26
26
  },
27
27
  "keywords": [
28
28
  "mcp",