fix full-suite isolation regressions
This commit is contained in:
+25
-26
@@ -65,10 +65,10 @@ describe("posthog client creation", () => {
|
||||
|
||||
// then
|
||||
expect(() => cliPostHog.trackActive("cli", "run_started")).not.toThrow()
|
||||
await expect(cliPostHog.shutdown()).resolves.toBeUndefined()
|
||||
expect(await cliPostHog.shutdown()).toBeUndefined()
|
||||
|
||||
expect(() => pluginPostHog.trackActive("plugin", "run_started")).not.toThrow()
|
||||
await expect(pluginPostHog.shutdown()).resolves.toBeUndefined()
|
||||
expect(await pluginPostHog.shutdown()).toBeUndefined()
|
||||
})
|
||||
|
||||
it("creates a plugin client when os.cpus throws", async () => {
|
||||
@@ -77,20 +77,6 @@ describe("posthog client creation", () => {
|
||||
process.env.OMO_SEND_ANONYMOUS_TELEMETRY = "1"
|
||||
process.env.POSTHOG_API_KEY = "test-api-key"
|
||||
|
||||
mock.module("os", () => ({
|
||||
default: {
|
||||
arch: () => "x64",
|
||||
cpus: () => {
|
||||
throw new Error("Failed to get CPU information")
|
||||
},
|
||||
hostname: () => "test-host",
|
||||
platform: () => "linux",
|
||||
release: () => "6.8.0-arch1-1",
|
||||
totalmem: () => 8 * 1024 * 1024 * 1024,
|
||||
type: () => "Linux",
|
||||
},
|
||||
}))
|
||||
|
||||
mock.module("posthog-node", () => ({
|
||||
PostHog: class {
|
||||
capture() {}
|
||||
@@ -98,14 +84,26 @@ describe("posthog client creation", () => {
|
||||
},
|
||||
}))
|
||||
|
||||
const { createPluginPostHog } = await importPostHogModule()
|
||||
const posthogModule = await importPostHogModule()
|
||||
posthogModule.__setOsProviderForTesting({
|
||||
arch: () => "x64",
|
||||
cpus: () => {
|
||||
throw new Error("Failed to get CPU information")
|
||||
},
|
||||
hostname: () => "test-host",
|
||||
platform: () => "linux",
|
||||
release: () => "6.8.0-arch1-1",
|
||||
totalmem: () => 8 * 1024 * 1024 * 1024,
|
||||
type: () => "Linux",
|
||||
})
|
||||
|
||||
// when
|
||||
const pluginPostHog = createPluginPostHog()
|
||||
const pluginPostHog = posthogModule.createPluginPostHog()
|
||||
|
||||
// then
|
||||
expect(() => pluginPostHog.trackActive("plugin", "run_started")).not.toThrow()
|
||||
await expect(pluginPostHog.shutdown()).resolves.toBeUndefined()
|
||||
expect(await pluginPostHog.shutdown()).toBeUndefined()
|
||||
posthogModule.__resetOsProviderForTesting()
|
||||
})
|
||||
|
||||
it("passes the strict PostHog constructor options for both clients", async () => {
|
||||
@@ -180,15 +178,16 @@ describe("posthog trackActive emission contract", () => {
|
||||
const emittedEvents = captured.map((message) => message.event)
|
||||
expect(emittedEvents).not.toContain("omo_hourly_active")
|
||||
const [dailyEvent] = captured
|
||||
if (!dailyEvent) {
|
||||
throw new Error("Expected daily event")
|
||||
}
|
||||
expect(dailyEvent?.event).toBe("omo_daily_active")
|
||||
expect(dailyEvent?.distinctId).toBe("distinct-cli")
|
||||
expect(dailyEvent?.properties).toMatchObject({
|
||||
day_utc: "2026-04-18",
|
||||
reason: "run_started",
|
||||
source: "cli",
|
||||
$process_person_profile: false,
|
||||
})
|
||||
expect(dailyEvent?.properties).not.toHaveProperty("hour_utc")
|
||||
expect(dailyEvent.properties?.day_utc).toBe("2026-04-18")
|
||||
expect(dailyEvent.properties?.reason).toBe("run_started")
|
||||
expect(dailyEvent.properties?.source).toBe("cli")
|
||||
expect(dailyEvent.properties?.$process_person_profile).toBe(false)
|
||||
expect(Object.prototype.hasOwnProperty.call(dailyEvent.properties ?? {}, "hour_utc")).toBe(false)
|
||||
})
|
||||
|
||||
it("emits nothing and never omo_hourly_active when captureDaily is false", async () => {
|
||||
|
||||
+24
-7
@@ -7,11 +7,17 @@ import { getPostHogActivityCaptureState } from "./posthog-activity-state"
|
||||
|
||||
/** @internal test-only seam: keep null in production to use the real implementation. */
|
||||
let activityStateProviderOverride: typeof getPostHogActivityCaptureState | null = null
|
||||
type OsProvider = Pick<typeof os, "arch" | "cpus" | "hostname" | "platform" | "release" | "totalmem" | "type">
|
||||
let osProviderOverride: OsProvider | null = null
|
||||
|
||||
function resolveActivityState(): ReturnType<typeof getPostHogActivityCaptureState> {
|
||||
return (activityStateProviderOverride ?? getPostHogActivityCaptureState)()
|
||||
}
|
||||
|
||||
function resolveOsProvider(): OsProvider {
|
||||
return osProviderOverride ?? os
|
||||
}
|
||||
|
||||
/** @internal test-only */
|
||||
export function __setActivityStateProviderForTesting(
|
||||
provider: typeof getPostHogActivityCaptureState,
|
||||
@@ -24,6 +30,16 @@ export function __resetActivityStateProviderForTesting(): void {
|
||||
activityStateProviderOverride = null
|
||||
}
|
||||
|
||||
/** @internal test-only */
|
||||
export function __setOsProviderForTesting(provider: OsProvider): void {
|
||||
osProviderOverride = provider
|
||||
}
|
||||
|
||||
/** @internal test-only */
|
||||
export function __resetOsProviderForTesting(): void {
|
||||
osProviderOverride = null
|
||||
}
|
||||
|
||||
const DEFAULT_POSTHOG_HOST = "https://us.i.posthog.com"
|
||||
const DEFAULT_POSTHOG_API_KEY = "phc_CFJhj5HyvA62QPhvyaUCtaq23aUfznnijg5VaaGkNk74"
|
||||
|
||||
@@ -67,7 +83,7 @@ function getPostHogHost(): string {
|
||||
|
||||
function safeCpus(): { length: number; model: string | undefined } {
|
||||
try {
|
||||
const cpus = os.cpus()
|
||||
const cpus = resolveOsProvider().cpus()
|
||||
return { length: cpus.length, model: cpus[0]?.model }
|
||||
} catch {
|
||||
return { length: 0, model: undefined }
|
||||
@@ -76,6 +92,7 @@ function safeCpus(): { length: number; model: string | undefined } {
|
||||
|
||||
function getSharedProperties(source: PostHogSource): NonNullable<PostHogCaptureEvent["properties"]> {
|
||||
const cpus = safeCpus()
|
||||
const osProvider = resolveOsProvider()
|
||||
|
||||
return {
|
||||
platform: "oh-my-opencode",
|
||||
@@ -85,13 +102,13 @@ function getSharedProperties(source: PostHogSource): NonNullable<PostHogCaptureE
|
||||
runtime: "bun",
|
||||
runtime_version: process.versions.bun ?? process.version,
|
||||
source,
|
||||
$os: os.platform(),
|
||||
$os_version: os.release(),
|
||||
os_arch: os.arch(),
|
||||
os_type: os.type(),
|
||||
$os: osProvider.platform(),
|
||||
$os_version: osProvider.release(),
|
||||
os_arch: osProvider.arch(),
|
||||
os_type: osProvider.type(),
|
||||
cpu_count: cpus.length,
|
||||
cpu_model: cpus.model,
|
||||
total_memory_gb: Math.round(os.totalmem() / 1024 / 1024 / 1024),
|
||||
total_memory_gb: Math.round(osProvider.totalmem() / 1024 / 1024 / 1024),
|
||||
locale: Intl.DateTimeFormat().resolvedOptions().locale,
|
||||
timezone: Intl.DateTimeFormat().resolvedOptions().timeZone,
|
||||
shell: process.env.SHELL,
|
||||
@@ -144,7 +161,7 @@ function createPostHogClient(
|
||||
|
||||
export function getPostHogDistinctId(): string {
|
||||
return createHash("sha256")
|
||||
.update(`${PUBLISHED_PACKAGE_NAME}:${os.hostname()}`)
|
||||
.update(`${PUBLISHED_PACKAGE_NAME}:${resolveOsProvider().hostname()}`)
|
||||
.digest("hex")
|
||||
}
|
||||
|
||||
|
||||
@@ -1,14 +1,11 @@
|
||||
/// <reference types="bun-types" />
|
||||
|
||||
import { beforeEach, describe, expect, it, mock } from "bun:test"
|
||||
|
||||
import type { TmuxConfig } from "../../../config/schema"
|
||||
import type { TmuxCommandResult } from "../runner"
|
||||
|
||||
const paneSpawnSpecifier = import.meta.resolve("./pane-spawn")
|
||||
const environmentSpecifier = import.meta.resolve("./environment")
|
||||
const loggerSpecifier = import.meta.resolve("../../logger")
|
||||
const runnerSpecifier = import.meta.resolve("../runner")
|
||||
const serverHealthSpecifier = import.meta.resolve("./server-health")
|
||||
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
|
||||
|
||||
const enabledTmuxConfig = {
|
||||
enabled: true,
|
||||
@@ -28,7 +25,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
|
||||
}))
|
||||
const isInsideTmuxMock = mock((): boolean => true)
|
||||
const isServerRunningMock = mock(async (): Promise<boolean> => true)
|
||||
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
|
||||
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
|
||||
const logMock = mock(() => undefined)
|
||||
|
||||
function toStringArray(value: unknown): string[] {
|
||||
@@ -64,23 +61,24 @@ function getSplitWindowCommand(): string {
|
||||
return splitCommand
|
||||
}
|
||||
|
||||
function createDeps(): NonNullable<Parameters<typeof import("./pane-spawn").spawnTmuxPane>[7]> {
|
||||
return {
|
||||
log: logMock,
|
||||
runTmuxCommand: runTmuxCommandMock,
|
||||
isInsideTmux: isInsideTmuxMock,
|
||||
isServerRunning: isServerRunningMock,
|
||||
getTmuxPath: getTmuxPathMock,
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpawnTmuxPane(): Promise<typeof import("./pane-spawn").spawnTmuxPane> {
|
||||
const module = await import(`${paneSpawnSpecifier}?test=${crypto.randomUUID()}`)
|
||||
return module.spawnTmuxPane
|
||||
}
|
||||
|
||||
function registerModuleMocks(): void {
|
||||
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
|
||||
mock.module(loggerSpecifier, () => ({ log: logMock }))
|
||||
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
|
||||
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
|
||||
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
|
||||
}
|
||||
|
||||
describe("spawnTmuxPane runner integration", () => {
|
||||
beforeEach(() => {
|
||||
mock.restore()
|
||||
registerModuleMocks()
|
||||
runTmuxCommandMock.mockClear()
|
||||
isInsideTmuxMock.mockClear()
|
||||
isServerRunningMock.mockClear()
|
||||
@@ -109,7 +107,7 @@ describe("spawnTmuxPane runner integration", () => {
|
||||
const directory = "/tmp/omo-project/(pane)"
|
||||
|
||||
// when
|
||||
const result = await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0")
|
||||
const result = await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0", "-h", createDeps())
|
||||
|
||||
// then
|
||||
const firstCall = getRunTmuxCommandCall(0)
|
||||
@@ -125,7 +123,7 @@ describe("spawnTmuxPane runner integration", () => {
|
||||
const spawnTmuxPane = await loadSpawnTmuxPane()
|
||||
|
||||
// when
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0")
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0", "-h", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSplitWindowCommand()).toContain("--dir '/path with spaces/here'")
|
||||
@@ -136,7 +134,7 @@ describe("spawnTmuxPane runner integration", () => {
|
||||
const spawnTmuxPane = await loadSpawnTmuxPane()
|
||||
|
||||
// when
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0")
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0", "-h", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSplitWindowCommand()).toContain(`--dir '${process.cwd()}'`)
|
||||
@@ -147,7 +145,7 @@ describe("spawnTmuxPane runner integration", () => {
|
||||
const spawnTmuxPane = await loadSpawnTmuxPane()
|
||||
|
||||
// when
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0")
|
||||
await spawnTmuxPane("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0", "-h", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSplitWindowCommand()).toContain("--dir '/path/with'\\''quote'")
|
||||
|
||||
@@ -1,11 +1,36 @@
|
||||
import type { TmuxConfig } from "../../../config/schema"
|
||||
import { getTmuxPath } from "../../../tools/interactive-bash/tmux-path-resolver"
|
||||
import type { SpawnPaneResult } from "../types"
|
||||
import type { runTmuxCommand as RunTmuxCommand } from "../runner"
|
||||
import type { SplitDirection } from "./environment"
|
||||
import { isInsideTmux } from "./environment"
|
||||
import { isServerRunning } from "./server-health"
|
||||
import { shellSingleQuote } from "../../shell-env"
|
||||
|
||||
type SpawnTmuxPaneDeps = {
|
||||
log: (message: string, data?: unknown) => void
|
||||
runTmuxCommand: typeof RunTmuxCommand
|
||||
isInsideTmux: typeof isInsideTmux
|
||||
isServerRunning: typeof isServerRunning
|
||||
getTmuxPath: typeof getTmuxPath
|
||||
}
|
||||
|
||||
async function resolveSpawnTmuxPaneDeps(deps?: Partial<SpawnTmuxPaneDeps>): Promise<SpawnTmuxPaneDeps> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
|
||||
return {
|
||||
log,
|
||||
runTmuxCommand,
|
||||
isInsideTmux,
|
||||
isServerRunning,
|
||||
getTmuxPath,
|
||||
...deps,
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawnTmuxPane(
|
||||
sessionId: string,
|
||||
description: string,
|
||||
@@ -14,11 +39,10 @@ export async function spawnTmuxPane(
|
||||
directory: string,
|
||||
targetPaneId?: string,
|
||||
splitDirection: SplitDirection = "-h",
|
||||
depsInput?: Partial<SpawnTmuxPaneDeps>,
|
||||
): Promise<SpawnPaneResult> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
const deps = await resolveSpawnTmuxPaneDeps(depsInput)
|
||||
const { log, runTmuxCommand } = deps
|
||||
|
||||
log("[spawnTmuxPane] called", {
|
||||
sessionId,
|
||||
@@ -33,18 +57,18 @@ export async function spawnTmuxPane(
|
||||
log("[spawnTmuxPane] SKIP: config.enabled is false")
|
||||
return { success: false }
|
||||
}
|
||||
if (!isInsideTmux()) {
|
||||
if (!deps.isInsideTmux()) {
|
||||
log("[spawnTmuxPane] SKIP: not inside tmux", { TMUX: process.env.TMUX })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const serverRunning = await isServerRunning(serverUrl)
|
||||
const serverRunning = await deps.isServerRunning(serverUrl)
|
||||
if (!serverRunning) {
|
||||
log("[spawnTmuxPane] SKIP: server not running", { serverUrl })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const tmux = await getTmuxPath()
|
||||
const tmux = await deps.getTmuxPath()
|
||||
if (!tmux) {
|
||||
log("[spawnTmuxPane] SKIP: tmux not found")
|
||||
return { success: false }
|
||||
|
||||
@@ -4,11 +4,6 @@ import type { TmuxConfig } from "../../../config/schema"
|
||||
import type { TmuxCommandResult } from "../runner"
|
||||
|
||||
const sessionSpawnSpecifier = import.meta.resolve("./session-spawn")
|
||||
const environmentSpecifier = import.meta.resolve("./environment")
|
||||
const loggerSpecifier = import.meta.resolve("../../logger")
|
||||
const runnerSpecifier = import.meta.resolve("../runner")
|
||||
const serverHealthSpecifier = import.meta.resolve("./server-health")
|
||||
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
|
||||
|
||||
const enabledTmuxConfig = {
|
||||
enabled: true,
|
||||
@@ -28,7 +23,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
|
||||
}))
|
||||
const isInsideTmuxMock = mock((): boolean => true)
|
||||
const isServerRunningMock = mock(async (): Promise<boolean> => true)
|
||||
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
|
||||
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
|
||||
const logMock = mock(() => undefined)
|
||||
|
||||
function toStringArray(value: unknown): string[] {
|
||||
@@ -64,23 +59,24 @@ function getSpawnCommand(): string {
|
||||
return newSessionCommand
|
||||
}
|
||||
|
||||
function createDeps(): NonNullable<Parameters<typeof import("./session-spawn").spawnTmuxSession>[6]> {
|
||||
return {
|
||||
log: logMock,
|
||||
runTmuxCommand: runTmuxCommandMock,
|
||||
isInsideTmux: isInsideTmuxMock,
|
||||
isServerRunning: isServerRunningMock,
|
||||
getTmuxPath: getTmuxPathMock,
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpawnTmuxSession(): Promise<typeof import("./session-spawn").spawnTmuxSession> {
|
||||
const module = await import(`${sessionSpawnSpecifier}?test=${crypto.randomUUID()}`)
|
||||
return module.spawnTmuxSession
|
||||
}
|
||||
|
||||
function registerModuleMocks(): void {
|
||||
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
|
||||
mock.module(loggerSpecifier, () => ({ log: logMock }))
|
||||
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
|
||||
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
|
||||
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
|
||||
}
|
||||
|
||||
describe("spawnTmuxSession runner integration", () => {
|
||||
beforeEach(() => {
|
||||
mock.restore()
|
||||
registerModuleMocks()
|
||||
runTmuxCommandMock.mockClear()
|
||||
isInsideTmuxMock.mockClear()
|
||||
isServerRunningMock.mockClear()
|
||||
@@ -111,7 +107,7 @@ describe("spawnTmuxSession runner integration", () => {
|
||||
const directory = "/tmp/omo-project/(session)"
|
||||
|
||||
// when
|
||||
const result = await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0")
|
||||
const result = await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, "%0", createDeps())
|
||||
|
||||
// then
|
||||
const displayCall = getRunTmuxCommandCall(0)
|
||||
@@ -134,7 +130,7 @@ describe("spawnTmuxSession runner integration", () => {
|
||||
const spawnTmuxSession = await loadSpawnTmuxSession()
|
||||
|
||||
// when
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0")
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", "%0", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSpawnCommand()).toContain("--dir '/path with spaces/here'")
|
||||
@@ -145,7 +141,7 @@ describe("spawnTmuxSession runner integration", () => {
|
||||
const spawnTmuxSession = await loadSpawnTmuxSession()
|
||||
|
||||
// when
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0")
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", "%0", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSpawnCommand()).toContain(`--dir '${process.cwd()}'`)
|
||||
@@ -156,7 +152,7 @@ describe("spawnTmuxSession runner integration", () => {
|
||||
const spawnTmuxSession = await loadSpawnTmuxSession()
|
||||
|
||||
// when
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0")
|
||||
await spawnTmuxSession("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", "%0", createDeps())
|
||||
|
||||
// then
|
||||
expect(getSpawnCommand()).toContain("--dir '/path/with'\\''quote'")
|
||||
|
||||
@@ -8,6 +8,30 @@ import { shellSingleQuote } from "../../shell-env"
|
||||
|
||||
const ISOLATED_SESSION_NAME_PREFIX = "omo-agents"
|
||||
|
||||
type SpawnTmuxSessionDeps = {
|
||||
log: (message: string, data?: unknown) => void
|
||||
runTmuxCommand: typeof RunTmuxCommand
|
||||
isInsideTmux: typeof isInsideTmux
|
||||
isServerRunning: typeof isServerRunning
|
||||
getTmuxPath: typeof getTmuxPath
|
||||
}
|
||||
|
||||
async function resolveSpawnTmuxSessionDeps(deps?: Partial<SpawnTmuxSessionDeps>): Promise<SpawnTmuxSessionDeps> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
|
||||
return {
|
||||
log,
|
||||
runTmuxCommand,
|
||||
isInsideTmux,
|
||||
isServerRunning,
|
||||
getTmuxPath,
|
||||
...deps,
|
||||
}
|
||||
}
|
||||
|
||||
export function getIsolatedSessionName(pid: number = process.pid): string {
|
||||
return `${ISOLATED_SESSION_NAME_PREFIX}-${pid}`
|
||||
}
|
||||
@@ -39,11 +63,10 @@ export async function spawnTmuxSession(
|
||||
serverUrl: string,
|
||||
directory: string,
|
||||
sourcePaneId?: string,
|
||||
depsInput?: Partial<SpawnTmuxSessionDeps>,
|
||||
): Promise<SpawnPaneResult> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
const deps = await resolveSpawnTmuxSessionDeps(depsInput)
|
||||
const { log, runTmuxCommand } = deps
|
||||
|
||||
log("[spawnTmuxSession] called", {
|
||||
sessionId,
|
||||
@@ -56,18 +79,18 @@ export async function spawnTmuxSession(
|
||||
log("[spawnTmuxSession] SKIP: config.enabled is false")
|
||||
return { success: false }
|
||||
}
|
||||
if (!isInsideTmux()) {
|
||||
if (!deps.isInsideTmux()) {
|
||||
log("[spawnTmuxSession] SKIP: not inside tmux", { TMUX: process.env.TMUX })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const serverRunning = await isServerRunning(serverUrl)
|
||||
const serverRunning = await deps.isServerRunning(serverUrl)
|
||||
if (!serverRunning) {
|
||||
log("[spawnTmuxSession] SKIP: server not running", { serverUrl })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const tmux = await getTmuxPath()
|
||||
const tmux = await deps.getTmuxPath()
|
||||
if (!tmux) {
|
||||
log("[spawnTmuxSession] SKIP: tmux not found")
|
||||
return { success: false }
|
||||
|
||||
@@ -4,11 +4,6 @@ import type { TmuxConfig } from "../../../config/schema"
|
||||
import type { TmuxCommandResult } from "../runner"
|
||||
|
||||
const windowSpawnSpecifier = import.meta.resolve("./window-spawn")
|
||||
const environmentSpecifier = import.meta.resolve("./environment")
|
||||
const loggerSpecifier = import.meta.resolve("../../logger")
|
||||
const runnerSpecifier = import.meta.resolve("../runner")
|
||||
const serverHealthSpecifier = import.meta.resolve("./server-health")
|
||||
const tmuxPathResolverSpecifier = import.meta.resolve("../../../tools/interactive-bash/tmux-path-resolver")
|
||||
|
||||
const enabledTmuxConfig = {
|
||||
enabled: true,
|
||||
@@ -28,7 +23,7 @@ const runTmuxCommandMock = mock(async (): Promise<TmuxCommandResult> => ({
|
||||
}))
|
||||
const isInsideTmuxMock = mock((): boolean => true)
|
||||
const isServerRunningMock = mock(async (): Promise<boolean> => true)
|
||||
const getTmuxPathMock = mock(async (): Promise<string | undefined> => "sh")
|
||||
const getTmuxPathMock = mock(async (): Promise<string | null> => "sh")
|
||||
const logMock = mock(() => undefined)
|
||||
|
||||
function toStringArray(value: unknown): string[] {
|
||||
@@ -64,23 +59,24 @@ function getNewWindowCommand(): string {
|
||||
return newWindowCommand
|
||||
}
|
||||
|
||||
function createDeps(): NonNullable<Parameters<typeof import("./window-spawn").spawnTmuxWindow>[5]> {
|
||||
return {
|
||||
log: logMock,
|
||||
runTmuxCommand: runTmuxCommandMock,
|
||||
isInsideTmux: isInsideTmuxMock,
|
||||
isServerRunning: isServerRunningMock,
|
||||
getTmuxPath: getTmuxPathMock,
|
||||
}
|
||||
}
|
||||
|
||||
async function loadSpawnTmuxWindow(): Promise<typeof import("./window-spawn").spawnTmuxWindow> {
|
||||
const module = await import(`${windowSpawnSpecifier}?test=${crypto.randomUUID()}`)
|
||||
return module.spawnTmuxWindow
|
||||
}
|
||||
|
||||
function registerModuleMocks(): void {
|
||||
mock.module(environmentSpecifier, () => ({ isInsideTmux: isInsideTmuxMock }))
|
||||
mock.module(loggerSpecifier, () => ({ log: logMock }))
|
||||
mock.module(runnerSpecifier, () => ({ runTmuxCommand: runTmuxCommandMock }))
|
||||
mock.module(serverHealthSpecifier, () => ({ isServerRunning: isServerRunningMock }))
|
||||
mock.module(tmuxPathResolverSpecifier, () => ({ getTmuxPath: getTmuxPathMock }))
|
||||
}
|
||||
|
||||
describe("spawnTmuxWindow runner integration", () => {
|
||||
beforeEach(() => {
|
||||
mock.restore()
|
||||
registerModuleMocks()
|
||||
runTmuxCommandMock.mockClear()
|
||||
isInsideTmuxMock.mockClear()
|
||||
isServerRunningMock.mockClear()
|
||||
@@ -109,7 +105,7 @@ describe("spawnTmuxWindow runner integration", () => {
|
||||
const directory = "/tmp/omo-project/(window)"
|
||||
|
||||
// when
|
||||
const result = await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory)
|
||||
const result = await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", directory, createDeps())
|
||||
|
||||
// then
|
||||
const firstCall = getRunTmuxCommandCall(0)
|
||||
@@ -125,7 +121,7 @@ describe("spawnTmuxWindow runner integration", () => {
|
||||
const spawnTmuxWindow = await loadSpawnTmuxWindow()
|
||||
|
||||
// when
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here")
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path with spaces/here", createDeps())
|
||||
|
||||
// then
|
||||
expect(getNewWindowCommand()).toContain("--dir '/path with spaces/here'")
|
||||
@@ -136,7 +132,7 @@ describe("spawnTmuxWindow runner integration", () => {
|
||||
const spawnTmuxWindow = await loadSpawnTmuxWindow()
|
||||
|
||||
// when
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "")
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "", createDeps())
|
||||
|
||||
// then
|
||||
expect(getNewWindowCommand()).toContain(`--dir '${process.cwd()}'`)
|
||||
@@ -147,7 +143,7 @@ describe("spawnTmuxWindow runner integration", () => {
|
||||
const spawnTmuxWindow = await loadSpawnTmuxWindow()
|
||||
|
||||
// when
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote")
|
||||
await spawnTmuxWindow("session-1", "worker", enabledTmuxConfig, "http://127.0.0.1:1234", "/path/with'quote", createDeps())
|
||||
|
||||
// then
|
||||
expect(getNewWindowCommand()).toContain("--dir '/path/with'\\''quote'")
|
||||
|
||||
@@ -4,20 +4,44 @@ import type { SpawnPaneResult } from "../types"
|
||||
import { isInsideTmux } from "./environment"
|
||||
import { isServerRunning } from "./server-health"
|
||||
import { shellSingleQuote } from "../../shell-env"
|
||||
import type { runTmuxCommand as RunTmuxCommand } from "../runner"
|
||||
|
||||
const ISOLATED_WINDOW_NAME = "omo-agents"
|
||||
|
||||
type SpawnTmuxWindowDeps = {
|
||||
log: (message: string, data?: unknown) => void
|
||||
runTmuxCommand: typeof RunTmuxCommand
|
||||
isInsideTmux: typeof isInsideTmux
|
||||
isServerRunning: typeof isServerRunning
|
||||
getTmuxPath: typeof getTmuxPath
|
||||
}
|
||||
|
||||
async function resolveSpawnTmuxWindowDeps(deps?: Partial<SpawnTmuxWindowDeps>): Promise<SpawnTmuxWindowDeps> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
|
||||
return {
|
||||
log,
|
||||
runTmuxCommand,
|
||||
isInsideTmux,
|
||||
isServerRunning,
|
||||
getTmuxPath,
|
||||
...deps,
|
||||
}
|
||||
}
|
||||
|
||||
export async function spawnTmuxWindow(
|
||||
sessionId: string,
|
||||
description: string,
|
||||
config: TmuxConfig,
|
||||
serverUrl: string,
|
||||
directory: string,
|
||||
depsInput?: Partial<SpawnTmuxWindowDeps>,
|
||||
): Promise<SpawnPaneResult> {
|
||||
const [{ log }, { runTmuxCommand }] = await Promise.all([
|
||||
import("../../logger"),
|
||||
import("../runner"),
|
||||
])
|
||||
const deps = await resolveSpawnTmuxWindowDeps(depsInput)
|
||||
const { log, runTmuxCommand } = deps
|
||||
|
||||
log("[spawnTmuxWindow] called", {
|
||||
sessionId,
|
||||
@@ -30,18 +54,18 @@ export async function spawnTmuxWindow(
|
||||
log("[spawnTmuxWindow] SKIP: config.enabled is false")
|
||||
return { success: false }
|
||||
}
|
||||
if (!isInsideTmux()) {
|
||||
if (!deps.isInsideTmux()) {
|
||||
log("[spawnTmuxWindow] SKIP: not inside tmux", { TMUX: process.env.TMUX })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const serverRunning = await isServerRunning(serverUrl)
|
||||
const serverRunning = await deps.isServerRunning(serverUrl)
|
||||
if (!serverRunning) {
|
||||
log("[spawnTmuxWindow] SKIP: server not running", { serverUrl })
|
||||
return { success: false }
|
||||
}
|
||||
|
||||
const tmux = await getTmuxPath()
|
||||
const tmux = await deps.getTmuxPath()
|
||||
if (!tmux) {
|
||||
log("[spawnTmuxWindow] SKIP: tmux not found")
|
||||
return { success: false }
|
||||
|
||||
Reference in New Issue
Block a user