wkappbot-code 0.3.3 → 0.3.7
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/README.md +31 -34
- package/bin/postinstall.js +72 -0
- package/bin/wkappbot-code.js +198 -45
- package/lib/config.js +7 -3
- package/lib/wk-hook.js +281 -0
- package/opencode.json +1 -26
- package/package.json +8 -4
- package/plugin/wisdom-kernel.js +65 -187
package/README.md
CHANGED
|
@@ -1,74 +1,71 @@
|
|
|
1
1
|
# wkappbot-code -- Wisdom Kernel (WK)
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
An OpenCode distribution: the upstream OpenCode release, pinned to `opencode-ai@1.19.5`, plus one harness plugin. wkappbot-code contains no coding engine of its own.
|
|
4
4
|
|
|
5
5
|
## Bridge, not harness
|
|
6
6
|
|
|
7
|
-
OpenCode is the **bridge**. It connects any model (Claude, GPT, Qwen, Kimi, GLM and local models) to files, shell, LSP and its clients.
|
|
7
|
+
OpenCode is the **bridge**. It connects any model (Claude, GPT, Qwen, Kimi, GLM and local models) to files, shell, LSP and its clients. The harness is the discipline layer that rides on the bridge: refusal gates, skill learning, memory and pace. In wkappbot-code the kernel loads bridges the way an OS kernel loads drivers. OpenCode, Claude Code and Codex are the drivers, and each model is a device behind them.
|
|
8
8
|
|
|
9
|
-
The plugin `plugin/wisdom-kernel.js` maps OpenCode plugin events onto the
|
|
9
|
+
The plugin `plugin/wisdom-kernel.js` maps OpenCode plugin events onto the harness hook launcher `.claude/hooks/wkharness-run.sh`. The file exports only the plugin function (OpenCode calls every named export as a plugin); its helpers live in `lib/wk-hook.js`, installed beside it.
|
|
10
10
|
|
|
11
|
-
| OpenCode event |
|
|
11
|
+
| OpenCode event | harness arm | effect |
|
|
12
12
|
|---|---|---|
|
|
13
|
-
| `tool.execute.before` | `pre` | Claude-shaped PreToolUse JSON (`tool_name`, `tool_input`, `session_id`, `cwd`). Exit 2 throws, which refuses the tool call with the
|
|
13
|
+
| `tool.execute.before` | `pre` | Claude-shaped PreToolUse JSON (`tool_name`, `tool_input`, `session_id`, `cwd`). Exit 2 throws, which refuses the tool call with the guard's own message as the reason. |
|
|
14
14
|
| `tool.execute.after` | `post` | fail-open |
|
|
15
|
+
| `chat.message` | `prompt` | UserPromptSubmit: the intent lock owes a skill search (`wkskill search ...`) before real work |
|
|
16
|
+
| `event` `session.created` | `session` | SessionStart |
|
|
15
17
|
| `event` `session.idle` | `stop-archive` | archive only, fail-open |
|
|
18
|
+
| `experimental.chat.system.transform` | (none) | appends the reflex rules to the system prompt of EVERY model request, whatever the model (pinned to OpenCode 1.19.5; the e2e asserts every request carried them) |
|
|
19
|
+
| `chat.params` | (none) | logged to `~/.wkappbot-code/plugin.log` only; your model settings are never changed |
|
|
16
20
|
|
|
17
21
|
Tool names are mapped as `bash`/`edit`/`write`/`read` -> `Bash`/`Edit`/`Write`/`Read`, with camelCase args (`filePath`, `oldString`) mapped to snake_case (`file_path`, `old_string`).
|
|
18
22
|
|
|
19
|
-
|
|
23
|
+
**Fast path.** A read tool (`read`, `glob`, `grep`, `list`) or one plain read-only shell command (`ls`, `cat`, `grep`, `head`, `echo`, `git status/log/diff/show` ... with no chain, pipe, redirect or substitution, and no secret-looking path) runs no hook at all, so it never waits for PowerShell.
|
|
20
24
|
|
|
21
|
-
|
|
22
|
-
bash wkappbot-code/install.sh # npm i -g opencode-ai@1.19.5, copy plugin + config (idempotent)
|
|
23
|
-
bash wkappbot-code/install.sh --no-npm # wire the plugin only
|
|
24
|
-
bash wkappbot-code/selftest.sh # RESULT N/N; --synthetic-only where no kih hook can run
|
|
25
|
-
```
|
|
25
|
+
**Fail direction.** A hook verdict decides: exit 0 allows, exit 2 refuses. When no verdict comes back (no harness found, a crash, a hook killed at its time budget) the call is allowed, **except** the risky classes, which are refused (fail-closed): `git --no-verify` (and `git commit -n`), `git push --force` (`-f`, `--force-with-lease`, a `+ref`), any change of `core.hooksPath`, `rm -r` of HOME, `/` or the project, and any write under `.git`. The sh lite harness refuses the same classes on its own.
|
|
26
26
|
|
|
27
|
-
|
|
27
|
+
**Which harness gives the verdict.** A user install (the web harness, marked by `~/.wk-harness/.wk-user-install`) always uses the sh lite harness `tools/wkharness-lite.sh`: the risk floor above, the intent lock and the study lock, about 10 ms per call. The full PowerShell harness runs only on a dev seat (a harness checkout, or `install --dev-seat`) where `pwsh` is on PATH; there the lite guard still runs first for every OpenCode call.
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
## Install
|
|
30
30
|
|
|
31
31
|
```bash
|
|
32
|
-
npm i -g
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
wkappbot-code install # every part below, idempotent
|
|
37
|
-
wkappbot-code doctor # one PASS/WARN/FAIL/SKIP row per part
|
|
32
|
+
npm i -g wkappbot-code # or the web tgz below
|
|
33
|
+
wkappbot-code install # every part below, idempotent
|
|
34
|
+
wkappbot-code doctor # one PASS/WARN/FAIL/SKIP row per part
|
|
35
|
+
wkappbot-code uninstall # undo it (see below)
|
|
38
36
|
wkappbot-code --version
|
|
39
|
-
# the old command name wk-appbot-code (0.2.x) still works: it is a second bin entry for the same file
|
|
40
37
|
```
|
|
41
38
|
|
|
39
|
+
Other roads to the CLI: `npm i -g https://kiexpert.github.io/wkappbot-kih/wkappbot-code/wkappbot-code.tgz` (the scrub-gated tgz with `SHA256SUMS` and `version.json` beside it), or `npm pack ./wkappbot-code && npm i -g ./wkappbot-code-0.3.6.tgz` from a checkout (`bash wkappbot-code/install.sh` wraps the same `install`). The old command name `wk-appbot-code` (0.2.x) still works.
|
|
42
40
|
|
|
43
41
|
| part | what `install` lays down | skip with |
|
|
44
42
|
|---|---|---|
|
|
45
43
|
| opencode | pinned `opencode-ai@1.19.5` (`npm -g`) | `--no-npm` |
|
|
46
|
-
| plugin | `plugin/wisdom-kernel.js` plus the `opencode.json` plugin entry | -- |
|
|
47
|
-
| bin | the `wkappbot` core for this platform from the SDK latest release asset `wkappbot-<platform>`, accepted only when it matches its `SHA256SUMS` line (`lib/wk-bin-acquire.sh
|
|
48
|
-
| harness | when no
|
|
44
|
+
| plugin | `plugin/wisdom-kernel.js` + `lib/wk-hook.js` plus the `opencode.json` plugin entry | -- |
|
|
45
|
+
| bin | the `wkappbot` core for this platform from the SDK latest release asset `wkappbot-<platform>`, accepted only when it matches its `SHA256SUMS` line (`lib/wk-bin-acquire.sh`); placed at `~/.local/share/wkappbot/wkappbot` and linked as `wkappbot` beside `wkappbot-code` | `--no-bin` |
|
|
46
|
+
| harness | when no harness checkout is found, the scrubbed web harness to `~/.wk-harness` through its own published `install.sh` (sha256-checked); `--web-harness` forces it | `--no-harness` |
|
|
49
47
|
| skills | the latest public skills from the same published tarball (sha256-checked) into the core's HQ `<core dir>/wkappbot.hq/skills`, so `wkappbot skill read on-load` prints | `--no-skills` |
|
|
50
48
|
|
|
51
|
-
|
|
49
|
+
`install` prints whether `pwsh` was found and which harness will give the verdict. The harness install ends with an advisory line, `[wk-verify] WARN n/6 advisory, not a failure -- c2 ...; c5 ...; c6 ...`, naming each check that missed and why; in a good web install c2 (wkhippo), c5 (core skill read before the core is linked) and c6 (no harness git checkout) are expected to miss. The verdict file is `~/.wkappbot-code/verify.json`.
|
|
50
|
+
|
|
51
|
+
The skills are a **read-only copy**: the HQ has no git remote, and a skill you edit yourself lands in the private home `~/.wkappbot/private-skills`, a local git repo with no remote. Doctor's `skill-push = owner-only` row names the GitHub login a push would use.
|
|
52
52
|
|
|
53
|
-
`
|
|
53
|
+
The generated `opencode.json` holds the minimum: `$schema` and the plugin entry (with `kihDir`, the harness the plugin calls). No provider and no permission block is added, because no refusal depends on one (every refusal comes from the plugin and the harness). An existing `opencode.json` is backed up (`opencode.json.bak-<stamp>`) and only the plugin entry is merged in; every other key you set stays. A config that is not plain JSON is left untouched, and `wkappbot-code.json` is written beside it for you to merge by hand. `wkappbot-code config` detects the provider and model from the API-key variables that are set (values are never read) and lets you edit them. Pass `--kih-dir` (or set `WK_KIH_DIR`) when a harness checkout is not auto-found (`~/wkappbot-kih` is searched). Relocation and mock overrides: `WK_BIN_BASE`, `WK_HARNESS_BASE`, `WK_HARNESS_DIR`, `WK_BIN_DEST`, `WK_APPBOT_HQ`, `WK_LINK_DIR`, `WK_PRIVATE_SKILLS`, `WK_SETTINGS`.
|
|
54
|
+
|
|
55
|
+
## Uninstall
|
|
56
|
+
|
|
57
|
+
`install` records, once, the machine state it changes: the global `core.hooksPath`, whether `~/.claude/settings.json`, `~/.wk-harness` and `~/.wk-harness-hooks` existed, and which shims it created (`wkappbot` beside the CLI, `wkskill` and `wk-verify` in `~/.local/bin`). `wkappbot-code uninstall` gives exactly that back: the recorded `core.hooksPath` (only when the current value is a hook folder the install wrote), the harness hook entries leave `settings.json` while yours stay (a file the install created is removed when nothing else is left), the recorded shims go, `~/.wk-harness` and `~/.wk-harness-hooks` go when the install created them, and the plugin entry leaves `opencode.json` (backed up first). It keeps `opencode-ai`, the core and its skills; `npm rm -g wkappbot-code` removes the CLI.
|
|
54
58
|
|
|
55
59
|
## Model swap
|
|
56
60
|
|
|
57
61
|
- One headless turn: `opencode run -m provider/model "prompt"`
|
|
58
62
|
- Inside the TUI: `/models`
|
|
59
|
-
-
|
|
63
|
+
- Any OpenAI-compatible endpoint: declare a provider with `"npm": "@ai-sdk/openai-compatible"` and `"options": { "baseURL": "{env:MY_BASE_URL}", "apiKey": "{env:MY_API_KEY}" }` in your own `opencode.json`. No key is stored in a file.
|
|
60
64
|
|
|
61
65
|
## In the browser WebVM (no login needed)
|
|
62
66
|
|
|
63
|
-
The x64 WebVM page
|
|
64
|
-
|
|
65
|
-
1. A custom OpenAI-compatible provider in `localStorage` (`webvm.appbot.provider` = `{"baseURL","model","key"}`, with the key in `webvm.key.<key>`), for example the in-house Qwen.
|
|
66
|
-
2. Otherwise the first family key the browser holds (claude, openai, gemini, xai).
|
|
67
|
-
3. Otherwise the **dummy haiku**: an OpenAI-compatible mock inside the page. It never calls a model, and its first line reads `dummy haiku (로그인 전)`.
|
|
68
|
-
|
|
69
|
-
The kih lite hooks stay live in the guest. Each question is a prompt event, and `!<command>` is a Bash tool call checked by `tools/wkharness-lite.sh` first, so the intent lock refuses a command until a `!wkskill search ...` has run. Proof: `node webvm/selftest/appbot-dummy-test.js` (no VM, no network), and `webvm/selftest/x64-appbot-test.js --page ... --install ...` against the published image. The guest's `wk-ai` gets the `!cmd`/hook support only after the SDK webvm-x64 bake; until then the page's dummy and provider path already works with the old image.
|
|
67
|
+
The x64 WebVM page types `wk-ai wkappbot-code` once the shell is ready, so the wkappbot-code prompt is up without typing. `?noappbot` turns this off, and an embedded page only autostarts with `?appbot`. Each question goes through the page bridge: a custom OpenAI-compatible provider kept in the browser, else the first model key the browser holds, else the **dummy haiku**, an OpenAI-compatible mock inside the page that never calls a model. The lite hooks stay live in the guest: each question is a prompt event, and `!<command>` is a Bash tool call checked by `tools/wkharness-lite.sh` first.
|
|
70
68
|
|
|
71
69
|
## Floor
|
|
72
70
|
|
|
73
|
-
|
|
74
71
|
Signing, submitting and paying stay human-only.
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// wkappbot-code postinstall -- npm runs this after `npm install wkappbot-code` (local or -g).
|
|
3
|
+
// Owner order 2026-11-06: the Default cloud environment's setup script is `npm install wkappbot-code`, and new cloud
|
|
4
|
+
// ROOTs still started with no hooks, because the package had no install script and npm only copied files.
|
|
5
|
+
// CONTRACT: (a) ALWAYS exits 0 and returns within a moment -- the real work runs DETACHED and logs to
|
|
6
|
+
// ~/.wkappbot-code/postinstall.log, then writes ~/.wkappbot-code/postinstall.done; (b) on a Claude Code cloud VM
|
|
7
|
+
// (CLAUDE_CODE_REMOTE set, or a Linux HOME of /root that holds .claude) it runs `wkappbot-code install --no-npm`,
|
|
8
|
+
// which lays the web harness, wires ~/.claude/settings.json (PreToolUse, Stop, UserPromptSubmit, SessionStart through
|
|
9
|
+
// the lite harness), the global git hooks, the wkskill/wk-verify shims and the public skills, keeping every user
|
|
10
|
+
// setting; idempotent: a done marker of the same version skips it; (c) on any other machine it only prints the one
|
|
11
|
+
// command to run. WKAPPBOT_CODE_NO_POSTINSTALL=1 skips it (the selftest sets it so npm-installs in its cases never
|
|
12
|
+
// touch the machine running it). WKAPPBOT_CODE_DEV_SEAT=1 adds --dev-seat (the full pwsh harness instead of lite);
|
|
13
|
+
// WKAPPBOT_CODE_INSTALL_ARGS adds install flags. A user install takes the lite harness: on a stranger's cloud VM the
|
|
14
|
+
// owner's full fleet harness (busy-fleet, letter gates, emergency-edit) would be the wrong default.
|
|
15
|
+
// Spec: wkappbot skill read agent-harness-landscape-2026-howto
|
|
16
|
+
import { spawn } from "node:child_process";
|
|
17
|
+
import { existsSync, mkdirSync, openSync, readFileSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { homedir } from "node:os";
|
|
19
|
+
import { dirname, join } from "node:path";
|
|
20
|
+
import { fileURLToPath } from "node:url";
|
|
21
|
+
|
|
22
|
+
const HERE = dirname(fileURLToPath(import.meta.url));
|
|
23
|
+
const CLI = join(HERE, "wkappbot-code.js");
|
|
24
|
+
const STATE = join(homedir(), ".wkappbot-code");
|
|
25
|
+
const LOG = join(STATE, "postinstall.log");
|
|
26
|
+
const DONE = join(STATE, "postinstall.done");
|
|
27
|
+
|
|
28
|
+
function version() {
|
|
29
|
+
try { return JSON.parse(readFileSync(join(HERE, "..", "package.json"), "utf8")).version || "?"; } catch { return "?"; }
|
|
30
|
+
}
|
|
31
|
+
export function isCloudVm(env = process.env, home = homedir(), platform = process.platform) {
|
|
32
|
+
if (env.CLAUDE_CODE_REMOTE) return true;
|
|
33
|
+
return platform === "linux" && home === "/root" && existsSync(join(home, ".claude"));
|
|
34
|
+
}
|
|
35
|
+
function stamp(msg) { try { mkdirSync(STATE, { recursive: true }); writeFileSync(LOG, new Date().toISOString() + " " + msg + "\n", { flag: "a" }); } catch {} }
|
|
36
|
+
|
|
37
|
+
// --run: the detached worker. Runs the installer to the end, then records the done marker with its exit code.
|
|
38
|
+
async function runWorker() {
|
|
39
|
+
const args = [CLI, "install", "--no-npm"];
|
|
40
|
+
if (process.env.WKAPPBOT_CODE_DEV_SEAT === "1") args.push("--dev-seat");
|
|
41
|
+
// extra install flags, e.g. "--no-bin --no-skills" (the selftest; a seat without the SDK release assets)
|
|
42
|
+
args.push(...String(process.env.WKAPPBOT_CODE_INSTALL_ARGS || "").split(/\s+/).filter((a) => /^--[a-z-]+$/.test(a)));
|
|
43
|
+
stamp("worker start: node " + args.join(" "));
|
|
44
|
+
const out = openSync(LOG, "a");
|
|
45
|
+
const rc = await new Promise((res) => {
|
|
46
|
+
const c = spawn(process.execPath, args, { stdio: ["ignore", out, out], env: { ...process.env, WKAPPBOT_CODE_NO_UPDATE_CHECK: "1" } });
|
|
47
|
+
c.on("error", () => res(-1));
|
|
48
|
+
c.on("close", (code) => res(code ?? -1));
|
|
49
|
+
});
|
|
50
|
+
try { writeFileSync(DONE, JSON.stringify({ version: version(), rc, at: new Date().toISOString() }) + "\n"); } catch {}
|
|
51
|
+
stamp("worker done rc " + rc);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function main() {
|
|
55
|
+
if (process.argv[2] === "--run") return runWorker().catch(() => {}).finally(() => process.exit(0));
|
|
56
|
+
if (process.env.WKAPPBOT_CODE_NO_POSTINSTALL === "1") return;
|
|
57
|
+
if (!isCloudVm()) {
|
|
58
|
+
console.log("[wkappbot-code] installed. To wire the harness and OpenCode on this machine run: npx wkappbot-code install (then: wkappbot-code doctor)");
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
try {
|
|
62
|
+
const d = JSON.parse(readFileSync(DONE, "utf8"));
|
|
63
|
+
if (d.version === version() && d.rc === 0) { console.log("[wkappbot-code] cloud VM: harness already wired by " + d.version + " (" + DONE + ")"); return; }
|
|
64
|
+
} catch {}
|
|
65
|
+
mkdirSync(STATE, { recursive: true });
|
|
66
|
+
const out = openSync(LOG, "a");
|
|
67
|
+
const c = spawn(process.execPath, [fileURLToPath(import.meta.url), "--run"], { detached: true, stdio: ["ignore", out, out], env: process.env });
|
|
68
|
+
c.unref();
|
|
69
|
+
console.log("[wkappbot-code] cloud VM detected: wiring the Claude Code harness in the background (log: " + LOG + ", done marker: " + DONE + ")");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try { main(); } catch (e) { try { stamp("postinstall error: " + e); } catch {} }
|