opencode-drive 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/README.md +90 -0
- package/bin/opencode-drive +2 -0
- package/package.json +45 -0
- package/src/cli/commands.ts +175 -0
- package/src/cli/describe.ts +14 -0
- package/src/cli/driver-runner.ts +39 -0
- package/src/cli/driver.ts +28 -0
- package/src/cli/index.ts +158 -0
- package/src/cli/instance.ts +218 -0
- package/src/cli/parse.ts +24 -0
- package/src/cli/registry.ts +120 -0
- package/src/cli/send.ts +48 -0
- package/src/cli/start.ts +56 -0
- package/src/cli/types.ts +46 -0
- package/src/client/backend.ts +184 -0
- package/src/client/client.ts +252 -0
- package/src/client/index.ts +17 -0
- package/src/client/protocol.ts +186 -0
- package/src/experimental/campaign-api.ts +34 -0
- package/src/experimental/campaign.ts +144 -0
- package/src/experimental/cli-campaign.ts +179 -0
- package/src/experimental/drive.ts +37 -0
- package/src/experimental/driver.ts +41 -0
- package/src/experimental/flow-driver.ts +189 -0
- package/src/experimental/flows/generate.ts +278 -0
- package/src/experimental/flows/index.ts +6 -0
- package/src/experimental/flows/properties.ts +51 -0
- package/src/experimental/flows/types.ts +47 -0
- package/src/experimental/flows/weights.ts +198 -0
- package/src/experimental/generators/config.ts +240 -0
- package/src/experimental/generators/filesystem.ts +95 -0
- package/src/experimental/generators/generate.ts +32 -0
- package/src/experimental/generators/index.ts +10 -0
- package/src/experimental/generators/initial-state.ts +37 -0
- package/src/experimental/generators/random.ts +35 -0
- package/src/experimental/hello-driver.ts +42 -0
- package/src/experimental/index.ts +3 -0
- package/src/experimental/model/derive.ts +119 -0
- package/src/experimental/model/index.ts +2 -0
- package/src/experimental/model/model.ts +42 -0
- package/src/experimental/opencode-simulation.ts +1 -0
- package/src/experimental/reproduce-stale-running-visible.ts +60 -0
- package/src/experimental/reproduce-stale-running.ts +26 -0
- package/src/experimental/stale-running-driver.ts +74 -0
- package/src/index.ts +1 -0
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import path from "node:path"
|
|
2
|
+
|
|
3
|
+
const root = "/tmp/opencode-probe-stale-running-visible"
|
|
4
|
+
const state = path.join(root, "state", "files")
|
|
5
|
+
await Bun.$`rm -rf ${root}`.quiet()
|
|
6
|
+
await Bun.$`mkdir -p ${path.join(state, ".config/opencode")} ${path.join(state, "src")}`.quiet()
|
|
7
|
+
|
|
8
|
+
const config = {
|
|
9
|
+
model: "simulation/sim-model",
|
|
10
|
+
permissions: [{ action: "*", resource: "*", effect: "allow" }],
|
|
11
|
+
providers: {
|
|
12
|
+
simulation: {
|
|
13
|
+
name: "Simulation",
|
|
14
|
+
request: { body: { apiKey: "sim-key" } },
|
|
15
|
+
models: {
|
|
16
|
+
"sim-model": {
|
|
17
|
+
name: "Simulated Model",
|
|
18
|
+
api: { type: "aisdk", package: "@ai-sdk/openai-compatible", url: "https://api.openai.com/v1" },
|
|
19
|
+
capabilities: { tools: true, input: ["text"], output: ["text"] },
|
|
20
|
+
limit: { context: 128000, output: 16000 },
|
|
21
|
+
},
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
await Promise.all([
|
|
28
|
+
Bun.write(path.join(state, "opencode.json"), `${JSON.stringify(config, undefined, 2)}\n`),
|
|
29
|
+
Bun.write(path.join(state, ".config/opencode/opencode.json"), `${JSON.stringify(config, undefined, 2)}\n`),
|
|
30
|
+
Bun.write(path.join(state, "src/example.ts"), "export const example = true\n"),
|
|
31
|
+
])
|
|
32
|
+
|
|
33
|
+
const child = Bun.spawn([
|
|
34
|
+
path.resolve("bin/opencode-sim"),
|
|
35
|
+
"--state", path.join(root, "state"),
|
|
36
|
+
"--anchor", path.join(root, "anchor"),
|
|
37
|
+
"--renderer", "visible",
|
|
38
|
+
"--driver", `bun ${path.resolve("src/experimental/stale-running-driver.ts")}`,
|
|
39
|
+
"--",
|
|
40
|
+
"bun", "run", "--conditions=browser", "--preload=@opentui/solid/preload",
|
|
41
|
+
"/root/projects/opencode-latest/packages/cli/src/index.ts", "--standalone",
|
|
42
|
+
], {
|
|
43
|
+
cwd: path.resolve("."),
|
|
44
|
+
env: {
|
|
45
|
+
...processEnv(),
|
|
46
|
+
OPENCODE_SIMULATION_DRIVER_LOG: path.join(root, "driver.log"),
|
|
47
|
+
OPENCODE_SIMULATION_LOG: path.join(root, "simulation.log"),
|
|
48
|
+
},
|
|
49
|
+
stdin: "inherit",
|
|
50
|
+
stdout: "inherit",
|
|
51
|
+
stderr: "inherit",
|
|
52
|
+
})
|
|
53
|
+
|
|
54
|
+
const status = await child.exited
|
|
55
|
+
if (status !== 0) throw new Error(`visible reproduction failed; see ${path.join(root, "driver.log")}`)
|
|
56
|
+
console.log(`Reproduction artifacts: ${root}`)
|
|
57
|
+
|
|
58
|
+
function processEnv(): Record<string, string> {
|
|
59
|
+
return Object.fromEntries(Object.entries(process.env).filter((entry): entry is [string, string] => entry[1] !== undefined))
|
|
60
|
+
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
const child = Bun.spawn([
|
|
2
|
+
"bun",
|
|
3
|
+
"test",
|
|
4
|
+
"test/cli/tui/data.test.tsx",
|
|
5
|
+
"--test-name-pattern",
|
|
6
|
+
"clears stale running status after reconnect",
|
|
7
|
+
], {
|
|
8
|
+
cwd: "/root/projects/opencode-latest/packages/tui",
|
|
9
|
+
stdout: "pipe",
|
|
10
|
+
stderr: "pipe",
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
const [status, stdout, stderr] = await Promise.all([
|
|
14
|
+
child.exited,
|
|
15
|
+
new Response(child.stdout).text(),
|
|
16
|
+
new Response(child.stderr).text(),
|
|
17
|
+
])
|
|
18
|
+
const output = `${stdout}${stderr}`
|
|
19
|
+
process.stdout.write(output)
|
|
20
|
+
|
|
21
|
+
if (status !== 0 && output.includes('Expected: "idle"') && output.includes('Received: "running"')) {
|
|
22
|
+
console.log("\nREPRODUCED: reconnect left an inactive session stuck in the running UI state.")
|
|
23
|
+
process.exit(0)
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
throw new Error(`reproduction did not produce the expected stale-running failure (test exit ${status})`)
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import { connectBackendSimulation, connectSimulation, type OpenedExchange } from "../client/index.js"
|
|
2
|
+
import { isRunning } from "./flows/index.js"
|
|
3
|
+
|
|
4
|
+
const ui = await connectSimulation({ url: requiredEnv("OPENCODE_SIMULATION_UI_WS") })
|
|
5
|
+
const backend = await connectBackendSimulation({ url: requiredEnv("OPENCODE_SIMULATION_BACKEND_WS") })
|
|
6
|
+
const requestOpened = deferred()
|
|
7
|
+
const releaseResponse = deferred()
|
|
8
|
+
const responseFinished = deferred()
|
|
9
|
+
|
|
10
|
+
await backend.attach(async (request: OpenedExchange) => {
|
|
11
|
+
if (isTitleRequest(request)) {
|
|
12
|
+
await backend.chunk(request.id, [{ type: "textDelta", text: "Stale running reproduction" }])
|
|
13
|
+
await backend.finish(request.id, "stop")
|
|
14
|
+
return
|
|
15
|
+
}
|
|
16
|
+
requestOpened.resolve()
|
|
17
|
+
await backend.chunk(request.id, [{ type: "textDelta", text: "The provider turn is finishing while events are disconnected." }])
|
|
18
|
+
await releaseResponse.promise
|
|
19
|
+
await backend.finish(request.id, "stop")
|
|
20
|
+
responseFinished.resolve()
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
await waitFor("prompt editor", async () => (await ui.state()).focused.editor)
|
|
25
|
+
await ui.typeText("Reproduce stale running status across an event-stream reconnect")
|
|
26
|
+
await ui.pressEnter()
|
|
27
|
+
await requestOpened.promise
|
|
28
|
+
await waitFor("running TUI", async () => isRunning(await ui.state()))
|
|
29
|
+
|
|
30
|
+
releaseResponse.resolve()
|
|
31
|
+
await responseFinished.promise
|
|
32
|
+
await waitFor("provider drain", async () => (await backend.pendingExchanges()).exchanges.length === 0)
|
|
33
|
+
await Bun.sleep(1_000)
|
|
34
|
+
|
|
35
|
+
const state = await ui.state()
|
|
36
|
+
if (!isRunning(state)) throw new Error("stale running status was not reproduced")
|
|
37
|
+
console.log("REPRODUCED: backend provider work is idle while the TUI still displays running.")
|
|
38
|
+
await Bun.sleep(Number(process.env.OPENCODE_PROBE_HOLD_MS ?? "10000"))
|
|
39
|
+
} finally {
|
|
40
|
+
ui.close()
|
|
41
|
+
backend.close()
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function deferred() {
|
|
45
|
+
let resolve!: () => void
|
|
46
|
+
const promise = new Promise<void>((done) => {
|
|
47
|
+
resolve = done
|
|
48
|
+
})
|
|
49
|
+
return { promise, resolve }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function requiredEnv(name: string) {
|
|
53
|
+
const value = process.env[name]
|
|
54
|
+
if (value === undefined) throw new Error(`${name} is required`)
|
|
55
|
+
return value
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function isTitleRequest(request: OpenedExchange) {
|
|
59
|
+
if (typeof request.body !== "object" || request.body === null || !("messages" in request.body)) return false
|
|
60
|
+
const messages = request.body.messages
|
|
61
|
+
if (!Array.isArray(messages)) return false
|
|
62
|
+
const first = messages[0]
|
|
63
|
+
if (typeof first !== "object" || first === null || !("content" in first)) return false
|
|
64
|
+
return typeof first.content === "string" && first.content.includes("You are a title generator")
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
async function waitFor(label: string, check: () => Promise<boolean>, timeout = 30_000) {
|
|
68
|
+
const deadline = Date.now() + timeout
|
|
69
|
+
while (Date.now() < deadline) {
|
|
70
|
+
if (await check()) return
|
|
71
|
+
await Bun.sleep(50)
|
|
72
|
+
}
|
|
73
|
+
throw new Error(`timed out waiting for ${label}`)
|
|
74
|
+
}
|
package/src/index.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from "./client/index.js"
|