Files
oh-my-opencode/src/shared/prompt-async-gate-path-compat.test.ts
T
YeonGyu-Kim a5cee76961 fix(prompt-async-gate): retry object-form path on runtime type error (#4417)
Atlas task() with run_in_background=false crashed with 'The "path" property must be of type string, got object' from Node's path.isAbsolute validation upstream of the SDK.

All callers (sync-prompt-sender, boulder-continuation-injector, idle-event, session-route, model-suggestion-retry) already route through dispatchInternalPrompt, so centralizing the compatibility shim in prompt-async-gate.ts covers every Bug 1 site without touching individual hooks.

On TypeError matching the object-path signature, retry once with path collapsed to its id string. Types broaden PromptSessionPath to string | { id }.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
2026-05-25 17:48:14 +09:00

93 lines
2.5 KiB
TypeScript

/// <reference types="bun-types" />
import { afterEach, describe, expect, mock, test } from "bun:test"
import {
dispatchInternalPrompt,
releaseAllPromptAsyncReservationsForTesting,
} from "./prompt-async-gate"
type CompatPromptInput = {
readonly path: { readonly id: string } | string
readonly body: {
readonly parts: readonly []
}
}
function createPathSensitivePrompt() {
const calls: CompatPromptInput[] = []
const prompt = mock(async (input: CompatPromptInput) => {
calls.push(input)
if (typeof input.path !== "string") {
throw new TypeError('The "path" property must be of type string, got object')
}
return { ok: true }
})
return { calls, prompt }
}
describe("dispatchInternalPrompt path compatibility", () => {
afterEach(() => {
releaseAllPromptAsyncReservationsForTesting()
})
test("#given sync prompt rejects object-form session path #when dispatching #then it retries with string-form path", async () => {
// given
const { calls, prompt } = createPathSensitivePrompt()
const client = { session: { prompt } }
// when
const result = await dispatchInternalPrompt<CompatPromptInput>({
mode: "sync",
client,
sessionID: "ses_sync_path_compat",
source: "test:path-compat:sync",
settleMs: 0,
checkStatus: false,
checkToolState: false,
queueBehavior: "defer",
input: {
path: { id: "ses_sync_path_compat" },
body: { parts: [] },
},
})
// then
expect(result.status).toBe("dispatched")
expect(calls.map((call) => call.path)).toEqual([
{ id: "ses_sync_path_compat" },
"ses_sync_path_compat",
])
})
test("#given async prompt rejects object-form session path #when dispatching #then it retries with string-form path", async () => {
// given
const { calls, prompt } = createPathSensitivePrompt()
const client = { session: { promptAsync: prompt } }
// when
const result = await dispatchInternalPrompt<CompatPromptInput>({
mode: "async",
client,
sessionID: "ses_async_path_compat",
source: "test:path-compat:async",
settleMs: 0,
checkStatus: false,
checkToolState: false,
queueBehavior: "defer",
input: {
path: { id: "ses_async_path_compat" },
body: { parts: [] },
},
})
// then
expect(result.status).toBe("dispatched")
expect(calls.map((call) => call.path)).toEqual([
{ id: "ses_async_path_compat" },
"ses_async_path_compat",
])
})
})