This commit is contained in:
@@ -1,3 +1,7 @@
|
|||||||
# CI-safe tests and scripts
|
# CI-safe tests and scripts
|
||||||
|
|
||||||
Do not assume local directories, files, tools, or generated artifacts exist in a clean CI environment. In particular, `/tmp/opencode` is an agent-environment convenience, not a CI guarantee. Tests should create temporary directories with `node:os` `tmpdir()` and `mkdtemp(join(tmpdir(), prefix))`, explicitly create required parent directories and fixtures, and clean up the files they create.
|
Do not assume local directories, files, tools, or generated artifacts exist in a clean CI environment. In particular, `/tmp/opencode` is an agent-environment convenience, not a CI guarantee. Tests should create temporary directories with `node:os` `tmpdir()` and `mkdtemp(join(tmpdir(), prefix))`, explicitly create required parent directories and fixtures, and clean up the files they create.
|
||||||
|
|
||||||
|
# README
|
||||||
|
|
||||||
|
Keep README.md to the absolutely required sections (installation, configuration, usage) with at most one short sentence of prose per section, plus code blocks.
|
||||||
|
|||||||
@@ -1,36 +1,19 @@
|
|||||||
# stanp-opencode
|
# stanp-opencode
|
||||||
|
|
||||||
## Release installation
|
## Installation
|
||||||
|
|
||||||
Requires OpenCode 2.0.22, Bun 1.4.2+, Bash, curl, jq, and `sha256sum` on Linux/WSL.
|
Requires OpenCode 2.0.22, Bun 1.4.2+, Bash, curl, jq and `sha256sum` on Linux/WSL.
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
curl -fsSL https://git.krtss.de/StanPonomarev/stanp-opencode/raw/branch/main/install.sh | bash -s -- --plugins agents,multi-work,quota,optchat
|
curl -fsSL https://git.krtss.de/StanPonomarev/stanp-opencode/raw/branch/main/install.sh | bash -s -- --plugins agents,multi-work,quota,optchat
|
||||||
```
|
```
|
||||||
|
|
||||||
Choose any plugin subset; Multi-work requires Agents first.
|
|
||||||
|
|
||||||
Quota shows ChatGPT limits for the connected OpenAI account and, when Claude Code is logged in, Claude subscription limits read from `${CLAUDE_CONFIG_DIR:-~/.claude}/.credentials.json` (read-only; Claude Code refreshes the token).
|
|
||||||
|
|
||||||
Optchat implements [VictorTaelin's optchat recipe](https://gist.github.com/VictorTaelin/91837951a5ce5b38f341ec1ba1df6449). It is off by default; run `/optchat on`, `/optchat off`, or `/optchat status` in a root session. In an enabled session, each turn sees a constant-size view of the whole chat, made of one-line summaries, plus the new message. The model can open any line with the `zoom` tool and read message times with `date`. Optchat does not apply to subagent sessions. Memory is stored in `~/.local/share/opencode/optchat/<sessionID>/`. Compactions run in a child session named "optchat compactions". Optchat activates only when the `compactionModel` option is set, in the format `provider/model[#variant]` (for example `opencode/claude-haiku-5-5#xhigh`); without it (or when empty) the plugin registers nothing. A non-empty `dataDir` overrides the memory location. The installer writes both options as empty strings for you to fill in.
|
|
||||||
|
|
||||||
## Local installation
|
|
||||||
|
|
||||||
From a local checkout, run:
|
|
||||||
|
|
||||||
```sh
|
|
||||||
bash /path/to/stanp-opencode/install-local.sh --plugins agents,multi-work,quota
|
|
||||||
opencode service restart
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Agents registers nothing until its `agents` option is set. The installer writes every missing option with an empty value (`""`) for you to fill in; an empty value means unset, so a fresh install stays inert. Each assignment is `provider/model[#variant]`; the variant selects the model's reasoning variant, falling back to a `reasoningEffort` request setting when the model does not advertise it. Merge this into `~/.config/opencode/opencode.json`, preserving unrelated settings and plugins; update existing plugin entries instead of appending duplicates. The `deep-lo` and `deep-hi` assignments are optional as a pair; if either is omitted, both deep workers are disabled and orchestration uses `good-lo`.
|
Fill in the empty values the installer writes to `~/.config/opencode/opencode.json`; empty plugins stay inactive.
|
||||||
|
|
||||||
```jsonc
|
```jsonc
|
||||||
{
|
{
|
||||||
"$schema": "https://opencode.ai/config.json",
|
|
||||||
// Required at the root and in worktrees.
|
|
||||||
"experimental": { "subagent_depth": 2 },
|
"experimental": { "subagent_depth": 2 },
|
||||||
"plugins": [
|
"plugins": [
|
||||||
{
|
{
|
||||||
@@ -48,6 +31,14 @@ Agents registers nothing until its `agents` option is set. The installer writes
|
|||||||
},
|
},
|
||||||
{ "package": "./plugins/stanp-opencode/multi-work" },
|
{ "package": "./plugins/stanp-opencode/multi-work" },
|
||||||
{ "package": "./plugins/stanp-opencode/quota" },
|
{ "package": "./plugins/stanp-opencode/quota" },
|
||||||
|
{
|
||||||
|
"package": "./plugins/stanp-opencode/optchat",
|
||||||
|
"options": { "compactionModel": "opencode/claude-haiku-5-5#xhigh", "dataDir": "" },
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
Run `/optchat on|off|status` in a root session to toggle optchat.
|
||||||
|
|||||||
+9
-1
@@ -42,6 +42,7 @@ const pluginSchema = z.union([z.string(), entrySchema]);
|
|||||||
const registrationSchema = z
|
const registrationSchema = z
|
||||||
.union([z.string().transform((packageName) => ({ package: packageName })), entrySchema])
|
.union([z.string().transform((packageName) => ({ package: packageName })), entrySchema])
|
||||||
.pipe(entrySchema);
|
.pipe(entrySchema);
|
||||||
|
const experimentalSchema = z.object({ subagent_depth: z.number().optional() }).catchall(z.json());
|
||||||
const configSchema = z
|
const configSchema = z
|
||||||
.object({ plugins: z.array(pluginSchema).optional(), $schema: z.string().optional() })
|
.object({ plugins: z.array(pluginSchema).optional(), $schema: z.string().optional() })
|
||||||
.catchall(z.json());
|
.catchall(z.json());
|
||||||
@@ -145,11 +146,18 @@ export async function install(source: string, config: string, selection?: string
|
|||||||
const first = configured.findIndex((entry) => isOwned(entry.package));
|
const first = configured.findIndex((entry) => isOwned(entry.package));
|
||||||
const next = entries.filter((_entry, index) => !isOwned(configured[index]!.package));
|
const next = entries.filter((_entry, index) => !isOwned(configured[index]!.package));
|
||||||
next.splice(first < 0 ? next.length : first, 0, ...registrations);
|
next.splice(first < 0 ? next.length : first, 0, ...registrations);
|
||||||
const updated = {
|
const updated: z.infer<typeof configSchema> = {
|
||||||
...current,
|
...current,
|
||||||
$schema: current.$schema ?? "https://opencode.ai/config.json",
|
$schema: current.$schema ?? "https://opencode.ai/config.json",
|
||||||
plugins: next,
|
plugins: next,
|
||||||
};
|
};
|
||||||
|
// Coordinate delegates to workers from Multi-work children, so routing needs depth >= 2.
|
||||||
|
const experimental = experimentalSchema.safeParse(current.experimental ?? {});
|
||||||
|
if (selected.includes("agents") && experimental.success)
|
||||||
|
updated.experimental = {
|
||||||
|
...experimental.data,
|
||||||
|
subagent_depth: Math.max(experimental.data.subagent_depth ?? 0, 2),
|
||||||
|
};
|
||||||
const parent = join(config, "plugins");
|
const parent = join(config, "plugins");
|
||||||
await mkdir(parent, { recursive: true });
|
await mkdir(parent, { recursive: true });
|
||||||
const staged = join(parent, `.stanp-opencode.new.${process.pid}`);
|
const staged = join(parent, `.stanp-opencode.new.${process.pid}`);
|
||||||
|
|||||||
@@ -265,6 +265,30 @@ test("installer writes empty option skeletons, fills only missing keys, and is i
|
|||||||
expect(await contents(f.config)).toEqual(filled);
|
expect(await contents(f.config)).toEqual(filled);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test("agents installs set experimental.subagent_depth to at least 2", async () => {
|
||||||
|
const f = await fixture();
|
||||||
|
await install(f.source, f.config, "quota");
|
||||||
|
expect((await contents(f.config)).experimental).toBeUndefined();
|
||||||
|
await install(f.source, f.config, "agents");
|
||||||
|
const fresh = await contents(f.config);
|
||||||
|
expect(fresh.experimental).toEqual({ subagent_depth: 2 });
|
||||||
|
expect(fresh).toMatchObject(unrelated);
|
||||||
|
for (const [depth, expected] of [
|
||||||
|
[1, 2],
|
||||||
|
[3, 3],
|
||||||
|
]) {
|
||||||
|
await writeFile(
|
||||||
|
join(f.config, "opencode.json"),
|
||||||
|
JSON.stringify({ ...fresh, experimental: { subagent_depth: depth, other: true } }),
|
||||||
|
);
|
||||||
|
await install(f.source, f.config);
|
||||||
|
const result = await contents(f.config);
|
||||||
|
expect(result.experimental).toEqual({ subagent_depth: expected, other: true });
|
||||||
|
await install(f.source, f.config);
|
||||||
|
expect(await contents(f.config)).toEqual(result);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
test("local installer retains executable permissions and supports direct invocation", async () => {
|
test("local installer retains executable permissions and supports direct invocation", async () => {
|
||||||
const f = await fixture();
|
const f = await fixture();
|
||||||
const path = new URL("../install-local.sh", import.meta.url).pathname;
|
const path = new URL("../install-local.sh", import.meta.url).pathname;
|
||||||
|
|||||||
Reference in New Issue
Block a user