wkappbot-code 0.3.35 → 0.3.39
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/bin/postinstall.js +3 -5
- package/bin/wkappbot-code.js +29 -4
- package/lib/doctor-path.js +8 -1
- package/lib/plugin-files.js +3 -1
- package/lib/setup-run.js +121 -1
- package/package.json +1 -1
- package/plugin/wisdom-kernel.js +8 -0
package/bin/postinstall.js
CHANGED
|
@@ -23,7 +23,7 @@ import { homedir } from "node:os";
|
|
|
23
23
|
import { appendFileSync } from "node:fs";
|
|
24
24
|
import { dirname, join } from "node:path";
|
|
25
25
|
import { fileURLToPath } from "node:url";
|
|
26
|
-
import { buildDone, files, globalBinDir,
|
|
26
|
+
import { buildDone, files, globalBinDir, parseDoctor, platformOf, selfTest, startWorker, withPathFirst, writeDone } from "../lib/setup-run.js";
|
|
27
27
|
|
|
28
28
|
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
29
29
|
const CLI = join(HERE, "wkappbot-code.js");
|
|
@@ -83,10 +83,8 @@ function main() {
|
|
|
83
83
|
if (d.version === version() && (d.installRc ?? d.rc) === 0) { console.log("[wkappbot-code] already set up by " + d.version + " (" + DONE + ")"); return; }
|
|
84
84
|
} catch {}
|
|
85
85
|
mkdirSync(STATE, { recursive: true });
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
const c = spawn(process.execPath, [fileURLToPath(import.meta.url), "--run"], { detached: true, stdio: ["ignore", out, out], env: process.env, windowsHide: true });
|
|
89
|
-
c.unref();
|
|
86
|
+
// 0.3.39: startWorker records the WORKER's pid in postinstall.running (this parent exits at once)
|
|
87
|
+
startWorker({ script: fileURLToPath(import.meta.url), home: homedir(), version: version(), env: process.env });
|
|
90
88
|
console.log("[wkappbot-code] " + (cloud ? "cloud VM detected: wiring the Claude Code harness in the background" : "setting up in the background (install, doctor, self-test)") +
|
|
91
89
|
" (log: " + LOG + ", result: " + DONE + "); the first `wkappbot-code` run prints the summary");
|
|
92
90
|
}
|
package/bin/wkappbot-code.js
CHANGED
|
@@ -51,7 +51,7 @@ import { installStarterMd, removeStarterMd } from "../lib/starter-md.js";
|
|
|
51
51
|
import { addAllows, removeAllows } from "../lib/allow-compact.js";
|
|
52
52
|
import { ensurePrereqs } from "../lib/prereq.js";
|
|
53
53
|
import { ensurePython } from "../lib/python-acquire.js";
|
|
54
|
-
import { files as setupFiles, launchOpenCode, printBanner } from "../lib/setup-run.js";
|
|
54
|
+
import { ensureOpencodeBin, files as setupFiles, findOpenCode, globalBinDir, launchOpenCode, printBanner, resumeIfWorkerDead, startWorker } from "../lib/setup-run.js";
|
|
55
55
|
|
|
56
56
|
const PKG_DIR = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
|
57
57
|
const PKG = JSON.parse(readFileSync(join(PKG_DIR, "package.json"), "utf8"));
|
|
@@ -364,6 +364,10 @@ function installPlugin(o, kih, kihSource) {
|
|
|
364
364
|
// The plugin file exports ONLY the plugin (OpenCode calls every named export as one); its helpers ride beside it.
|
|
365
365
|
for (const f of PLUGIN_LIBS) copyFileSync(join(PKG_DIR, "lib", f), join(cfg, "wkappbot-code", f));
|
|
366
366
|
writeFileSync(join(cfg, "wkappbot-code", "package.json"), '{ "type": "module" }\n');
|
|
367
|
+
// 0.3.41: the session arm (plugin/wisdom-kernel.js) resumes a dead postinstall worker from this package root, which a
|
|
368
|
+
// global npm install does not place beside the plugin (lib/setup-run.js postinstallScriptIn).
|
|
369
|
+
mkdirSync(join(homedir(), ".wkappbot-code"), { recursive: true });
|
|
370
|
+
writeFileSync(join(homedir(), ".wkappbot-code", "package-dir"), PKG_DIR + "\n");
|
|
367
371
|
const url = pathToFileURL(dest).href;
|
|
368
372
|
const tmpl = JSON.parse(readFileSync(join(PKG_DIR, "opencode.json"), "utf8"));
|
|
369
373
|
const target = o.project ? join(process.cwd(), "opencode.json") : join(cfg, "opencode.json");
|
|
@@ -430,6 +434,14 @@ function waitForSetupWorker() {
|
|
|
430
434
|
log(alive() ? "background setup still running after 10 min; installing anyway" : "background setup finished; continuing");
|
|
431
435
|
}
|
|
432
436
|
|
|
437
|
+
// npm install -g opencode-ai at the pinned version; returns the rc (-1 when npm cannot start). A failure is logged, never silent.
|
|
438
|
+
function npmInstallOpencode(log) {
|
|
439
|
+
const r = spawnSync("npm", ["install", "-g", "opencode-ai@" + OPENCODE_VERSION, "--no-audit", "--no-fund"], { shell: process.platform === "win32", stdio: "inherit" });
|
|
440
|
+
const rc = r.error ? -1 : (r.status ?? -1);
|
|
441
|
+
if (rc !== 0) log("opencode: npm install FAILED (rc " + rc + (r.error ? ", " + String(r.error.message).split("\n")[0] : "") + ") -- run: npm install -g opencode-ai@" + OPENCODE_VERSION);
|
|
442
|
+
return rc;
|
|
443
|
+
}
|
|
444
|
+
|
|
433
445
|
function install(o) {
|
|
434
446
|
waitForSetupWorker();
|
|
435
447
|
const prev = readState(o);
|
|
@@ -458,9 +470,10 @@ function install(o) {
|
|
|
458
470
|
else {
|
|
459
471
|
log("npm install -g opencode-ai@" + OPENCODE_VERSION + " (was: " + (have || "none") + ")");
|
|
460
472
|
// 0.3.12: a failed OpenCode install (offline, no npm rights) must not abort the harness, plugin and config parts below.
|
|
461
|
-
|
|
462
|
-
catch (e) { bad++; log("opencode: npm install FAILED (" + String(e.message).split("\n")[0] + ") -- run: npm install -g opencode-ai@" + OPENCODE_VERSION); }
|
|
473
|
+
if (npmInstallOpencode(log) !== 0) bad++;
|
|
463
474
|
}
|
|
475
|
+
// 0.3.39 (2026-11-14): the bin must be LINKED, not the 479-byte placeholder; one retry, every rc logged (lib/setup-run.js)
|
|
476
|
+
if (!ensureOpencodeBin({ bin: findOpenCode(process.env, globalBinDir(process.env)), reinstall: () => npmInstallOpencode(log), log }).ok) bad++;
|
|
464
477
|
}
|
|
465
478
|
// 0.3.13 (owner 2026-11-08): the famous AI CLIs come along, so a user who knows nothing about LLMs still has them.
|
|
466
479
|
st.aiClis = (o.npm && o.aiClis) ? installAiClis(log, process.env, undefined, (prev.aiClis && prev.aiClis.installed) || [], (prev.aiClis && prev.aiClis.prefix) || "")
|
|
@@ -680,7 +693,11 @@ function doctor(o) {
|
|
|
680
693
|
core = r.status === 0 ? (r.stdout || "").trim().split("\n")[0] : "";
|
|
681
694
|
add("bin", !!core, core ? dest + " " + core + (st.bin && st.bin.link ? " (PATH link " + st.bin.link + ")" : "") : dest + " does not run", !!(st.bin && !st.bin.skipped));
|
|
682
695
|
}
|
|
683
|
-
const pr = checkPath({ home: homedir(), env: process.env, platform: process.platform });
|
|
696
|
+
const pr = checkPath({ home: homedir(), env: process.env, platform: process.platform });
|
|
697
|
+
// --no-bin: this install wrote no bare-name link, so the path row is a skip whatever core file sits at dest, as the bin row is
|
|
698
|
+
// (a core at dest that is not on PATH is only a FAIL when the install owns the links, i.e. bin is not skipped)
|
|
699
|
+
if (!pr.ok && st.bin && st.bin.skipped) add("path", false, pr.text, false, true);
|
|
700
|
+
else add("path", pr.ok, pr.text, true);
|
|
684
701
|
// kih-hook: the harness the plugin calls and its verdict road; no pwsh and no lite = NO verdict = FAIL whatever the install mode.
|
|
685
702
|
const k = (entry && Array.isArray(entry) && entry[1] && entry[1].kihDir) || kihDir(o);
|
|
686
703
|
const hk = hasHook(k);
|
|
@@ -742,6 +759,14 @@ if (["install", "doctor", "config"].includes(o.cmd) && !o.update) {
|
|
|
742
759
|
if (nv && (o.autoUpdate || process.env.WKAPPBOT_CODE_AUTO_UPDATE === "1")) upd();
|
|
743
760
|
}
|
|
744
761
|
// The one-line result of the automatic post-install setup (lib/setup-run.js), once; not for the worker's own children.
|
|
762
|
+
// 0.3.39 self-heal (2026-11-14): a background worker that died before postinstall.done is started again here, on every run
|
|
763
|
+
// (doctor and doctor --fix included). The worker's own children and the opt-out switch never arm it.
|
|
764
|
+
if (process.env.WKAPPBOT_CODE_SETUP_WORKER !== "1" && process.env.WKAPPBOT_CODE_NO_POSTINSTALL !== "1" && !["--version", "-v", "version"].includes(o.cmd)) {
|
|
765
|
+
try {
|
|
766
|
+
const rs = resumeIfWorkerDead({ home: homedir(), runSetup: () => startWorker({ script: join(PKG_DIR, "bin", "postinstall.js"), home: homedir(), version: PKG.version }) });
|
|
767
|
+
if (rs.resumed) log("background setup did not finish (" + rs.reason + "); started it again -- log: " + setupFiles(homedir()).log);
|
|
768
|
+
} catch {}
|
|
769
|
+
}
|
|
745
770
|
if (!["--version", "-v", "version", "uninstall"].includes(o.cmd) && process.env.WKAPPBOT_CODE_SETUP_WORKER !== "1") { try { printBanner(); } catch {} }
|
|
746
771
|
// Bare `wkappbot-code`: open OpenCode (its start screen); no opencode on PATH falls through to the usage below.
|
|
747
772
|
if (process.argv.length <= 2 && o.cmd === "help") { const rc = launchOpenCode(); if (rc !== null) process.exit(rc); }
|
package/lib/doctor-path.js
CHANGED
|
@@ -40,6 +40,13 @@ export function loginShell(h, env, bin) {
|
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
// The environment a fresh cmd.exe is started with on win32. It carries the registry PATH only, plus PATHEXT, so the
|
|
44
|
+
// .cmd shims the installer writes always resolve by bare name (PATHEXT is never left to cmd's built-in default).
|
|
45
|
+
function winShellEnv(h, env, winPath) {
|
|
46
|
+
// profile-mark: winShellEnv
|
|
47
|
+
return { SystemRoot: env.SystemRoot || "C:\\Windows", USERPROFILE: h, HOME: h, PATHEXT: env.PATHEXT || ".COM;.EXE;.BAT;.CMD", PATH: winPath };
|
|
48
|
+
}
|
|
49
|
+
|
|
43
50
|
// checkPath({home, env, platform, readRegPath, runShell}) -> {ok, text}
|
|
44
51
|
// home HOME (or USERPROFILE on Windows) the fresh shell starts in
|
|
45
52
|
// env carries the OS persistent PATH (PATH key; default process.env)
|
|
@@ -61,7 +68,7 @@ export function checkPath({ home, env = process.env, platform = process.platform
|
|
|
61
68
|
try {
|
|
62
69
|
out = execFileSync("cmd.exe", ["/d", "/s", "/c", `${bin} --version`], {
|
|
63
70
|
encoding: "utf8", stdio: ["ignore", "pipe", "pipe"], timeout: 60000,
|
|
64
|
-
env:
|
|
71
|
+
env: winShellEnv(h, env, winPath),
|
|
65
72
|
});
|
|
66
73
|
} catch (e) {
|
|
67
74
|
out = typeof e.stdout === "string" ? e.stdout : "";
|
package/lib/plugin-files.js
CHANGED
|
@@ -3,4 +3,6 @@
|
|
|
3
3
|
// copied file must be in this list: a missing one made the whole plugin fail to load (0.3.34, SDK run 43333160662:
|
|
4
4
|
// ERR_MODULE_NOT_FOUND, every guard dead). bin/prepack.js refuses to pack when an import is not listed, and
|
|
5
5
|
// selftest-pluginlayout.mjs imports the plugin from an installed layout. Spec: wkappbot skill read agent-harness-landscape-2026-howto
|
|
6
|
-
|
|
6
|
+
// 0.3.40: the session arm (wisdom-kernel.js session.created) resumes a dead postinstall worker through lib/setup-run.js,
|
|
7
|
+
// whose chain is setup-run -> fslocked -> lockwait -> holders; each rides beside the plugin like the others.
|
|
8
|
+
export const PLUGIN_LIBS = ["wk-hook.js", "rmguard.js", "setup-run.js", "fslocked.js", "lockwait.js", "holders.js"];
|
package/lib/setup-run.js
CHANGED
|
@@ -8,7 +8,8 @@
|
|
|
8
8
|
// Files in ~/.wkappbot-code: postinstall.log, postinstall.running (worker alive), postinstall.done, postinstall.seen.
|
|
9
9
|
// A user's own settings.json entries are the installer's job to keep (wkappbot-code.js install); this module only reads.
|
|
10
10
|
// Spec: wkappbot skill read agent-harness-landscape-2026-howto
|
|
11
|
-
import { spawnSync } from "node:child_process";
|
|
11
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
12
|
+
import { closeSync, openSync, statSync, writeSync } from "node:fs";
|
|
12
13
|
import { existsSync, mkdirSync, mkdtempSync, readFileSync, renameSync, rmSync, writeFileSync } from "./fslocked.js";
|
|
13
14
|
import { homedir, tmpdir } from "node:os";
|
|
14
15
|
import { delimiter, dirname, join } from "node:path";
|
|
@@ -88,6 +89,125 @@ export function writeDone(home, rec) {
|
|
|
88
89
|
renameSync(tmp, f.done); // the done file appears whole or not at all (the selftest polls for it)
|
|
89
90
|
rmSync(f.running, { force: true });
|
|
90
91
|
}
|
|
92
|
+
// Starts the detached worker (bin/postinstall.js --run) and records ITS pid in postinstall.running. The parent used to record
|
|
93
|
+
// its own pid, which exits at once, so a live worker looked dead. (0.3.39, 2026-11-14)
|
|
94
|
+
export function startWorker({ script, home = homedir(), version, env = process.env, spawnFn = spawn }) {
|
|
95
|
+
const f = files(home);
|
|
96
|
+
mkdirSync(f.dir, { recursive: true });
|
|
97
|
+
rmSync(f.done, { force: true }); // the old verdict belongs to the old install
|
|
98
|
+
const out = openSync(f.log, "a");
|
|
99
|
+
const c = spawnFn(process.execPath, [script, "--run"], { detached: true, stdio: ["ignore", out, out], env, windowsHide: true });
|
|
100
|
+
if (c && c.unref) c.unref();
|
|
101
|
+
writeFileSync(f.running, JSON.stringify({ version, at: new Date().toISOString(), pid: (c && c.pid) || 0 }) + "\n");
|
|
102
|
+
return c;
|
|
103
|
+
}
|
|
104
|
+
// A pid that answers signal 0 is alive (EPERM = alive under another user). 0 or junk is never alive.
|
|
105
|
+
export function pidAlive(pid) {
|
|
106
|
+
const n = Number(pid);
|
|
107
|
+
if (!Number.isInteger(n) || n <= 0) return false;
|
|
108
|
+
try { process.kill(n, 0); return true; } catch (e) { return e.code === "EPERM"; }
|
|
109
|
+
}
|
|
110
|
+
// Self-heal (0.3.39, 2026-11-14): a worker that died before postinstall.done leaves postinstall.running naming a dead pid,
|
|
111
|
+
// and nothing ever retried. Every wkappbot-code run (doctor included) calls this: a dead pid with no done file runs setup
|
|
112
|
+
// again through runSetup (production: startWorker). Returns {resumed, pid, reason}.
|
|
113
|
+
// ONE SHARED LOCK (0.3.42, 2026-11-15): the shell arm pi_resume in tools/wkharness-lite.sh takes the same file,
|
|
114
|
+
// ~/.wkappbot-code/resume.lock, with noclobber. Whoever holds it re-reads postinstall.running UNDER the lock, so a second
|
|
115
|
+
// starter sees the first worker's live pid and does nothing. A lock whose holder pid is dead is reclaimed.
|
|
116
|
+
export const resumeLockFile = (home = homedir()) => join(stateDir(home), "resume.lock");
|
|
117
|
+
const RESUME_LOCK_STALE_MS = 60 * 1000; // an empty lock file (writer died before its pid) is reclaimed after this
|
|
118
|
+
function createResumeLock(p) {
|
|
119
|
+
try {
|
|
120
|
+
const fd = openSync(p, "wx");
|
|
121
|
+
try { writeSync(fd, String(process.pid) + "\n"); } finally { closeSync(fd); }
|
|
122
|
+
return true;
|
|
123
|
+
} catch (e) { if (e.code === "EEXIST") return false; throw e; }
|
|
124
|
+
}
|
|
125
|
+
export function takeResumeLock(home = homedir(), alive = pidAlive) {
|
|
126
|
+
const p = resumeLockFile(home);
|
|
127
|
+
try { mkdirSync(stateDir(home), { recursive: true }); } catch {}
|
|
128
|
+
try { if (createResumeLock(p)) return true; } catch { return false; }
|
|
129
|
+
let holder = "";
|
|
130
|
+
try { holder = String(readFileSync(p, "utf8")).trim(); } catch { return false; }
|
|
131
|
+
if (/^[0-9]+$/.test(holder)) { if (alive(Number(holder))) return false; }
|
|
132
|
+
else if (Date.now() - statSync(p).mtimeMs < RESUME_LOCK_STALE_MS) return false;
|
|
133
|
+
rmSync(p, { force: true });
|
|
134
|
+
try { return createResumeLock(p); } catch { return false; }
|
|
135
|
+
}
|
|
136
|
+
export function releaseResumeLock(home = homedir()) { try { rmSync(resumeLockFile(home), { force: true }); } catch {} }
|
|
137
|
+
export function resumeIfWorkerDead({ home = homedir(), runSetup, alive = pidAlive } = {}) {
|
|
138
|
+
const f = files(home);
|
|
139
|
+
if (!takeResumeLock(home, alive)) return { resumed: false, pid: 0, reason: "resume lock held by another starter" };
|
|
140
|
+
try {
|
|
141
|
+
const running = readJson(f.running); // read AFTER taking the lock: the lock holder may have just written the new pid
|
|
142
|
+
if (!running) return { resumed: false, pid: 0, reason: "no postinstall.running" };
|
|
143
|
+
if (existsSync(f.done)) return { resumed: false, pid: 0, reason: "postinstall.done exists" };
|
|
144
|
+
const pid = Number(running.pid);
|
|
145
|
+
if (!(pid > 0)) return { resumed: false, pid: 0, reason: "postinstall.running names no pid" };
|
|
146
|
+
if (alive(pid)) return { resumed: false, pid, reason: "worker pid " + pid + " is alive" };
|
|
147
|
+
runSetup(); // startWorker writes the new worker's pid into postinstall.running before the lock is released
|
|
148
|
+
return { resumed: true, pid, reason: "worker pid " + pid + " is dead and postinstall.done is absent" };
|
|
149
|
+
} finally { releaseResumeLock(home); }
|
|
150
|
+
}
|
|
151
|
+
// Session arm (0.3.40, 2026-11-14): called once per OpenCode session start by plugin/wisdom-kernel.js (event session.created).
|
|
152
|
+
// The same self-heal as the CLI dispatch, but fail-open: it never throws, and the worker is detached (startWorker), so the
|
|
153
|
+
// session start waits for nothing. The CLI's skip flags apply: WKAPPBOT_CODE_SETUP_WORKER=1 (a worker's own child) and
|
|
154
|
+
// WKAPPBOT_CODE_NO_POSTINSTALL=1 (the opt-out). runSetup is injectable for selftest-resume.mjs (s5a-s5e).
|
|
155
|
+
export function sessionStartResume({ home = homedir(), env = process.env, script = "", version = "", runSetup, spawnFn } = {}) {
|
|
156
|
+
try {
|
|
157
|
+
if (env.WKAPPBOT_CODE_SETUP_WORKER === "1" || env.WKAPPBOT_CODE_NO_POSTINSTALL === "1") return { resumed: false, pid: 0, reason: "skip flag set" };
|
|
158
|
+
const ver = version || (script && (readJson(join(dirname(script), "..", "package.json")) || {}).version) || "";
|
|
159
|
+
const run = runSetup || (script ? () => startWorker({ script, home, version: ver, env, spawnFn }) : null);
|
|
160
|
+
if (!run) return { resumed: false, pid: 0, reason: "no postinstall script" };
|
|
161
|
+
return resumeIfWorkerDead({ home, runSetup: run });
|
|
162
|
+
} catch (e) { return { resumed: false, pid: 0, reason: "error: " + String(e && e.message).slice(0, 120) }; }
|
|
163
|
+
}
|
|
164
|
+
// The worker script: <kih>/wkappbot-code/bin/postinstall.js when the harness checkout carries it, else the package root the
|
|
165
|
+
// installer recorded in ~/.wkappbot-code/package-dir (0.3.41: a normal npm global install has no checkout). "" when neither exists.
|
|
166
|
+
export function postinstallScriptIn(kihDir, home = homedir()) {
|
|
167
|
+
const inKih = kihDir ? join(kihDir, "wkappbot-code", "bin", "postinstall.js") : "";
|
|
168
|
+
if (inKih && existsSync(inKih)) return inKih;
|
|
169
|
+
let pkg = "";
|
|
170
|
+
try { pkg = String(readFileSync(join(stateDir(home), "package-dir"), "utf8")).trim(); } catch {}
|
|
171
|
+
const p = pkg ? join(pkg, "bin", "postinstall.js") : "";
|
|
172
|
+
return p && existsSync(p) ? p : "";
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// The opencode-ai npm package links a 479-byte placeholder bin that its own postinstall replaces with the real binary.
|
|
176
|
+
// The step is LINKED when the file is over OPENCODE_MIN_BYTES or `--version` exits 0. Otherwise npm install runs ONCE more
|
|
177
|
+
// (reinstall) and every size and rc goes to log, never silently. Returns {ok, retries, size, rc}. (0.3.39, 2026-11-14)
|
|
178
|
+
export const OPENCODE_MIN_BYTES = 4096;
|
|
179
|
+
// A Windows npm bin is a .cmd shim that node cannot spawn without a shell, so the probe returned rc 1
|
|
180
|
+
// (SDK run 43689775714, 2026-11-15). The shell flag is set only for .cmd/.bat on win32.
|
|
181
|
+
export function versionSpawnPlan(bin, isWin = IS_WIN) {
|
|
182
|
+
const shell = isWin && /\.(?:cmd|bat)$/i.test(bin);
|
|
183
|
+
return { file: shell ? '"' + bin + '"' : bin, shell };
|
|
184
|
+
}
|
|
185
|
+
function defaultVersionRc(bin) {
|
|
186
|
+
const plan = versionSpawnPlan(bin);
|
|
187
|
+
const r = spawnSync(plan.file, ["--version"], { encoding: "utf8", timeout: 30000, shell: plan.shell, stdio: ["ignore", "pipe", "ignore"] });
|
|
188
|
+
return r.status ?? 1;
|
|
189
|
+
}
|
|
190
|
+
export function ensureOpencodeBin({ bin, reinstall, log = () => {}, versionRc = defaultVersionRc, minBytes = OPENCODE_MIN_BYTES }) {
|
|
191
|
+
const check = () => {
|
|
192
|
+
const size = bin && existsSync(bin) ? statSync(bin).size : -1;
|
|
193
|
+
const rc = size >= 0 ? versionRc(bin) : -1;
|
|
194
|
+
return { ok: size > minBytes || rc === 0, size, rc };
|
|
195
|
+
};
|
|
196
|
+
const describe = (s) => "bin " + (bin || "(none)") + " size " + s.size + ", --version rc " + s.rc + (s.ok ? " -- linked" : " -- NOT linked (placeholder or missing)");
|
|
197
|
+
let s = check();
|
|
198
|
+
log("opencode " + describe(s));
|
|
199
|
+
let retries = 0;
|
|
200
|
+
if (!s.ok) {
|
|
201
|
+
retries = 1;
|
|
202
|
+
const rc = reinstall();
|
|
203
|
+
log("opencode: retry 1 of 1: npm install rc " + rc);
|
|
204
|
+
s = check();
|
|
205
|
+
log("opencode after retry: " + describe(s));
|
|
206
|
+
}
|
|
207
|
+
if (!s.ok) log("opencode: FAIL -- the opencode bin is still not linked after the retry; run: npm install -g opencode-ai@<pinned version>");
|
|
208
|
+
return { ok: s.ok, retries, size: s.size, rc: s.rc };
|
|
209
|
+
}
|
|
210
|
+
|
|
91
211
|
export function buildDone({ version, installRc, doctor, selftest, env = process.env }) {
|
|
92
212
|
return { version, installRc, rc: installRc, doctor: { pass: doctor.pass, warn: doctor.warn, fail: doctor.fail, skip: doctor.skip },
|
|
93
213
|
selftest: { n: selftest.n, m: selftest.m }, platform: platformOf(env), at: new Date().toISOString(),
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wkappbot-code",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.39",
|
|
4
4
|
"description": "wkappbot-code -- Wisdom Kernel: the pinned OpenCode bridge plus the reflex harness plugin, the wkappbot core, the web harness and the latest public skills in one install",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
package/plugin/wisdom-kernel.js
CHANGED
|
@@ -82,6 +82,14 @@ export const WisdomKernel = async (ctx = {}, options = {}) => {
|
|
|
82
82
|
if (!event) return;
|
|
83
83
|
if (event.type === "session.created") {
|
|
84
84
|
const sid = event.properties && event.properties.info && event.properties.info.id;
|
|
85
|
+
// 0.3.40 session arm: a postinstall worker that died before postinstall.done is started again, detached and fail-open
|
|
86
|
+
// (lib/setup-run.js sessionStartResume; the CLI dispatch does the same on every run).
|
|
87
|
+
try {
|
|
88
|
+
let S;
|
|
89
|
+
try { S = await import("./setup-run.js"); } catch { S = await import("../lib/setup-run.js"); }
|
|
90
|
+
const os = await import("node:os");
|
|
91
|
+
S.sessionStartResume({ home: os.homedir(), env: process.env, script: S.postinstallScriptIn(k) });
|
|
92
|
+
} catch {}
|
|
85
93
|
await H.runHook("session", { hook_event_name: "SessionStart", session_id: sid, cwd, source: "startup", agent_family: "opencode" }, { kihDir: k, timeoutMs: 30000 });
|
|
86
94
|
} else if (event.type === "session.idle") {
|
|
87
95
|
const sid = event.properties && event.properties.sessionID;
|