opencode-forking-agents-plugin 0.1.0 → 0.1.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 +28 -2
- package/package.json +16 -2
- package/src/index.ts +1 -1
- package/bun.lock +0 -50
- package/test/fork-subagent.test.ts +0 -77
- package/tsconfig.json +0 -12
package/README.md
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
# forking-agents-plugin
|
|
1
|
+
# opencode-forking-agents-plugin
|
|
2
2
|
|
|
3
3
|
OpenCode plugin that adds `fork_<agent>` subagents. When you run the **task** tool with `subagent_type` set to `fork_general`, `fork_explore`, or `fork_<name>` for any configured `mode: subagent` agent, the plugin rewrites the call to the base agent and prepends the **parent session transcript** to the task prompt (wrapped in `<parent_session_transcript>`).
|
|
4
4
|
|
|
5
|
+
## Available Fork Subagents
|
|
6
|
+
|
|
7
|
+
This plugin automatically creates the following fork subagents:
|
|
8
|
+
|
|
9
|
+
- **`fork_explore`** - Fork of the built-in `explore` agent. Use this when you need the subagent to have context from the parent session while exploring codebases.
|
|
10
|
+
- **`fork_general`** - Fork of the built-in `general` agent. Use this for general-purpose tasks that need parent session context.
|
|
11
|
+
- **`fork_<name>`** - For any custom agent with `mode: subagent` in your config.
|
|
12
|
+
|
|
13
|
+
Example usage:
|
|
14
|
+
```json
|
|
15
|
+
{
|
|
16
|
+
"subagent_type": "fork_explore",
|
|
17
|
+
"prompt": "Find all files that use the User type"
|
|
18
|
+
}
|
|
19
|
+
```
|
|
20
|
+
|
|
5
21
|
## Install
|
|
6
22
|
|
|
7
23
|
In `opencode.json`:
|
|
@@ -9,7 +25,7 @@ In `opencode.json`:
|
|
|
9
25
|
```json
|
|
10
26
|
{
|
|
11
27
|
"$schema": "https://opencode.ai/config.json",
|
|
12
|
-
"plugin": ["forking-agents-plugin"]
|
|
28
|
+
"plugin": ["opencode-forking-agents-plugin"]
|
|
13
29
|
}
|
|
14
30
|
```
|
|
15
31
|
|
|
@@ -19,6 +35,16 @@ OpenCode installs npm plugins automatically (see [Plugins](https://opencode.ai/d
|
|
|
19
35
|
|
|
20
36
|
Clone this repo and point `plugin` at the entry file, or use `bun link` / `npm link`.
|
|
21
37
|
|
|
38
|
+
## Release to npm
|
|
39
|
+
|
|
40
|
+
GitHub Actions publishes to npm when a GitHub release is published.
|
|
41
|
+
|
|
42
|
+
The workflow uses npm trusted publishing from GitHub Actions, so no `NPM_TOKEN` repository secret is required.
|
|
43
|
+
|
|
44
|
+
The GitHub release tag must match the current `package.json` version as either `X.Y.Z` or `vX.Y.Z`.
|
|
45
|
+
|
|
46
|
+
Versions with a prerelease suffix such as `0.2.0-beta.1` publish to the `next` dist-tag. Versions without a prerelease suffix publish to `latest`.
|
|
47
|
+
|
|
22
48
|
## Disable
|
|
23
49
|
|
|
24
50
|
Set `OPENCODE_DISABLE_FORK_SUBAGENT_PLUGIN=1`.
|
package/package.json
CHANGED
|
@@ -1,9 +1,18 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "opencode-forking-agents-plugin",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.3",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"description": "OpenCode plugin: fork_* subagents with parent session transcript prepended to task prompts",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/wkronmiller/forking-agents-plugin.git"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"src",
|
|
13
|
+
"README.md",
|
|
14
|
+
"LICENSE"
|
|
15
|
+
],
|
|
7
16
|
"exports": {
|
|
8
17
|
".": {
|
|
9
18
|
"types": "./src/index.ts",
|
|
@@ -22,5 +31,10 @@
|
|
|
22
31
|
"typecheck": "tsc --noEmit",
|
|
23
32
|
"test": "bun test"
|
|
24
33
|
},
|
|
25
|
-
"keywords": [
|
|
34
|
+
"keywords": [
|
|
35
|
+
"opencode",
|
|
36
|
+
"plugin",
|
|
37
|
+
"subagent",
|
|
38
|
+
"fork"
|
|
39
|
+
]
|
|
26
40
|
}
|
package/src/index.ts
CHANGED
package/bun.lock
DELETED
|
@@ -1,50 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"lockfileVersion": 1,
|
|
3
|
-
"configVersion": 1,
|
|
4
|
-
"workspaces": {
|
|
5
|
-
"": {
|
|
6
|
-
"name": "forking-agents-plugin",
|
|
7
|
-
"devDependencies": {
|
|
8
|
-
"@opencode-ai/plugin": "1.4.3",
|
|
9
|
-
"@types/bun": "1.2.23",
|
|
10
|
-
"typescript": "5.9.2",
|
|
11
|
-
},
|
|
12
|
-
"peerDependencies": {
|
|
13
|
-
"@opencode-ai/plugin": ">=1.4.3",
|
|
14
|
-
},
|
|
15
|
-
},
|
|
16
|
-
},
|
|
17
|
-
"packages": {
|
|
18
|
-
"@opencode-ai/plugin": ["@opencode-ai/plugin@1.4.3", "", { "dependencies": { "@opencode-ai/sdk": "1.4.3", "zod": "4.1.8" }, "peerDependencies": { "@opentui/core": ">=0.1.97", "@opentui/solid": ">=0.1.97" }, "optionalPeers": ["@opentui/core", "@opentui/solid"] }, "sha512-Ob/3tVSIeuMRJBr2O23RtrnC5djRe01Lglx+TwGEmjrH9yDBJ2tftegYLnNEjRoMuzITgq9LD8168p4pzv+U/A=="],
|
|
19
|
-
|
|
20
|
-
"@opencode-ai/sdk": ["@opencode-ai/sdk@1.4.3", "", { "dependencies": { "cross-spawn": "7.0.6" } }, "sha512-X0CAVbwoGAjTY2iecpWkx2B+GAa2jSaQKYpJ+xILopeF/OGKZUN15mjqci+L7cEuwLHV5wk3x2TStUOVCa5p0A=="],
|
|
21
|
-
|
|
22
|
-
"@types/bun": ["@types/bun@1.2.23", "", { "dependencies": { "bun-types": "1.2.23" } }, "sha512-le8ueOY5b6VKYf19xT3McVbXqLqmxzPXHsQT/q9JHgikJ2X22wyTW3g3ohz2ZMnp7dod6aduIiq8A14Xyimm0A=="],
|
|
23
|
-
|
|
24
|
-
"@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
|
25
|
-
|
|
26
|
-
"@types/react": ["@types/react@19.2.14", "", { "dependencies": { "csstype": "^3.2.2" } }, "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w=="],
|
|
27
|
-
|
|
28
|
-
"bun-types": ["bun-types@1.2.23", "", { "dependencies": { "@types/node": "*" }, "peerDependencies": { "@types/react": "^19" } }, "sha512-R9f0hKAZXgFU3mlrA0YpE/fiDvwV0FT9rORApt2aQVWSuJDzZOyB5QLc0N/4HF57CS8IXJ6+L5E4W1bW6NS2Aw=="],
|
|
29
|
-
|
|
30
|
-
"cross-spawn": ["cross-spawn@7.0.6", "", { "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" } }, "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA=="],
|
|
31
|
-
|
|
32
|
-
"csstype": ["csstype@3.2.3", "", {}, "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ=="],
|
|
33
|
-
|
|
34
|
-
"isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
|
35
|
-
|
|
36
|
-
"path-key": ["path-key@3.1.1", "", {}, "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="],
|
|
37
|
-
|
|
38
|
-
"shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="],
|
|
39
|
-
|
|
40
|
-
"shebang-regex": ["shebang-regex@3.0.0", "", {}, "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="],
|
|
41
|
-
|
|
42
|
-
"typescript": ["typescript@5.9.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A=="],
|
|
43
|
-
|
|
44
|
-
"undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
|
45
|
-
|
|
46
|
-
"which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
|
47
|
-
|
|
48
|
-
"zod": ["zod@4.1.8", "", {}, "sha512-5R1P+WwQqmmMIEACyzSvo4JXHY5WiAFHRMg+zBZKgKS+Q1viRa0C1hmUKtHltoIFKtIdki3pRxkmpP74jnNYHQ=="],
|
|
49
|
-
}
|
|
50
|
-
}
|
|
@@ -1,77 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test"
|
|
2
|
-
import forkSubagentPlugin, { type ForkPluginInput } from "../src/index.js"
|
|
3
|
-
|
|
4
|
-
function ctx(over: Partial<ForkPluginInput> = {}): ForkPluginInput {
|
|
5
|
-
return {
|
|
6
|
-
client: {
|
|
7
|
-
app: { log: () => Promise.resolve({} as never) },
|
|
8
|
-
},
|
|
9
|
-
directory: "/tmp",
|
|
10
|
-
worktree: "/tmp",
|
|
11
|
-
project: {} as never,
|
|
12
|
-
serverUrl: new URL("http://localhost:4096"),
|
|
13
|
-
$: undefined as never,
|
|
14
|
-
listSessionMessages: async () => [],
|
|
15
|
-
...over,
|
|
16
|
-
} as ForkPluginInput
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe("forkSubagentPlugin", () => {
|
|
20
|
-
test("config adds fork_* agents for builtins and configured subagents", async () => {
|
|
21
|
-
const hooks = await forkSubagentPlugin(ctx())
|
|
22
|
-
const cfg: { agent?: Record<string, { mode?: string; description?: string; hidden?: boolean }> } = {
|
|
23
|
-
agent: {
|
|
24
|
-
zebra: { mode: "subagent", description: "Zebra" },
|
|
25
|
-
},
|
|
26
|
-
}
|
|
27
|
-
await hooks.config?.(cfg as never)
|
|
28
|
-
expect(cfg.agent?.fork_general?.mode).toBe("subagent")
|
|
29
|
-
expect(cfg.agent?.fork_explore?.hidden).toBe(true)
|
|
30
|
-
expect(cfg.agent?.fork_zebra?.mode).toBe("subagent")
|
|
31
|
-
expect(cfg.agent?.fork_zebra?.description).toContain("Fork of @zebra")
|
|
32
|
-
})
|
|
33
|
-
|
|
34
|
-
test("config does not replace an existing fork entry", async () => {
|
|
35
|
-
const hooks = await forkSubagentPlugin(ctx())
|
|
36
|
-
const cfg = {
|
|
37
|
-
agent: {
|
|
38
|
-
fork_general: { mode: "subagent" as const, description: "custom" },
|
|
39
|
-
},
|
|
40
|
-
}
|
|
41
|
-
await hooks.config?.(cfg as never)
|
|
42
|
-
expect(cfg.agent.fork_general.description).toBe("custom")
|
|
43
|
-
})
|
|
44
|
-
|
|
45
|
-
test("tool.execute.before rewrites fork_* and prepends transcript", async () => {
|
|
46
|
-
const hooks = await forkSubagentPlugin(
|
|
47
|
-
ctx({
|
|
48
|
-
directory: "/proj",
|
|
49
|
-
worktree: "/proj",
|
|
50
|
-
listSessionMessages: async () => [
|
|
51
|
-
{
|
|
52
|
-
info: { role: "user" },
|
|
53
|
-
parts: [{ type: "text", text: "hello" }],
|
|
54
|
-
},
|
|
55
|
-
{
|
|
56
|
-
info: { role: "assistant" },
|
|
57
|
-
parts: [{ type: "text", text: "hi" }],
|
|
58
|
-
},
|
|
59
|
-
],
|
|
60
|
-
}),
|
|
61
|
-
)
|
|
62
|
-
const fn = hooks["tool.execute.before"]
|
|
63
|
-
expect(fn).toBeDefined()
|
|
64
|
-
const out = {
|
|
65
|
-
args: {
|
|
66
|
-
subagent_type: "fork_explore",
|
|
67
|
-
prompt: "find foo",
|
|
68
|
-
},
|
|
69
|
-
}
|
|
70
|
-
await fn!({ tool: "task", sessionID: "sess-1", callID: "c1" } as never, out as never)
|
|
71
|
-
expect(out.args.subagent_type).toBe("explore")
|
|
72
|
-
expect(out.args.prompt).toContain("<parent_session_transcript>")
|
|
73
|
-
expect(out.args.prompt).toContain("USER:\nhello")
|
|
74
|
-
expect(out.args.prompt).toContain("<task>")
|
|
75
|
-
expect(out.args.prompt).toContain("find foo")
|
|
76
|
-
})
|
|
77
|
-
})
|
package/tsconfig.json
DELETED