mirror of
https://github.com/Heretek-AI/opencode-v2-plugin-teser.git
synced 2026-11-07 11:05:59 -10:00
M6: fixture plugins for every surface (pass/fail, throws, tui, no-entry)
This commit is contained in:
1 parent
42aca14049
commit
f7816fdb9e
51 files changed
+473
No files matched your search
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,14 @@
|
||||
id: event-file-pass
|
||||
description: event.subscribe side effect writes .fixture/events.log on tool calls
|
||||
mode: scripted
|
||||
prompt: Run a tool so the event fires.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: shell
|
||||
arguments: { command: "echo hi", description: "fire an event" }
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
files:
|
||||
- path: .fixture/events.log
|
||||
exists: true
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# fixture-event-file
|
||||
|
||||
Proves a plugin can observe `ctx.event.subscribe()` and produce a side effect
|
||||
on disk: on every `session.tool.called` event it appends to
|
||||
`.fixture/events.log` in the project directory (`ctx.location.directory`).
|
||||
|
||||
- The subscription runs in a background loop; `setup` does not await it.
|
||||
- `pass.yaml` runs one `shell` call and asserts the log file exists.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-event-file",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+27
@@ -0,0 +1,27 @@
|
||||
// Fixture: subscribes to session events in a background loop (never awaited
|
||||
// in setup, or setup would never return) and appends a line to
|
||||
// .fixture/events.log in the project directory on every session.tool.called.
|
||||
import { appendFileSync, mkdirSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
|
||||
export default {
|
||||
id: "fixture-event-file",
|
||||
setup: async (ctx) => {
|
||||
const directory = ctx.location.directory;
|
||||
void (async () => {
|
||||
try {
|
||||
for await (const event of ctx.event.subscribe()) {
|
||||
if (event?.type === "session.tool.called") {
|
||||
mkdirSync(join(directory, ".fixture"), { recursive: true });
|
||||
appendFileSync(
|
||||
join(directory, ".fixture", "events.log"),
|
||||
`${JSON.stringify({ type: event.type, at: Date.now() })}\n`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch {
|
||||
// The event stream ends when the run ends; nothing to do.
|
||||
}
|
||||
})();
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,23 @@
|
||||
id: hook-mutate-pass
|
||||
description: execute.before redacts "secret" from the shell command
|
||||
mode: scripted
|
||||
prompt: Print the word secret.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: shell
|
||||
arguments: { command: "echo secret", description: "print secret" }
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
hooks:
|
||||
- hook: tool.execute.before
|
||||
phase: pre
|
||||
tool: shell
|
||||
input: { command: { contains: secret } }
|
||||
- hook: tool.execute.before
|
||||
phase: post
|
||||
tool: shell
|
||||
input:
|
||||
command:
|
||||
notContains: secret
|
||||
contains: "[x]"
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# fixture-hook-mutate
|
||||
|
||||
Proves a plugin can mutate tool inputs in `execute.before`: the hook replaces
|
||||
`secret` with `[x]` in `shell` commands.
|
||||
|
||||
- The `pre` probe records the original input (`contains: secret`); the `post`
|
||||
probe records the redacted input (`notContains: secret`).
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-hook-mutate",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+20
@@ -0,0 +1,20 @@
|
||||
// Fixture: an execute.before hook that redacts the word "secret" from shell
|
||||
// commands, replacing it with "[x]". The pre probe records the original
|
||||
// input; the post probe records the mutated input.
|
||||
export default {
|
||||
id: "fixture-hook-mutate",
|
||||
setup: async (ctx) => {
|
||||
await ctx.tool.hook("execute.before", (i) => {
|
||||
if (i.tool !== "shell") {
|
||||
return;
|
||||
}
|
||||
const input = i.input;
|
||||
if (input !== null && typeof input === "object" && "command" in input) {
|
||||
const record = input as Record<string, unknown>;
|
||||
if (typeof record.command === "string") {
|
||||
record.command = record.command.replaceAll("secret", "[x]");
|
||||
}
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,8 @@
|
||||
id: no-entry-smoke
|
||||
description: Documents the expected outcome (validate/run exits 2, no server entry)
|
||||
mode: scripted
|
||||
prompt: Hello.
|
||||
script:
|
||||
- text: ok
|
||||
expect:
|
||||
exitCode: 0
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
# fixture-no-entry
|
||||
|
||||
Proves a plugin directory with no loadable server entry is a configuration
|
||||
error: `package.json` has no `exports` or `main`, and there is no
|
||||
`server.*` or `index.*` at the root (`src/helper.ts` is not an entry).
|
||||
|
||||
- `validate`/`run` must exit 2 with a clear "no server entry" message.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "fixture-no-entry",
|
||||
"type": "module"
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Deliberately NOT an entry point: there is no server.*/index.* at the
|
||||
// plugin root and package.json has no exports or main, so the entry
|
||||
// resolver must report "no server entry".
|
||||
export function helper(): string {
|
||||
return "not an entry";
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,20 @@
|
||||
id: shell-env-pass
|
||||
description: shell.create.before injects FIXTURE_ON=1 into the shell env
|
||||
mode: scripted
|
||||
prompt: Show the fixture env var.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: shell
|
||||
arguments:
|
||||
command: "echo FIXTURE_ON=$FIXTURE_ON"
|
||||
description: "print fixture env"
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
hooks:
|
||||
- hook: shell.create.before
|
||||
env: { FIXTURE_ON: { equals: "1" } }
|
||||
tools:
|
||||
- name: shell
|
||||
status: completed
|
||||
output: { contains: "FIXTURE_ON=1" }
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# fixture-shell-env
|
||||
|
||||
Proves a plugin can inject environment variables via
|
||||
`shell.hook("create.before")`: every spawned shell sees `FIXTURE_ON=1`.
|
||||
|
||||
- `pass.yaml` asserts the post probe's `shell.before` record has
|
||||
`FIXTURE_ON equals 1` and that running `echo FIXTURE_ON=$FIXTURE_ON`
|
||||
outputs `FIXTURE_ON=1`.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-shell-env",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+14
@@ -0,0 +1,14 @@
|
||||
// Fixture: a shell.create.before hook that injects FIXTURE_ON=1 into the
|
||||
// environment of every spawned shell.
|
||||
export default {
|
||||
id: "fixture-shell-env",
|
||||
setup: async (ctx) => {
|
||||
await ctx.shell.hook("create.before", (i) => {
|
||||
if (i.env && typeof i.env === "object") {
|
||||
i.env.FIXTURE_ON = "1";
|
||||
} else {
|
||||
i.env = { FIXTURE_ON: "1" };
|
||||
}
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,11 @@
|
||||
id: system-inject-pass
|
||||
description: session context hook appends FIXTURE-SYSTEM to the system prompt
|
||||
mode: scripted
|
||||
prompt: Hello.
|
||||
script:
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
request:
|
||||
nth: 1
|
||||
system: { contains: FIXTURE-SYSTEM }
|
||||
@@ -0,0 +1,7 @@
|
||||
# fixture-system-inject
|
||||
|
||||
Proves a plugin can extend the system prompt via `session.hook("context")`:
|
||||
the hook pushes a `{ type: "text", text: "FIXTURE-SYSTEM" }` part.
|
||||
|
||||
- `pass.yaml` asserts the first primary stub request's system text contains
|
||||
`FIXTURE-SYSTEM` (stub-recorded, so no model is needed).
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-system-inject",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
// Fixture: a session context hook that appends a marker part to the system
|
||||
// prompt, so the stub-recorded request carries FIXTURE-SYSTEM.
|
||||
export default {
|
||||
id: "fixture-system-inject",
|
||||
setup: async (ctx) => {
|
||||
await ctx.session.hook("context", (i) => {
|
||||
i.system.push({ type: "text", text: "FIXTURE-SYSTEM" });
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,2 @@
|
||||
plugin:
|
||||
path: .
|
||||
@@ -0,0 +1,8 @@
|
||||
id: throws-smoke
|
||||
description: Documents the expected outcome (tier 1 fails with "fixture boom")
|
||||
mode: scripted
|
||||
prompt: Hello.
|
||||
script:
|
||||
- text: ok
|
||||
expect:
|
||||
exitCode: 0
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
# fixture-throws
|
||||
|
||||
Proves load failures are reported: `setup` throws `Error("fixture boom")`.
|
||||
|
||||
- Tier 1 must fail with `fixture boom` in the failure text (inventory status
|
||||
`failed`, plus a `failed to load plugin` stderr line).
|
||||
- `smoke.yaml` only documents the shape of a run against this fixture; it can
|
||||
never pass while `setup` throws.
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-throws",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Fixture: setup throws, so tier 1 must fail with "fixture boom" in the
|
||||
// failure text (the probe inventory reports the plugin as failed).
|
||||
export default {
|
||||
id: "fixture-throws",
|
||||
setup: async () => {
|
||||
throw new Error("fixture boom");
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
plugin:
|
||||
path: .
|
||||
expect:
|
||||
tools: [echo_tool]
|
||||
@@ -0,0 +1,15 @@
|
||||
id: tool-basic-fail
|
||||
description: Deliberately failing scenario (expects echo:bye, the tool returns echo:hi)
|
||||
mode: scripted
|
||||
prompt: Say hi through the echo tool.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: echo_tool
|
||||
arguments: { text: hi }
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
tools:
|
||||
- name: echo_tool
|
||||
status: completed
|
||||
output: { contains: "echo:bye" }
|
||||
@@ -0,0 +1,15 @@
|
||||
id: tool-basic-pass
|
||||
description: echo_tool is called and returns echo:hi
|
||||
mode: scripted
|
||||
prompt: Say hi through the echo tool.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: echo_tool
|
||||
arguments: { text: hi }
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
tools:
|
||||
- name: echo_tool
|
||||
status: completed
|
||||
output: { contains: "echo:hi" }
|
||||
+12
@@ -0,0 +1,12 @@
|
||||
# fixture-tool-basic
|
||||
|
||||
Proves a plugin tool added with `ctx.tool.transform` works through Code Mode
|
||||
(the default): the model calls it via the built-in `execute` tool and the
|
||||
probe sees the inner call under its real name.
|
||||
|
||||
- `src/server.ts` adds `echo_tool`, which returns `echo:<text>`. The transform
|
||||
is idempotent (checks `editor.get` first, with an `editor.list` fallback).
|
||||
- `package.json` points `exports["./server"]` at `./src/server.ts`, so the
|
||||
entry resolver and shim are exercised (opencode itself ignores `exports`).
|
||||
- `pass.yaml` asserts the call completes with output containing `echo:hi`.
|
||||
- `fail.yaml` expects `echo:bye` and must fail.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-tool-basic",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Fixture: adds echo_tool in Code Mode (the default) and returns "echo:<text>".
|
||||
// The transform is idempotent: opencode re-runs transforms on reloads, so the
|
||||
// tool is only added when it is not already registered.
|
||||
export default {
|
||||
id: "fixture-tool-basic",
|
||||
setup: async (ctx) => {
|
||||
await ctx.tool.transform((editor) => {
|
||||
let existing = false;
|
||||
try {
|
||||
existing = editor.get("echo_tool") !== undefined;
|
||||
} catch {
|
||||
try {
|
||||
existing = editor.list().some((tool) => tool.id === "echo_tool");
|
||||
} catch {
|
||||
existing = false;
|
||||
}
|
||||
}
|
||||
if (existing) {
|
||||
return;
|
||||
}
|
||||
editor.add({
|
||||
name: "echo_tool",
|
||||
description: "Echo the input text back",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: { text: { type: "string" } },
|
||||
required: ["text"],
|
||||
},
|
||||
execute: async (input) => ({ content: `echo:${input.text}` }),
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,4 @@
|
||||
plugin:
|
||||
path: .
|
||||
expect:
|
||||
tools: [echo_tool]
|
||||
@@ -0,0 +1,18 @@
|
||||
id: tool-direct-pass
|
||||
description: echo_tool is offered directly (codemode false) and returns echo:hi
|
||||
mode: scripted
|
||||
prompt: Say hi through the echo tool.
|
||||
script:
|
||||
- toolCalls:
|
||||
- name: echo_tool
|
||||
arguments: { text: hi }
|
||||
- text: done
|
||||
expect:
|
||||
exitCode: 0
|
||||
tools:
|
||||
- name: echo_tool
|
||||
status: completed
|
||||
output: { contains: "echo:hi" }
|
||||
request:
|
||||
nth: 1
|
||||
tools: { includes: [echo_tool] }
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
# fixture-tool-direct
|
||||
|
||||
Proves `options: { codemode: false }` makes a plugin tool a direct tool: it
|
||||
appears in the model request's `tools` array instead of only in the Code Mode
|
||||
catalog.
|
||||
|
||||
- `src/server.ts` adds `echo_tool` with `codemode: false` (idempotent
|
||||
transform, as in `tool-basic`).
|
||||
- `pass.yaml` asserts the call completes with `echo:hi` and that the first
|
||||
primary stub request offers `echo_tool` directly.
|
||||
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"name": "fixture-tool-direct",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
"./server": "./src/server.ts"
|
||||
}
|
||||
}
|
||||
+33
@@ -0,0 +1,33 @@
|
||||
// Fixture: same as tool-basic, but with `options: { codemode: false }`, so the
|
||||
// tool is offered to the model directly instead of through Code Mode.
|
||||
export default {
|
||||
id: "fixture-tool-direct",
|
||||
setup: async (ctx) => {
|
||||
await ctx.tool.transform((editor) => {
|
||||
let existing = false;
|
||||
try {
|
||||
existing = editor.get("echo_tool") !== undefined;
|
||||
} catch {
|
||||
try {
|
||||
existing = editor.list().some((tool) => tool.id === "echo_tool");
|
||||
} catch {
|
||||
existing = false;
|
||||
}
|
||||
}
|
||||
if (existing) {
|
||||
return;
|
||||
}
|
||||
editor.add({
|
||||
name: "echo_tool",
|
||||
description: "Echo the input text back",
|
||||
input: {
|
||||
type: "object",
|
||||
properties: { text: { type: "string" } },
|
||||
required: ["text"],
|
||||
},
|
||||
options: { codemode: false },
|
||||
execute: async (input) => ({ content: `echo:${input.text}` }),
|
||||
});
|
||||
});
|
||||
},
|
||||
};
|
||||
@@ -0,0 +1,3 @@
|
||||
plugin:
|
||||
path: .
|
||||
ui: true
|
||||
@@ -0,0 +1,8 @@
|
||||
id: tui-broken-smoke
|
||||
description: Documents the expected outcome (static tier 1 fails on import)
|
||||
mode: scripted
|
||||
prompt: Hello.
|
||||
script:
|
||||
- text: ok
|
||||
expect:
|
||||
exitCode: 0
|
||||
+5
@@ -0,0 +1,5 @@
|
||||
# fixture-tui-broken
|
||||
|
||||
Proves a TUI entry that throws on import fails static tier 1.
|
||||
|
||||
- Importing `tui.ts` throws `Error("tui boom")`; the static check must fail.
|
||||
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "fixture-tui-broken",
|
||||
"type": "module"
|
||||
}
|
||||
+2
@@ -0,0 +1,2 @@
|
||||
// Fixture: a TUI entry that throws on import, so static tier 1 fails.
|
||||
throw new Error("tui boom");
|
||||
@@ -0,0 +1,3 @@
|
||||
plugin:
|
||||
path: .
|
||||
ui: true
|
||||
@@ -0,0 +1,8 @@
|
||||
id: tui-only-smoke
|
||||
description: TUI-only plugin (static tier 1 passes, tiers 2 and 3 skipped)
|
||||
mode: scripted
|
||||
prompt: Hello.
|
||||
script:
|
||||
- text: ok
|
||||
expect:
|
||||
exitCode: 0
|
||||
+6
@@ -0,0 +1,6 @@
|
||||
# fixture-tui-only
|
||||
|
||||
Proves TUI-only plugins get a static tier 1: `tui.ts` exports
|
||||
`{ id: "fixture-tui", setup() {} }` and there is no server entry.
|
||||
|
||||
- Static tier 1 passes; tiers 2 and 3 are reported `skipped`.
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "fixture-tui-only",
|
||||
"type": "module"
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
// Fixture: a TUI-only plugin (no server entry). Static tier 1 imports this
|
||||
// module and checks the default export has a string id and a setup function;
|
||||
// tiers 2 and 3 are reported skipped.
|
||||
export default {
|
||||
id: "fixture-tui",
|
||||
setup() {},
|
||||
};
|
||||
Reference in new issue
Block a user