merge(dev): resolve latest sync-task conflict for delegated fallback PR
Sync the PR branch with the latest dev branch and resolve the remaining conflict in sync-task.test.ts while preserving both the new upstream poll-recovery coverage and this branch's delegated bootstrap cleanup and isolation coverage. Re-verified the affected delegated fallback suites and typecheck after the merge resolution. Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent) Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
@@ -54,7 +54,7 @@ export function migrateLegacyPluginEntry(configPath: string): boolean {
|
||||
|
||||
const tempPath = `${configPath}.tmp`
|
||||
writeFileSync(tempPath, updated, "utf-8")
|
||||
const tempFileDescriptor = openSync(tempPath, "r")
|
||||
const tempFileDescriptor = openSync(tempPath, "r+")
|
||||
try {
|
||||
fsyncSync(tempFileDescriptor)
|
||||
} finally {
|
||||
|
||||
@@ -615,6 +615,18 @@ describe("resolveCompatibleModelSettings", () => {
|
||||
expect(result.changes).toEqual([])
|
||||
})
|
||||
|
||||
test("#given desired.maxTokens is 0 #then maxTokens is dropped", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
providerID: "openai",
|
||||
modelID: "gpt-5.4",
|
||||
desired: { maxTokens: 0 },
|
||||
capabilities: { maxOutputTokens: 128_000 },
|
||||
})
|
||||
|
||||
expect(result.maxTokens).toBeUndefined()
|
||||
expect(result.changes).toEqual([])
|
||||
})
|
||||
|
||||
// Passthrough: undefined desired values produce no changes
|
||||
test("no-op when desired settings are empty", () => {
|
||||
const result = resolveCompatibleModelSettings({
|
||||
|
||||
@@ -175,6 +175,10 @@ export function resolveCompatibleModelSettings(
|
||||
}
|
||||
|
||||
let maxTokens = input.desired.maxTokens
|
||||
if (maxTokens !== undefined && maxTokens <= 0) {
|
||||
maxTokens = undefined
|
||||
}
|
||||
|
||||
if (
|
||||
maxTokens !== undefined &&
|
||||
input.capabilities?.maxOutputTokens !== undefined &&
|
||||
|
||||
@@ -0,0 +1,11 @@
|
||||
/**
|
||||
* Detect whether we are running inside cmux (cmux omo).
|
||||
* When cmux-omo sets up the environment it injects a tmux shim and sets
|
||||
* CMUX_SOCKET_PATH / TMUX. If detected, redirect tmux commands to
|
||||
* `cmux __tmux-compat` so they become native cmux splits instead of
|
||||
* failing because there is no real tmux server running.
|
||||
*/
|
||||
export function isCmuxCompatEnvironment(): boolean {
|
||||
return Boolean(process.env.CMUX_SOCKET_PATH) ||
|
||||
process.env.TMUX?.includes("cmuxterm") === true
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
export * from "./types"
|
||||
export * from "./constants"
|
||||
export * from "./cmux-detect"
|
||||
export * from "./runner"
|
||||
export * from "./tmux-utils"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { afterAll, describe, expect, test } from "bun:test"
|
||||
import { afterAll, beforeEach, describe, expect, test } from "bun:test"
|
||||
import { randomUUID } from "node:crypto"
|
||||
import fs from "node:fs/promises"
|
||||
import os from "node:os"
|
||||
@@ -9,6 +9,9 @@ import path from "node:path"
|
||||
import { runTmuxCommand } from "./runner"
|
||||
|
||||
const temporaryDirectories: string[] = []
|
||||
const originalCmuxSocketPath = process.env.CMUX_SOCKET_PATH
|
||||
const originalTmux = process.env.TMUX
|
||||
const originalPath = process.env.PATH
|
||||
|
||||
async function createTemporaryDirectory(): Promise<string> {
|
||||
const directoryPath = await fs.mkdtemp(path.join(os.tmpdir(), "tmux-runner-"))
|
||||
@@ -21,7 +24,39 @@ async function readInvocationCount(counterFilePath: string): Promise<number> {
|
||||
return Number.parseInt(count, 10)
|
||||
}
|
||||
|
||||
async function createFakeCmux(directoryPath: string, argsFilePath: string): Promise<string> {
|
||||
const cmuxPath = path.join(directoryPath, "cmux")
|
||||
const script = [
|
||||
"#!/bin/sh",
|
||||
"printf '%s\\n' \"$@\" > \"$1.args\"",
|
||||
"printf '%s\\n' '%42'",
|
||||
].join("\n")
|
||||
await fs.writeFile(cmuxPath, script.replace("$1.args", argsFilePath), "utf8")
|
||||
await fs.chmod(cmuxPath, 0o755)
|
||||
return cmuxPath
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
delete process.env.CMUX_SOCKET_PATH
|
||||
delete process.env.TMUX
|
||||
process.env.PATH = originalPath
|
||||
})
|
||||
|
||||
afterAll(async () => {
|
||||
if (originalCmuxSocketPath === undefined) {
|
||||
delete process.env.CMUX_SOCKET_PATH
|
||||
} else {
|
||||
process.env.CMUX_SOCKET_PATH = originalCmuxSocketPath
|
||||
}
|
||||
|
||||
if (originalTmux === undefined) {
|
||||
delete process.env.TMUX
|
||||
} else {
|
||||
process.env.TMUX = originalTmux
|
||||
}
|
||||
|
||||
process.env.PATH = originalPath
|
||||
|
||||
for (const directoryPath of temporaryDirectories) {
|
||||
await fs.rm(directoryPath, { recursive: true, force: true })
|
||||
}
|
||||
@@ -124,4 +159,26 @@ describe("runTmuxCommand", () => {
|
||||
expect(success).toBe(true)
|
||||
expect(output).toBe("%9")
|
||||
})
|
||||
|
||||
test("#given cmux environment #when run #then delegates through cmux tmux compatibility command", async () => {
|
||||
// given
|
||||
const temporaryDirectory = await createTemporaryDirectory()
|
||||
const argsFilePath = path.join(temporaryDirectory, "cmux.args")
|
||||
const cmuxPath = await createFakeCmux(temporaryDirectory, argsFilePath)
|
||||
process.env.CMUX_SOCKET_PATH = path.join(temporaryDirectory, "cmux.sock")
|
||||
process.env.PATH = `${temporaryDirectory}${path.delimiter}${originalPath ?? ""}`
|
||||
|
||||
// when
|
||||
const result = await runTmuxCommand(cmuxPath, ["display-message", "-p", "#{pane_id}"])
|
||||
|
||||
// then
|
||||
expect(result).toEqual({
|
||||
success: true,
|
||||
output: "%42",
|
||||
stdout: "%42",
|
||||
stderr: "",
|
||||
exitCode: 0,
|
||||
})
|
||||
await expect(fs.readFile(argsFilePath, "utf8")).resolves.toBe("__tmux-compat\ndisplay-message\n-p\n#{pane_id}\n")
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { spawn } from "../bun-spawn-shim"
|
||||
import { isCmuxCompatEnvironment } from "./cmux-detect"
|
||||
|
||||
type RunTmuxOptions = {
|
||||
retry?: number
|
||||
@@ -29,20 +30,14 @@ function isTerminalTmuxError(stderr: string): boolean {
|
||||
return TERMINAL_TMUX_ERROR_PATTERN.test(stderr)
|
||||
}
|
||||
|
||||
/**
|
||||
* Detect whether we are running inside cmux (cmux omo).
|
||||
* When cmux-omo sets up the environment it injects a tmux shim and sets
|
||||
* CMUX_SOCKET_PATH / TMUX. If detected, redirect tmux commands to
|
||||
* `cmux __tmux-compat` so they become native cmux splits instead of
|
||||
* failing because there is no real tmux server running.
|
||||
*/
|
||||
function resolveTmuxExecutable(tmuxPath: string): string[] {
|
||||
const inCmux = Boolean(process.env.CMUX_SOCKET_PATH) ||
|
||||
process.env.TMUX?.includes("cmuxterm") === true
|
||||
if (inCmux) {
|
||||
return ["cmux", "__tmux-compat"]
|
||||
if (!isCmuxCompatEnvironment()) {
|
||||
return [tmuxPath]
|
||||
}
|
||||
return [tmuxPath]
|
||||
|
||||
const executableName = tmuxPath.split(/[\\/]/).pop()
|
||||
const cmuxExecutable = executableName === "cmux" ? tmuxPath : "cmux"
|
||||
return [cmuxExecutable, "__tmux-compat"]
|
||||
}
|
||||
|
||||
async function runTmuxCommandOnce(tmuxPath: string, args: Array<string>, timeoutMs?: number): Promise<TmuxCommandResult> {
|
||||
|
||||
@@ -16,7 +16,7 @@ export function writeFileAtomically(
|
||||
): void {
|
||||
const tempPath = `${filePath}.tmp`
|
||||
writeFileSync(tempPath, content, "utf-8")
|
||||
const tempFileDescriptor = openSync(tempPath, "r")
|
||||
const tempFileDescriptor = openSync(tempPath, "r+")
|
||||
try {
|
||||
tolerantFsyncSync(tempFileDescriptor, `writeFileAtomically:${filePath}`, deps.fsyncSync)
|
||||
} finally {
|
||||
|
||||
@@ -96,6 +96,41 @@ describe("migrateLegacyPluginEntry", () => {
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given migration writes a temp file for fsync", () => {
|
||||
describe("#when opening the temp file descriptor", () => {
|
||||
it("#then uses r+ mode to satisfy FlushFileBuffers requirements on Windows", async () => {
|
||||
const configPath = join(testDir, "opencode.json")
|
||||
writeFileSync(configPath, JSON.stringify({ plugin: ["oh-my-opencode@latest"] }, null, 2))
|
||||
|
||||
const fs = await import("node:fs")
|
||||
const originalOpenSync = fs.openSync
|
||||
const openSyncCalls: string[] = []
|
||||
|
||||
mock.module("node:fs", () => ({
|
||||
...fs,
|
||||
openSync: (path: Parameters<typeof fs.openSync>[0], flags: Parameters<typeof fs.openSync>[1]) => {
|
||||
openSyncCalls.push(String(flags))
|
||||
return originalOpenSync(path, flags)
|
||||
},
|
||||
}))
|
||||
|
||||
try {
|
||||
const { migrateLegacyPluginEntry } = await importFreshMigrationModule()
|
||||
|
||||
const result = migrateLegacyPluginEntry(configPath)
|
||||
|
||||
expect(result).toBe(true)
|
||||
expect(openSyncCalls).toContain("r+")
|
||||
} finally {
|
||||
mock.module("node:fs", () => ({
|
||||
...fs,
|
||||
openSync: originalOpenSync,
|
||||
}))
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("#given opencode.json contains pinned oh-my-opencode version", () => {
|
||||
describe("#when migrating the config", () => {
|
||||
it("#then preserves the version pin", async () => {
|
||||
@@ -217,4 +252,4 @@ describe("migrateLegacyPluginEntry", () => {
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user