@tachikomagundam/abathur 0.2.2 → 0.2.3
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 +36 -18
- package/dist/test/bench-fixture.test.js +6 -2
- package/dist/test/opencode.test.js +21 -0
- package/package.json +1 -1
- package/plugin/abathur.ts +47 -7
package/README.md
CHANGED
|
@@ -142,7 +142,11 @@ Abathur ships an official opencode plugin adapter. The harness remains the
|
|
|
142
142
|
orchestrator, but once set up an opencode session can also drive the CLI
|
|
143
143
|
directly. Two install routes:
|
|
144
144
|
|
|
145
|
-
|
|
145
|
+
Both routes deliver the tool **and** the `/abathur` slash command; pick by
|
|
146
|
+
taste — A needs no edit of your opencode config and works fully offline, B
|
|
147
|
+
needs no file copying and removes with one deleted line.
|
|
148
|
+
|
|
149
|
+
**Route A — one command (file-copy installer).**
|
|
146
150
|
|
|
147
151
|
```bash
|
|
148
152
|
npm i -g @tachikomagundam/abathur
|
|
@@ -150,7 +154,7 @@ abathur opencode install # copies plugin assets into ~/.config/opencode/
|
|
|
150
154
|
# restart opencode
|
|
151
155
|
```
|
|
152
156
|
|
|
153
|
-
**Route B — config only (
|
|
157
|
+
**Route B — config only (auto-downloaded from npm).** Put the package name in
|
|
154
158
|
your opencode config and restart:
|
|
155
159
|
|
|
156
160
|
```jsonc
|
|
@@ -159,11 +163,16 @@ your opencode config and restart:
|
|
|
159
163
|
|
|
160
164
|
opencode then downloads the package from npm at startup into its own cache
|
|
161
165
|
(`~/.cache/opencode/packages/…`) and loads the package's `./server` export —
|
|
162
|
-
no file copying, no uninstall step (delete the line).
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
166
|
+
no file copying, no uninstall step (delete the line). Since 0.2.3 the plugin
|
|
167
|
+
also self-registers `/abathur` through its `config` hook: slash commands are
|
|
168
|
+
entries in the merged config's `command` map, and a plugin may add one there
|
|
169
|
+
— the upstream `opencode-acp` plugin registers `/acp` the same way (the
|
|
170
|
+
0.2.0–0.2.2 note here claiming plugins cannot register slash commands was
|
|
171
|
+
wrong; corrected). If Route A's copied `commands/abathur.md` exists it stays
|
|
172
|
+
authoritative — the injection never overwrites it, and the name-keyed command
|
|
173
|
+
map means `/abathur` can never appear twice. One honest limit stands for both
|
|
174
|
+
routes: the tool spawns the `abathur` **CLI**, which must exist on `PATH` (or
|
|
175
|
+
via `ABATHUR_BIN`) — keep the `npm i -g` install.
|
|
167
176
|
|
|
168
177
|
Either route gives the session:
|
|
169
178
|
|
|
@@ -176,9 +185,10 @@ Either route gives the session:
|
|
|
176
185
|
so the allowlist limits typos and UX, not capability. `promote` and
|
|
177
186
|
`tombstone` are deliberately NOT reachable through the tool: they are
|
|
178
187
|
human gates and must be run in a terminal.
|
|
179
|
-
- the user command **`/abathur <args…>`**
|
|
180
|
-
|
|
181
|
-
the
|
|
188
|
+
- the user command **`/abathur <args…>`** — a slash command that tells the
|
|
189
|
+
agent to translate its arguments into a tool call and report the exit code.
|
|
190
|
+
Route A provides it as the copied `commands/abathur.md` file; Route B gets
|
|
191
|
+
it from the plugin's `config` hook at startup.
|
|
182
192
|
|
|
183
193
|
Route A ships its own manager commands: `abathur opencode status` shows each
|
|
184
194
|
target's path, installed/packaged sha256, and state (up-to-date / outdated /
|
|
@@ -430,7 +440,10 @@ abathur run --genome toy-smoke --dry-run # 计划 + requires[] 探针,零
|
|
|
430
440
|
Abathur 自带官方 opencode 插件适配器。工装仍是编排者,但配置之后,opencode
|
|
431
441
|
会话也可以直接驱动 CLI。安装有两条路线:
|
|
432
442
|
|
|
433
|
-
|
|
443
|
+
两条路线都交付工具**和** `/abathur` 斜杠命令,按喜好挑选:A 不必改动你的
|
|
444
|
+
opencode 配置、且完全离线可用;B 不复制任何文件、删掉一行即卸载。
|
|
445
|
+
|
|
446
|
+
**路线 A——一条命令(文件复制安装器)。**
|
|
434
447
|
|
|
435
448
|
```bash
|
|
436
449
|
npm i -g @tachikomagundam/abathur
|
|
@@ -438,7 +451,7 @@ abathur opencode install # 把插件资产复制进 ~/.config/opencode/
|
|
|
438
451
|
# 重启 opencode
|
|
439
452
|
```
|
|
440
453
|
|
|
441
|
-
**路线 B
|
|
454
|
+
**路线 B——纯配置(自动从 npm 下载)。** 把包名写进 opencode 配置再
|
|
442
455
|
重启:
|
|
443
456
|
|
|
444
457
|
```jsonc
|
|
@@ -447,10 +460,14 @@ abathur opencode install # 把插件资产复制进 ~/.config/opencode/
|
|
|
447
460
|
|
|
448
461
|
opencode 会在启动时自行从 npm 把包下载到它自己的缓存
|
|
449
462
|
(`~/.cache/opencode/packages/…`)并加载该包的 `./server` 导出——不复制文件,
|
|
450
|
-
也没有 uninstall
|
|
451
|
-
`
|
|
452
|
-
`
|
|
453
|
-
|
|
463
|
+
也没有 uninstall 步骤(删掉那行即可)。自 0.2.3 起,插件还会通过它的
|
|
464
|
+
`config` 钩子自注册 `/abathur`:斜杠命令本质是合并后配置的 `command`
|
|
465
|
+
映射里的条目,插件可以在钩子里添加一条——上游的 `opencode-acp` 插件正是
|
|
466
|
+
这样注册 `/acp` 的(0.2.0–0.2.2 在此声称"插件无法注册斜杠命令"是错的,
|
|
467
|
+
已更正)。若路线 A 复制的 `commands/abathur.md` 存在,它保持权威——注入
|
|
468
|
+
绝不覆盖它,而且命令映射按名字键控,`/abathur` 绝不会出现两份。对两条
|
|
469
|
+
路线同样成立的一个诚实限制:工具 spawn 的是 `abathur` **CLI**,CLI 必须
|
|
470
|
+
在 `PATH`(或 `ABATHUR_BIN`)上可达,`npm i -g` 不能省。
|
|
454
471
|
|
|
455
472
|
任一路线都会让会话获得:
|
|
456
473
|
|
|
@@ -461,8 +478,9 @@ opencode 会在启动时自行从 npm 把包下载到它自己的缓存
|
|
|
461
478
|
bash 等同的权限——`run` 与 `genome` 按设计就会 spawn 变异器/引擎二进制——
|
|
462
479
|
所以允许清单限制的是笔误与体验,而非能力。`promote` 与 `tombstone` 刻意
|
|
463
480
|
不可经由工具触达:它们是人类闸门,必须在终端里运行。
|
|
464
|
-
- 用户命令 **`/abathur
|
|
465
|
-
|
|
481
|
+
- 用户命令 **`/abathur <参数…>`**——斜杠命令,指示智能体把参数
|
|
482
|
+
翻译成工具调用并回报退出码。路线 A 由复制的 `commands/abathur.md`
|
|
483
|
+
文件提供;路线 B 由插件的 `config` 钩子在启动时注册。
|
|
466
484
|
|
|
467
485
|
路线 A 自带管理命令:`abathur opencode status` 逐目标打印路径、已安装/随包
|
|
468
486
|
sha256 与状态
|
|
@@ -62,9 +62,13 @@ fi
|
|
|
62
62
|
echo "ran $unit"
|
|
63
63
|
echo '{"tokensEst":123,"turns":4}'
|
|
64
64
|
`;
|
|
65
|
+
// Unique sleep duration: node --test runs files concurrently and this test's
|
|
66
|
+
// orphan check scans the whole machine's `ps` output — "sleep 31.7" is the
|
|
67
|
+
// shipped toy genome's hang marker (bench-toy.test.ts); reusing it here made
|
|
68
|
+
// the two files' checks see each other's live sleeps (CI-verified 5/5 collision).
|
|
65
69
|
const BIN_HANG = `#!/usr/bin/env bash
|
|
66
70
|
if [ "\${1:-}" = "--version" ]; then echo "1.2.3"; exit 0; fi
|
|
67
|
-
exec sleep 31
|
|
71
|
+
exec sleep 29.31
|
|
68
72
|
`;
|
|
69
73
|
const BIN_NO_VERSION = `#!/usr/bin/env bash
|
|
70
74
|
if [ "\${1:-}" = "--version" ]; then echo "unsupported flag" >&2; exit 1; fi
|
|
@@ -296,7 +300,7 @@ test("(E) hanging unit: group kill at timeoutS, zero orphans", async (t) => {
|
|
|
296
300
|
assert.equal(run.exitCode, null);
|
|
297
301
|
assert.match(run.note ?? "", /killed after 1s: process group SIGKILL/);
|
|
298
302
|
const ps = spawnSync("ps", ["-eo", "args"], { encoding: "utf8" });
|
|
299
|
-
assert.ok(!ps.stdout.includes("sleep 31
|
|
303
|
+
assert.ok(!ps.stdout.includes("sleep 29.31"), "orphaned sleep survived the group kill");
|
|
300
304
|
});
|
|
301
305
|
// ------------------------------------------------------------------ AC (F)
|
|
302
306
|
test("(F) infra_failed is recorded with a distinct status/note, never a silent 0", async (t) => {
|
|
@@ -241,6 +241,27 @@ test("plugin/abathur.ts: V1 shape — marker, default {id, server}, tool allowli
|
|
|
241
241
|
assert.ok(!/shell\s*:\s*true/.test(bytes), "shell:true is banned");
|
|
242
242
|
assert.ok(bytes.includes("ABATHUR_BIN"), "bin overridable via ABATHUR_BIN");
|
|
243
243
|
});
|
|
244
|
+
test("plugin/abathur.ts: config hook self-registers /abathur — template byte-mirrors the command md body", async () => {
|
|
245
|
+
const bytes = await readFile(pluginAssetPath, "utf8");
|
|
246
|
+
// Route B needs the config hook: opencode hands plugins the fully-merged config
|
|
247
|
+
// (file commands already inside cfg.command), so a `??=` injection registers
|
|
248
|
+
// /abathur ONLY when no commands/abathur.md exists — Route A bytes/behaviour
|
|
249
|
+
// stay untouched and the name-keyed command map guarantees no duplicate entry.
|
|
250
|
+
assert.ok(/config:\s*async\s*\(cfg:\s*Config\)/.test(bytes), "config hook with typed cfg required");
|
|
251
|
+
assert.ok(bytes.includes('import { tool, type Config }'), "Config type imported from @opencode-ai/plugin");
|
|
252
|
+
assert.ok(/cfg\.command\s*\?\?=\s*\{\}/.test(bytes), "cfg.command must be lazily created with ??=");
|
|
253
|
+
assert.ok(/cfg\.command\.abathur\s*\?\?=/.test(bytes), "entry must be ??= — a file-installed command is never overwritten");
|
|
254
|
+
assert.ok(bytes.includes('description: "Drive the abathur evolution harness (usage: /abathur <command> [args...])"'), "injected description must be pinned verbatim");
|
|
255
|
+
// Extract the template literal (inner backticks appear escaped as \`) and byte-compare.
|
|
256
|
+
const literal = bytes.match(/const COMMAND_TEMPLATE = `((?:[^`\\]|\\.)*)`;/);
|
|
257
|
+
assert.ok(literal !== null, "COMMAND_TEMPLATE literal must exist");
|
|
258
|
+
const template = (literal[1] ?? "").replaceAll("\\`", "`");
|
|
259
|
+
const md = await readFile(commandAssetPath, "utf8");
|
|
260
|
+
assert.ok(md.startsWith(COMMAND_MARKER), "md must still carry its first-line marker");
|
|
261
|
+
const body = md.slice(md.indexOf("\n") + 1); // everything after the marker line
|
|
262
|
+
assert.ok(body.startsWith("Drive the abathur evolution harness"), "body start sanity");
|
|
263
|
+
assert.equal(template, body, "injected template must byte-equal the command md minus its marker line");
|
|
264
|
+
});
|
|
244
265
|
test("plugin/abathur-command.md: first-line marker, $ARGUMENTS, points at the abathur tool", async () => {
|
|
245
266
|
const text = await readFile(commandAssetPath, "utf8");
|
|
246
267
|
assert.equal(text.split("\n")[0], COMMAND_MARKER, "first line must be the command marker");
|
package/package.json
CHANGED
package/plugin/abathur.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
// abathur-opencode-plugin v0.2.
|
|
1
|
+
// abathur-opencode-plugin v0.2.3
|
|
2
2
|
// Official opencode plugin adapter for the abathur evolution harness.
|
|
3
3
|
// Registers ONE agent tool, `abathur`, that shells out to the abathur CLI —
|
|
4
4
|
// argv-only (node:child_process execFile, never a shell), top-level commands
|
|
@@ -9,17 +9,19 @@
|
|
|
9
9
|
// server(input, options) resolves to Hooks; Hooks.tool is a
|
|
10
10
|
// { [name]: ToolDefinition } record (see @opencode-ai/plugin).
|
|
11
11
|
// This file is shipped as-is (package.json "files") and reaches a session by
|
|
12
|
-
// two routes, neither compiling it through abathur's tsc
|
|
13
|
-
//
|
|
14
|
-
//
|
|
15
|
-
// opencode
|
|
12
|
+
// two routes, neither compiling it through abathur's tsc. Both deliver the
|
|
13
|
+
// tool AND the /abathur slash command:
|
|
14
|
+
// (A) copied into ~/.config/opencode/plugins/ by `abathur opencode install`,
|
|
15
|
+
// which also drops commands/abathur.md; "@opencode-ai/plugin" then
|
|
16
|
+
// resolves in opencode's config-directory node_modules.
|
|
16
17
|
// (B) served as the package's "./server" export when the npm package name is
|
|
17
18
|
// listed in opencode.jsonc "plugin" — opencode's arborist install places
|
|
18
19
|
// @opencode-ai/plugin (runtime dependency) next to the package in its
|
|
19
|
-
// cache, so the same import resolves there too.
|
|
20
|
+
// cache, so the same import resolves there too; with no commands/abathur.md
|
|
21
|
+
// on disk, the config hook below self-registers the command instead.
|
|
20
22
|
|
|
21
23
|
import { execFile } from "node:child_process";
|
|
22
|
-
import { tool } from "@opencode-ai/plugin";
|
|
24
|
+
import { tool, type Config } from "@opencode-ai/plugin";
|
|
23
25
|
|
|
24
26
|
/** Spawn cap: a CLI call that outlives this is killed and reported, never awaited forever. */
|
|
25
27
|
const TIMEOUT_MS = 120_000;
|
|
@@ -114,9 +116,47 @@ function runCli(bin: string, argv: readonly string[]): Promise<CliResult> {
|
|
|
114
116
|
});
|
|
115
117
|
}
|
|
116
118
|
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* /abathur slash-command template, injected into cfg.command.abathur by the
|
|
122
|
+
* config hook for installs without a commands/abathur.md on disk (Route B).
|
|
123
|
+
* Byte-mirror of plugin/abathur-command.md minus its first-line marker —
|
|
124
|
+
* src/test/opencode.test.ts pins the equality.
|
|
125
|
+
*/
|
|
126
|
+
const COMMAND_TEMPLATE = `Drive the abathur evolution harness through the \`abathur\` tool on this machine.
|
|
127
|
+
|
|
128
|
+
User request: $ARGUMENTS
|
|
129
|
+
|
|
130
|
+
Interpret the request as one \`abathur\` CLI invocation: the first word is the
|
|
131
|
+
top-level command (\`genome\`, \`run\`, \`status\`, \`bundle\`, \`graft\`, \`self-eval\`,
|
|
132
|
+
\`kernel\`, or \`--help\`) and the rest are argv tokens. Call the \`abathur\` tool
|
|
133
|
+
with \`command\` set to the first word and \`extra\` set to the remaining tokens,
|
|
134
|
+
then report the CLI exit code (0 ok / 1 blocked decision / 2 cannot-answer)
|
|
135
|
+
and the relevant lines of its output. If no request was given, call the tool
|
|
136
|
+
with \`command: "--help"\` and summarize the command list. \`promote\` and
|
|
137
|
+
\`tombstone\` cannot be called through the tool at all — the tool refuses them.
|
|
138
|
+
They are human gates that belong to a terminal: if the user asks for one,
|
|
139
|
+
tell them to run \`abathur promote …\` / \`abathur tombstone …\` there.
|
|
140
|
+
`;
|
|
141
|
+
|
|
117
142
|
export default {
|
|
118
143
|
id: "abathur",
|
|
119
144
|
server: async () => ({
|
|
145
|
+
// Self-registering /abathur: opencode calls hook.config(cfg) once per
|
|
146
|
+
// instance after ALL config sources are merged — file-based commands are
|
|
147
|
+
// already in cfg.command by then (config.ts merges {command,commands}/**/*.md;
|
|
148
|
+
// the hook fires from plugin/index.ts after config.get()). `??=` keeps a
|
|
149
|
+
// Route-A commands/abathur.md authoritative (identical behaviour to 0.2.2)
|
|
150
|
+
// and only fills the gap for Route B; the command map is keyed by name, so
|
|
151
|
+
// a same-name file + injection never duplicates the entry. Proven upstream
|
|
152
|
+
// mechanism: opencode-acp registers its /acp command exactly this way.
|
|
153
|
+
config: async (cfg: Config) => {
|
|
154
|
+
cfg.command ??= {};
|
|
155
|
+
cfg.command.abathur ??= {
|
|
156
|
+
description: "Drive the abathur evolution harness (usage: /abathur <command> [args...])",
|
|
157
|
+
template: COMMAND_TEMPLATE,
|
|
158
|
+
};
|
|
159
|
+
},
|
|
120
160
|
tool: {
|
|
121
161
|
abathur: tool({
|
|
122
162
|
description:
|