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.
Files changed (45) hide show
  1. package/README.md +90 -0
  2. package/bin/opencode-drive +2 -0
  3. package/package.json +45 -0
  4. package/src/cli/commands.ts +175 -0
  5. package/src/cli/describe.ts +14 -0
  6. package/src/cli/driver-runner.ts +39 -0
  7. package/src/cli/driver.ts +28 -0
  8. package/src/cli/index.ts +158 -0
  9. package/src/cli/instance.ts +218 -0
  10. package/src/cli/parse.ts +24 -0
  11. package/src/cli/registry.ts +120 -0
  12. package/src/cli/send.ts +48 -0
  13. package/src/cli/start.ts +56 -0
  14. package/src/cli/types.ts +46 -0
  15. package/src/client/backend.ts +184 -0
  16. package/src/client/client.ts +252 -0
  17. package/src/client/index.ts +17 -0
  18. package/src/client/protocol.ts +186 -0
  19. package/src/experimental/campaign-api.ts +34 -0
  20. package/src/experimental/campaign.ts +144 -0
  21. package/src/experimental/cli-campaign.ts +179 -0
  22. package/src/experimental/drive.ts +37 -0
  23. package/src/experimental/driver.ts +41 -0
  24. package/src/experimental/flow-driver.ts +189 -0
  25. package/src/experimental/flows/generate.ts +278 -0
  26. package/src/experimental/flows/index.ts +6 -0
  27. package/src/experimental/flows/properties.ts +51 -0
  28. package/src/experimental/flows/types.ts +47 -0
  29. package/src/experimental/flows/weights.ts +198 -0
  30. package/src/experimental/generators/config.ts +240 -0
  31. package/src/experimental/generators/filesystem.ts +95 -0
  32. package/src/experimental/generators/generate.ts +32 -0
  33. package/src/experimental/generators/index.ts +10 -0
  34. package/src/experimental/generators/initial-state.ts +37 -0
  35. package/src/experimental/generators/random.ts +35 -0
  36. package/src/experimental/hello-driver.ts +42 -0
  37. package/src/experimental/index.ts +3 -0
  38. package/src/experimental/model/derive.ts +119 -0
  39. package/src/experimental/model/index.ts +2 -0
  40. package/src/experimental/model/model.ts +42 -0
  41. package/src/experimental/opencode-simulation.ts +1 -0
  42. package/src/experimental/reproduce-stale-running-visible.ts +60 -0
  43. package/src/experimental/reproduce-stale-running.ts +26 -0
  44. package/src/experimental/stale-running-driver.ts +74 -0
  45. 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"