oh-my-opencode 4.9.0 → 4.9.2

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 (37) hide show
  1. package/.agents/skills/opencode-qa/SKILL.md +1 -0
  2. package/.agents/skills/opencode-qa/scripts/lib/fake-openai-branches.mjs +39 -0
  3. package/.agents/skills/opencode-qa/scripts/lib/fake-openai-events.mjs +106 -0
  4. package/.agents/skills/opencode-qa/scripts/lib/fake-openai-server.mjs +117 -0
  5. package/.agents/skills/opencode-qa/scripts/serve-wake-split-probe.sh +716 -0
  6. package/dist/cli/doctor/checks/dependencies.d.ts +2 -2
  7. package/dist/cli/index.js +31742 -31476
  8. package/dist/cli-node/index.js +31742 -31476
  9. package/dist/config/schema/experimental.d.ts +1 -0
  10. package/dist/config/schema/oh-my-opencode-config.d.ts +1 -0
  11. package/dist/index.js +3067 -1344
  12. package/dist/oh-my-opencode.schema.json +3 -0
  13. package/dist/shared/internal-initiator-marker.d.ts +7 -0
  14. package/dist/shared/live-server-route.d.ts +24 -0
  15. package/dist/shared/module-resolution-failure.d.ts +7 -0
  16. package/dist/shared/prompt-async-gate/prompt-message-state.d.ts +1 -0
  17. package/dist/testing/create-plugin-module.d.ts +4 -0
  18. package/package.json +12 -12
  19. package/packages/omo-codex/plugin/.codex-plugin/plugin.json +1 -1
  20. package/packages/omo-codex/plugin/components/comment-checker/hooks/hooks.json +1 -1
  21. package/packages/omo-codex/plugin/components/comment-checker/package.json +1 -1
  22. package/packages/omo-codex/plugin/components/git-bash/hooks/hooks.json +2 -2
  23. package/packages/omo-codex/plugin/components/git-bash/package.json +1 -1
  24. package/packages/omo-codex/plugin/components/lsp/hooks/hooks.json +2 -2
  25. package/packages/omo-codex/plugin/components/lsp/package.json +1 -1
  26. package/packages/omo-codex/plugin/components/rules/hooks/hooks.json +4 -4
  27. package/packages/omo-codex/plugin/components/rules/package.json +1 -1
  28. package/packages/omo-codex/plugin/components/start-work-continuation/hooks/hooks.json +2 -2
  29. package/packages/omo-codex/plugin/components/start-work-continuation/package.json +1 -1
  30. package/packages/omo-codex/plugin/components/telemetry/hooks/hooks.json +1 -1
  31. package/packages/omo-codex/plugin/components/telemetry/package.json +1 -1
  32. package/packages/omo-codex/plugin/components/ultrawork/hooks/hooks.json +1 -1
  33. package/packages/omo-codex/plugin/components/ultrawork/package.json +1 -1
  34. package/packages/omo-codex/plugin/components/ulw-loop/hooks/hooks.json +2 -2
  35. package/packages/omo-codex/plugin/components/ulw-loop/package.json +1 -1
  36. package/packages/omo-codex/plugin/hooks/hooks.json +16 -16
  37. package/packages/omo-codex/plugin/package.json +1 -1
@@ -54,6 +54,7 @@ sandbox, free port, server start/stop, and an EXIT-trap cleanup). It requires
54
54
  | Export a whole session as JSON | D | `scripts/export-roundtrip.sh <ses_id>` | `references/db-investigation.md` |
55
55
  | Check the HTTP server / an endpoint | B | `scripts/server-smoke.sh` | `references/server-api.md` |
56
56
  | Prove a hook / action / event fired | B | `scripts/sse-hook-probe.sh` | `references/events-hooks.md` |
57
+ | Prove serve-topology wake runner-split (reproduced/fixed) | B | `scripts/serve-wake-split-probe.sh --expect reproduced\|fixed --evidence-dir DIR` (self-test: `--self-test`; fake LLM: `scripts/lib/fake-openai-server.mjs`) | `references/events-hooks.md` |
57
58
  | Smoke-test the TUI | C | `scripts/tui-smoke.sh` | `references/tui-tmux.md` |
58
59
  | Write/run a test in the opencode source | - | (bun test) | `references/testing-harness.md` |
59
60
  | Drive opencode from a Bun/TS script | - | (SDK) | `references/sdk.md` |
@@ -0,0 +1,39 @@
1
+ export const branchCounts = {
2
+ title: 0,
3
+ "parent-tool-call": 0,
4
+ "parent-hold": 0,
5
+ child: 0,
6
+ wake: 0,
7
+ default: 0,
8
+ }
9
+
10
+ export const latches = {
11
+ parentToolCallIssued: false,
12
+ parentHoldIssued: false,
13
+ }
14
+
15
+ export function hasToolResult(inputStr) {
16
+ return (
17
+ inputStr.includes('"type":"function_call_output"') ||
18
+ inputStr.includes('"type": "function_call_output"') ||
19
+ inputStr.includes('"type":"tool_result"') ||
20
+ inputStr.includes('"type": "tool_result"') ||
21
+ inputStr.includes('"role":"tool"') ||
22
+ inputStr.includes('"role": "tool"')
23
+ )
24
+ }
25
+
26
+ export function selectBranch(inputStr) {
27
+ const isTitle = inputStr.includes("Generate a title")
28
+ const isSplitProbe = inputStr.includes("Run the split probe")
29
+ const isChild = inputStr.includes("SPLIT_CHILD_TASK")
30
+ const isWake = inputStr.includes("[BACKGROUND TASK")
31
+ const hasResult = hasToolResult(inputStr)
32
+
33
+ if (isTitle) return "title"
34
+ if (isChild && !isSplitProbe) return "child"
35
+ if (isWake) return "wake"
36
+ if (isSplitProbe && !hasResult && !latches.parentToolCallIssued) return "parent-tool-call"
37
+ if (isSplitProbe && (hasResult || latches.parentToolCallIssued) && !latches.parentHoldIssued) return "parent-hold"
38
+ return "default"
39
+ }
@@ -0,0 +1,106 @@
1
+ import fs from "node:fs"
2
+
3
+ export function completedUsage() {
4
+ return {
5
+ input_tokens: 10,
6
+ output_tokens: 5,
7
+ input_tokens_details: { cached_tokens: 0 },
8
+ output_tokens_details: { reasoning_tokens: 0 },
9
+ }
10
+ }
11
+
12
+ export function sendSse(res, events) {
13
+ res.writeHead(200, {
14
+ "content-type": "text/event-stream; charset=utf-8",
15
+ "cache-control": "no-cache",
16
+ connection: "keep-alive",
17
+ })
18
+ for (const event of events) {
19
+ res.write(`data: ${JSON.stringify(event)}\n\n`)
20
+ }
21
+ res.write("data: [DONE]\n\n")
22
+ res.end()
23
+ }
24
+
25
+ export function textEvents(callCount, text) {
26
+ const id = `resp_${callCount}`
27
+ const item = `msg_${callCount}`
28
+ return [
29
+ {
30
+ type: "response.created",
31
+ response: { id, created_at: Math.floor(Date.now() / 1000), model: "gpt-fake" },
32
+ },
33
+ {
34
+ type: "response.output_item.added",
35
+ output_index: 0,
36
+ item: { type: "message", id: item },
37
+ },
38
+ {
39
+ type: "response.output_text.delta",
40
+ item_id: item,
41
+ output_index: 0,
42
+ delta: text,
43
+ },
44
+ {
45
+ type: "response.output_item.done",
46
+ output_index: 0,
47
+ item: { type: "message", id: item },
48
+ },
49
+ {
50
+ type: "response.completed",
51
+ response: { usage: completedUsage() },
52
+ },
53
+ ]
54
+ }
55
+
56
+ export function toolCallEvents(callCount, name, callId, argsObj) {
57
+ const id = `resp_${callCount}`
58
+ const fcId = `fc_${callCount}`
59
+ const argsStr = JSON.stringify(argsObj)
60
+ return [
61
+ {
62
+ type: "response.created",
63
+ response: { id, created_at: Math.floor(Date.now() / 1000), model: "gpt-fake" },
64
+ },
65
+ {
66
+ type: "response.output_item.added",
67
+ output_index: 0,
68
+ item: {
69
+ type: "function_call",
70
+ id: fcId,
71
+ call_id: callId,
72
+ name,
73
+ arguments: "",
74
+ },
75
+ },
76
+ {
77
+ type: "response.function_call_arguments.delta",
78
+ item_id: fcId,
79
+ output_index: 0,
80
+ delta: argsStr,
81
+ },
82
+ {
83
+ type: "response.output_item.done",
84
+ output_index: 0,
85
+ item: {
86
+ type: "function_call",
87
+ id: fcId,
88
+ call_id: callId,
89
+ name,
90
+ arguments: argsStr,
91
+ status: "completed",
92
+ },
93
+ },
94
+ {
95
+ type: "response.completed",
96
+ response: { usage: completedUsage() },
97
+ },
98
+ ]
99
+ }
100
+
101
+ export function appendLog(logFile, line) {
102
+ try {
103
+ fs.appendFileSync(logFile, line)
104
+ } catch {
105
+ }
106
+ }
@@ -0,0 +1,117 @@
1
+ #!/usr/bin/env node
2
+ import http from "node:http"
3
+ import fs from "node:fs"
4
+ import path from "node:path"
5
+ import os from "node:os"
6
+ import { sendSse, textEvents, toolCallEvents, appendLog } from "./fake-openai-events.mjs"
7
+ import { branchCounts, latches, selectBranch } from "./fake-openai-branches.mjs"
8
+
9
+ const requestedPort = Number(process.env.FAKE_OPENAI_PORT ?? 0)
10
+ const logFile = process.env.FAKE_LLM_LOG ?? path.join(os.tmpdir(), "fake-llm.log")
11
+
12
+ let callCount = 0
13
+
14
+ function logBranch(branch, extra = {}) {
15
+ const now = new Date().toISOString()
16
+ const line = `[${now}] branch=${branch} call=${callCount}${Object.keys(extra).length ? " " + JSON.stringify(extra) : ""}\n`
17
+ appendLog(logFile, line)
18
+ process.stdout.write(line)
19
+ }
20
+
21
+ function readBody(req) {
22
+ return new Promise((resolve, reject) => {
23
+ const chunks = []
24
+ req.on("data", (chunk) => chunks.push(chunk))
25
+ req.on("end", () => resolve(Buffer.concat(chunks).toString("utf8")))
26
+ req.on("error", reject)
27
+ })
28
+ }
29
+
30
+ function sleep(ms) {
31
+ return new Promise((resolve) => setTimeout(resolve, ms))
32
+ }
33
+
34
+ const server = http.createServer(async (req, res) => {
35
+ if (req.method === "GET" && req.url === "/health") {
36
+ res.writeHead(200, { "content-type": "text/plain" }).end("ok")
37
+ return
38
+ }
39
+
40
+ if (req.method !== "POST" || !req.url?.includes("/responses")) {
41
+ res.writeHead(404, { "content-type": "application/json" }).end(JSON.stringify({ error: "not found" }))
42
+ return
43
+ }
44
+
45
+ callCount++
46
+ const raw = await readBody(req)
47
+ let body
48
+ try { body = JSON.parse(raw) } catch { body = {} }
49
+
50
+ const inputStr = JSON.stringify(body.input ?? body.messages ?? body)
51
+ const branch = selectBranch(inputStr)
52
+ branchCounts[branch] = (branchCounts[branch] ?? 0) + 1
53
+ logBranch(branch)
54
+
55
+ if (branch === "title") {
56
+ sendSse(res, textEvents(callCount, "wake split probe session"))
57
+ return
58
+ }
59
+
60
+ if (branch === "child") {
61
+ sendSse(res, textEvents(callCount, "DONE"))
62
+ return
63
+ }
64
+
65
+ if (branch === "wake") {
66
+ await sleep(3000)
67
+ sendSse(res, textEvents(callCount, `WAKE_ACK ${callCount}`))
68
+ return
69
+ }
70
+
71
+ if (branch === "parent-tool-call") {
72
+ latches.parentToolCallIssued = true
73
+ sendSse(res, toolCallEvents(callCount, "task", `call_agent_${callCount}`, {
74
+ description: "split probe child",
75
+ prompt: "SPLIT_CHILD_TASK: reply exactly DONE",
76
+ subagent_type: "explore",
77
+ run_in_background: true,
78
+ load_skills: [],
79
+ }))
80
+ return
81
+ }
82
+
83
+ if (branch === "parent-hold") {
84
+ latches.parentHoldIssued = true
85
+ sendSse(res, toolCallEvents(callCount, "bash", `call_bash_${callCount}`, {
86
+ command: "i=0; while [ $i -lt 8 ]; do i=$((i+1)); sleep 1; done",
87
+ description: "hold turn",
88
+ }))
89
+ return
90
+ }
91
+
92
+ if (inputStr.includes("say exactly: TUI_NOREG_OK")) {
93
+ sendSse(res, textEvents(callCount, "TUI_NOREG_OK"))
94
+ return
95
+ }
96
+ sendSse(res, textEvents(callCount, `fake response ${callCount}`))
97
+ })
98
+
99
+ function logFinalCounts() {
100
+ const summary = Object.entries(branchCounts).map(([k, v]) => `${k}=${v}`).join(" ")
101
+ const line = `[${new Date().toISOString()}] FINAL_COUNTS ${summary}\n`
102
+ appendLog(logFile, line)
103
+ process.stdout.write(line)
104
+ }
105
+
106
+ server.listen(requestedPort, "127.0.0.1", () => {
107
+ const addr = server.address()
108
+ const port = typeof addr === "object" && addr !== null ? addr.port : requestedPort
109
+ try {
110
+ fs.mkdirSync(path.dirname(logFile), { recursive: true })
111
+ appendLog(logFile, `[${new Date().toISOString()}] START port=${port}\n`)
112
+ } catch {}
113
+ process.stdout.write(`fake-openai listening on ${port}\n`)
114
+ })
115
+
116
+ process.on("SIGTERM", () => { logFinalCounts(); server.close(() => process.exit(0)) })
117
+ process.on("SIGINT", () => { logFinalCounts(); server.close(() => process.exit(0)) })