opencode-forking-agents-plugin 0.1.0
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/LICENSE +21 -0
- package/README.md +32 -0
- package/bun.lock +50 -0
- package/package.json +26 -0
- package/src/index.ts +132 -0
- package/test/fork-subagent.test.ts +77 -0
- package/tsconfig.json +12 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 wkronmiller
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
# forking-agents-plugin
|
|
2
|
+
|
|
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
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
In `opencode.json`:
|
|
8
|
+
|
|
9
|
+
```json
|
|
10
|
+
{
|
|
11
|
+
"$schema": "https://opencode.ai/config.json",
|
|
12
|
+
"plugin": ["forking-agents-plugin"]
|
|
13
|
+
}
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
OpenCode installs npm plugins automatically (see [Plugins](https://opencode.ai/docs/plugins/)).
|
|
17
|
+
|
|
18
|
+
## Local development
|
|
19
|
+
|
|
20
|
+
Clone this repo and point `plugin` at the entry file, or use `bun link` / `npm link`.
|
|
21
|
+
|
|
22
|
+
## Disable
|
|
23
|
+
|
|
24
|
+
Set `OPENCODE_DISABLE_FORK_SUBAGENT_PLUGIN=1`.
|
|
25
|
+
|
|
26
|
+
## Requirements
|
|
27
|
+
|
|
28
|
+
OpenCode must pass **`listSessionMessages`** on the plugin input (session store, no HTTP). This matches `@opencode-ai/plugin` **1.5.0+** in the main OpenCode repo; the published npm plugin package types may lag—runtime behavior follows OpenCode.
|
|
29
|
+
|
|
30
|
+
## License
|
|
31
|
+
|
|
32
|
+
MIT
|
package/bun.lock
ADDED
|
@@ -0,0 +1,50 @@
|
|
|
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
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "opencode-forking-agents-plugin",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"description": "OpenCode plugin: fork_* subagents with parent session transcript prepended to task prompts",
|
|
7
|
+
"exports": {
|
|
8
|
+
".": {
|
|
9
|
+
"types": "./src/index.ts",
|
|
10
|
+
"default": "./src/index.ts"
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"peerDependencies": {
|
|
14
|
+
"@opencode-ai/plugin": ">=1.4.3"
|
|
15
|
+
},
|
|
16
|
+
"devDependencies": {
|
|
17
|
+
"@opencode-ai/plugin": "1.4.3",
|
|
18
|
+
"@types/bun": "1.2.23",
|
|
19
|
+
"typescript": "5.9.2"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"typecheck": "tsc --noEmit",
|
|
23
|
+
"test": "bun test"
|
|
24
|
+
},
|
|
25
|
+
"keywords": ["opencode", "plugin", "subagent", "fork"]
|
|
26
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import type { Config, Hooks, PluginInput } from "@opencode-ai/plugin"
|
|
2
|
+
|
|
3
|
+
/** OpenCode passes this once `@opencode-ai/plugin` 1.5.0 ships; expressed locally until then. */
|
|
4
|
+
type Msg = { info: { role: string }; parts: Array<{ type: string; text?: string }> }
|
|
5
|
+
export type ForkPluginInput = PluginInput & {
|
|
6
|
+
listSessionMessages: (input: { sessionID: string }) => Promise<Msg[]>
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
const PREFIX = "fork_"
|
|
10
|
+
const SVC = "plugin.forking-agents"
|
|
11
|
+
const PREVIEW = 4000
|
|
12
|
+
const CAP = 400_000
|
|
13
|
+
|
|
14
|
+
function plog(
|
|
15
|
+
client: PluginInput["client"],
|
|
16
|
+
level: "debug" | "info" | "error" | "warn",
|
|
17
|
+
message: string,
|
|
18
|
+
extra?: Record<string, unknown>,
|
|
19
|
+
) {
|
|
20
|
+
void client.app.log({
|
|
21
|
+
body: { service: SVC, level, message, extra },
|
|
22
|
+
})
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function bases(cfg: Config) {
|
|
26
|
+
const out = new Set<string>(["general", "explore"])
|
|
27
|
+
for (const [k, v] of Object.entries(cfg.agent ?? {})) {
|
|
28
|
+
if (v?.disable) continue
|
|
29
|
+
if (k.startsWith(PREFIX)) continue
|
|
30
|
+
if (v?.mode === "subagent") out.add(k)
|
|
31
|
+
}
|
|
32
|
+
if (cfg.agent?.general?.disable) out.delete("general")
|
|
33
|
+
if (cfg.agent?.explore?.disable) out.delete("explore")
|
|
34
|
+
return [...out]
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function clip(txt: string) {
|
|
38
|
+
if (txt.length <= CAP) return txt
|
|
39
|
+
return `${txt.slice(0, CAP)}\n\n[truncated ${txt.length - CAP} chars]`
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function line(msg: Msg) {
|
|
43
|
+
const chunks = msg.parts
|
|
44
|
+
.filter((p) => p.type === "text" && typeof p.text === "string" && p.text.trim())
|
|
45
|
+
.map((p) => p.text!)
|
|
46
|
+
if (chunks.length === 0) return ""
|
|
47
|
+
return `${msg.info.role.toUpperCase()}:\n${chunks.join("\n\n")}\n`
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function transcript(msgs: Msg[]) {
|
|
51
|
+
return clip(msgs.map(line).filter(Boolean).join("\n"))
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Adds `fork_<name>` subagents for each subagent (built-in general/explore plus any `mode: subagent` in config).
|
|
56
|
+
* When the task tool runs with `subagent_type` `fork_*`, rewrites to the base agent and prepends the parent session transcript to the task prompt.
|
|
57
|
+
*
|
|
58
|
+
* Requires OpenCode with plugin input `listSessionMessages`. Set `OPENCODE_DISABLE_FORK_SUBAGENT_PLUGIN=1` to disable.
|
|
59
|
+
*/
|
|
60
|
+
export default async function forkSubagentPlugin(input: ForkPluginInput): Promise<Hooks> {
|
|
61
|
+
if (process.env.OPENCODE_DISABLE_FORK_SUBAGENT_PLUGIN === "1") return {}
|
|
62
|
+
|
|
63
|
+
const { client, directory, listSessionMessages } = input
|
|
64
|
+
|
|
65
|
+
return {
|
|
66
|
+
async config(cfg) {
|
|
67
|
+
cfg.agent ??= {}
|
|
68
|
+
for (const base of bases(cfg)) {
|
|
69
|
+
const fork = `${PREFIX}${base}`
|
|
70
|
+
if (cfg.agent[fork] !== undefined) continue
|
|
71
|
+
cfg.agent[fork] = {
|
|
72
|
+
mode: "subagent",
|
|
73
|
+
hidden: true,
|
|
74
|
+
description: `Fork of @${base}: parent session transcript is prepended before your task prompt.`,
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
},
|
|
78
|
+
"tool.execute.before": async (hook, out) => {
|
|
79
|
+
if (hook.tool !== "task" || !out.args || typeof out.args !== "object") return
|
|
80
|
+
const args = out.args as { subagent_type?: string; prompt?: string }
|
|
81
|
+
const sub = args.subagent_type
|
|
82
|
+
if (typeof sub !== "string" || !sub.startsWith(PREFIX)) return
|
|
83
|
+
const base = sub.slice(PREFIX.length)
|
|
84
|
+
if (!base) return
|
|
85
|
+
plog(client, "info", "fork task rewrite", {
|
|
86
|
+
sessionID: hook.sessionID,
|
|
87
|
+
callID: hook.callID,
|
|
88
|
+
from: sub,
|
|
89
|
+
to: base,
|
|
90
|
+
directory,
|
|
91
|
+
})
|
|
92
|
+
args.subagent_type = base
|
|
93
|
+
const task = typeof args.prompt === "string" ? args.prompt : ""
|
|
94
|
+
let block = ""
|
|
95
|
+
try {
|
|
96
|
+
const list = await listSessionMessages({ sessionID: hook.sessionID })
|
|
97
|
+
plog(client, "info", "fork parent messages loaded", {
|
|
98
|
+
sessionID: hook.sessionID,
|
|
99
|
+
messageCount: list.length,
|
|
100
|
+
})
|
|
101
|
+
plog(client, "info", "fork parent messages", {
|
|
102
|
+
sessionID: hook.sessionID,
|
|
103
|
+
messageCount: list.length,
|
|
104
|
+
transcriptChars: list.length === 0 ? 0 : transcript(list).length,
|
|
105
|
+
})
|
|
106
|
+
const t = list.length === 0 ? "" : transcript(list)
|
|
107
|
+
plog(client, "debug", "fork transcript preview", {
|
|
108
|
+
head: t.slice(0, PREVIEW),
|
|
109
|
+
})
|
|
110
|
+
if (list.length === 0) block = "[fork-subagent: empty parent transcript]\n\n"
|
|
111
|
+
else block = `<parent_session_transcript>\n${t}\n</parent_session_transcript>\n\n`
|
|
112
|
+
} catch (err) {
|
|
113
|
+
plog(client, "error", "fork parent messages failed", {
|
|
114
|
+
error: err instanceof Error ? err.message : JSON.stringify(err),
|
|
115
|
+
sessionID: hook.sessionID,
|
|
116
|
+
directory,
|
|
117
|
+
})
|
|
118
|
+
block = "[fork-subagent: failed to load parent transcript]\n\n"
|
|
119
|
+
}
|
|
120
|
+
args.prompt = `${block}<task>\n${task}\n</task>`
|
|
121
|
+
plog(client, "info", "fork child prompt built", {
|
|
122
|
+
sessionID: hook.sessionID,
|
|
123
|
+
totalChars: args.prompt.length,
|
|
124
|
+
hasParentBlock: args.prompt.includes("<parent_session_transcript>"),
|
|
125
|
+
preview500: args.prompt.slice(0, 500),
|
|
126
|
+
})
|
|
127
|
+
plog(client, "debug", "fork child prompt preview", {
|
|
128
|
+
head: args.prompt.slice(0, PREVIEW),
|
|
129
|
+
})
|
|
130
|
+
},
|
|
131
|
+
}
|
|
132
|
+
}
|
|
@@ -0,0 +1,77 @@
|
|
|
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
ADDED