mirror of
https://github.com/stablyai/orca.git
synced 2026-11-14 01:47:24 +00:00
test: remove five low-value runtime pipeline suites (#41844)
* test: remove runtime projection suites with low-value fixtures * test: remove simulated mobile and worker observation pipelines --------- Co-authored-by: OrcaWin <293788423+OrcaWin@users.noreply.github.com>
This commit is contained in:
@@ -480,7 +480,6 @@
|
||||
1 test-no-wall-clock:src/main/runtime/rpc/methods/agent-launch/agent-launch-replay.test.ts
|
||||
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/federation/federation-output.test.ts
|
||||
2 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/observation/fleet-status-observed-identity.test.ts
|
||||
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/observation/worker-interactive-wait.test.ts
|
||||
1 test-no-wall-clock:src/main/runtime/rpc/methods/orchestration/worker/output/worker-output.test.ts
|
||||
2 test-no-wall-clock:src/main/runtime/rpc/server/remote-runtime-request-connection.integration.test.ts
|
||||
2 test-no-wall-clock:src/main/runtime/rpc/server/runtime-rpc-pairing-mode-persistence.test.ts
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -2956,7 +2956,6 @@
|
||||
"src/main/runtime/integrations/linear/linear-save-markdown.test.ts": 930,
|
||||
"src/main/runtime/workspaces/creation/managed-worktree-create-registry.test.ts": 4143,
|
||||
"src/main/runtime/workspaces/creation/managed-worktree-create-telemetry.test.ts": 4152,
|
||||
"src/main/runtime/workspaces/removal/missing-worktree-terminal-reconciliation.test.ts": 432,
|
||||
"src/main/runtime/mobile-cloud-relay/testing/desktop-relay-service-installer.test.ts": 48,
|
||||
"src/main/runtime/mobile-cloud-relay/testing/mobile-relay-e2ee.integration.test.ts": 408,
|
||||
"src/main/runtime/mobile-cloud-relay/authentication/relay-auth-coordinator-readiness.test.ts": 580,
|
||||
@@ -2980,7 +2979,6 @@
|
||||
"src/main/runtime/push/notifications/mobile-notification-replay.test.ts": 43,
|
||||
"src/main/runtime/rpc/server/pairing/mobile-pairing-userdata-path.test.ts": 171,
|
||||
"src/main/runtime/terminal/layout/mobile-presence-lock.test.ts": 25928,
|
||||
"src/main/runtime/terminal/lifecycle/mobile-session-dead-pty.test.ts": 4708,
|
||||
"src/main/runtime/session-tabs/headless-editor-tabs.test.ts": 57,
|
||||
"src/main/runtime/session-lifecycle/publication/mobile-session-tabs-agent-status-heartbeat.test.ts": 65,
|
||||
"src/main/runtime/terminal/surface/mobile-session-terminal-persistence-retirement.test.ts": 278,
|
||||
@@ -3271,7 +3269,6 @@
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/release/worker-auto-release-turns.test.ts": 3578,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/release/worker-auto-release.test.ts": 4863,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/follow-up/worker-follow-up.test.ts": 4916,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/observation/worker-interactive-wait.test.ts": 5579,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/fleet/worker-list-pagination.test.ts": 4740,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/output/worker-output.test.ts": 541,
|
||||
"src/main/runtime/rpc/methods/orchestration/worker/release/worker-release-authority-contracts.test.ts": 3949,
|
||||
|
||||
@@ -43,7 +43,6 @@ await import('../orca-runtime-tests/terminal-creation-and-readiness-part-10.spec
|
||||
await import('../orca-runtime-tests/terminal-creation-and-readiness-part-11.spec')
|
||||
await import('../orca-runtime-tests/conpty-stream-read.spec')
|
||||
await import('../orca-runtime-tests/terminal-creation-and-readiness-part-12.spec')
|
||||
await import('../orca-runtime-tests/terminal-output-and-worker-recovery.spec')
|
||||
await import('../orca-runtime-tests/terminal-output-and-worker-recovery-part-02.spec')
|
||||
await import('../orca-runtime-tests/terminal-output-and-worker-recovery-part-03.spec')
|
||||
await import('../orca-runtime-tests/terminal-output-and-worker-recovery-part-04.spec')
|
||||
@@ -71,7 +70,6 @@ await import('../orca-runtime-tests/mobile-session-tabs-targeted-agent-resume.sp
|
||||
await import('../orca-runtime-tests/mobile-session-tabs-headless-wake-report.spec')
|
||||
await import('../orca-runtime-tests/exit-retirement-activation.spec')
|
||||
await import('../orca-runtime-tests/mobile-creation-and-orchestration.spec')
|
||||
await import('../orca-runtime-tests/mobile-creation-and-orchestration-part-02.spec')
|
||||
await import('../orca-runtime-tests/mobile-creation-and-orchestration-part-03.spec')
|
||||
await import('../orca-runtime-tests/mobile-creation-and-orchestration-part-04.spec')
|
||||
await import('../orca-runtime-tests/mobile-summaries.spec')
|
||||
|
||||
@@ -1,81 +0,0 @@
|
||||
import { settledWriteStub } from '../../providers/testing/settled-pty-write-stub'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import './runtime-test-module-registration.spec'
|
||||
import { OrcaRuntimeService } from '../orca-runtime'
|
||||
|
||||
import {
|
||||
setInMemoryOrchestrationMessages,
|
||||
store,
|
||||
syncSinglePty
|
||||
} from './orca-runtime-test-fixtures.spec'
|
||||
import { InMemoryOrchestrationMessages } from '../orchestration/mailbox/terminal-pointer/in-memory-orchestration-messages.spec'
|
||||
import { MessageWake } from '../orchestration/mailbox/runtime-message-waiters'
|
||||
|
||||
describe('OrcaRuntimeService', () => {
|
||||
it('repoints worker_done when a stale waiter wakes without consuming it', async () => {
|
||||
vi.useFakeTimers()
|
||||
try {
|
||||
const runtime = new OrcaRuntimeService(store)
|
||||
const db = new InMemoryOrchestrationMessages()
|
||||
const write = vi.fn().mockReturnValue(true)
|
||||
setInMemoryOrchestrationMessages(runtime, db)
|
||||
runtime.setPtyController({
|
||||
write,
|
||||
writeWithSettlement: settledWriteStub(write),
|
||||
kill: vi.fn(),
|
||||
getForegroundProcess: async () => null
|
||||
})
|
||||
syncSinglePty(runtime)
|
||||
|
||||
const [terminal] = (await runtime.listTerminals()).terminals
|
||||
db.setRun({
|
||||
id: 'run_stale_waiter',
|
||||
coordinator_handle: terminal.handle,
|
||||
coordinator_pane_key: `${terminal.tabId}:${terminal.leafId}`
|
||||
})
|
||||
runtime.onPtyData('pty-1', '\x1b]0;Codex working\x07', 100)
|
||||
runtime.onPtyData('pty-1', '\x1b]0;Codex done\x07', 101)
|
||||
write.mockClear()
|
||||
|
||||
const staleWait = runtime.waitForMessage('run:run_stale_waiter', {
|
||||
typeFilter: ['worker_done'],
|
||||
timeoutMs: 60_000
|
||||
})
|
||||
db.insertMessage({
|
||||
from: 'term_final_worker',
|
||||
to: 'run:run_stale_waiter',
|
||||
subject: 'final worker settled',
|
||||
type: 'worker_done'
|
||||
})
|
||||
runtime.notifyMessageArrived('run:run_stale_waiter', 'worker_done')
|
||||
|
||||
await expect(staleWait).resolves.toBeInstanceOf(MessageWake)
|
||||
expect(write).not.toHaveBeenCalled()
|
||||
|
||||
db.insertMessage({
|
||||
from: 'term_other_worker',
|
||||
to: 'run:run_stale_waiter',
|
||||
subject: 'newer status',
|
||||
type: 'status'
|
||||
})
|
||||
runtime.deliverPendingMessagesForHandle('run:run_stale_waiter', new Set(['worker_done']))
|
||||
await vi.advanceTimersByTimeAsync(500)
|
||||
|
||||
const pointers = () =>
|
||||
write.mock.calls.filter(
|
||||
([, payload]) => typeof payload === 'string' && payload.includes('orchestration check')
|
||||
)
|
||||
expect(pointers()).toHaveLength(1)
|
||||
expect(pointers()[0]?.[1]).toContain('You have 1 orchestration message')
|
||||
|
||||
await vi.advanceTimersByTimeAsync(1_500)
|
||||
expect(pointers()).toHaveLength(2)
|
||||
expect(pointers()[1]?.[1]).toContain('You have 1 orchestration message')
|
||||
db.close()
|
||||
} finally {
|
||||
vi.useRealTimers()
|
||||
}
|
||||
})
|
||||
|
||||
// Mail follows the terminal: a window reload does not hold a live-idle terminal's restored mail.
|
||||
})
|
||||
@@ -1,70 +0,0 @@
|
||||
import { settledWriteStub } from '../../providers/testing/settled-pty-write-stub'
|
||||
import { makeAgentStatusStoreWiring } from '../terminal/identity/agent-status-store-wiring.test-fixture'
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import { committingSpawn } from './bind-first-pane.test-fixture'
|
||||
import './runtime-test-module-registration.spec'
|
||||
import { OrcaRuntimeService } from '../orca-runtime'
|
||||
import '../../../shared/agent-status/title/agent-title-status'
|
||||
import '../orca-runtime-test-mocks/setup.spec'
|
||||
import { waitForMobileSessionTabsEvents } from './mobile-session-tab-event-wait'
|
||||
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-session-contracts'
|
||||
import { HEADLESS_LEAF_ID, TEST_WORKTREE_ID, store } from './orca-runtime-test-fixtures.spec'
|
||||
|
||||
describe('OrcaRuntimeService', () => {
|
||||
// Hook-only OSC 9999 transitions must republish mobile status without a renderer (#7970).
|
||||
it('republishes mobile session tabs with hook payloads for title-less OSC 9999 transitions', async () => {
|
||||
const spawn = vi.fn().mockResolvedValue({ id: 'hook-only-pty' })
|
||||
const statusWiring = makeAgentStatusStoreWiring()
|
||||
const runtime = new OrcaRuntimeService(store, undefined, statusWiring.deps)
|
||||
const uninstallRepublish = statusWiring.attach(runtime)
|
||||
runtime.setPtyController({
|
||||
spawn: committingSpawn(runtime, spawn),
|
||||
write: () => true,
|
||||
writeWithSettlement: settledWriteStub(() => true),
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => null
|
||||
})
|
||||
const events: RuntimeMobileSessionTabsResult[] = []
|
||||
const unsubscribe = runtime.onMobileSessionTabsChanged((snapshot) => events.push(snapshot))
|
||||
|
||||
await runtime.createTerminal(`id:${TEST_WORKTREE_ID}`, {
|
||||
tabId: 'hook-tab',
|
||||
leafId: HEADLESS_LEAF_ID
|
||||
})
|
||||
events.length = 0
|
||||
|
||||
runtime.onPtyData(
|
||||
'hook-only-pty',
|
||||
'\x1b]9999;{"state":"working","prompt":"fix the tests","agentType":"opencode"}\x07',
|
||||
100
|
||||
)
|
||||
|
||||
await waitForMobileSessionTabsEvents(events, 1)
|
||||
expect(events).toHaveLength(1)
|
||||
expect(events[0]?.tabs[0]).toEqual(
|
||||
expect.objectContaining({
|
||||
type: 'terminal',
|
||||
agentStatus: expect.objectContaining({
|
||||
state: 'working',
|
||||
prompt: 'fix the tests',
|
||||
agentType: 'opencode'
|
||||
})
|
||||
})
|
||||
)
|
||||
|
||||
runtime.onPtyData(
|
||||
'hook-only-pty',
|
||||
'\x1b]9999;{"state":"waiting","prompt":"fix the tests","agentType":"opencode"}\x07',
|
||||
101
|
||||
)
|
||||
|
||||
await waitForMobileSessionTabsEvents(events, 2)
|
||||
expect(events).toHaveLength(2)
|
||||
expect(events[1]?.tabs[0]?.type === 'terminal' && events[1].tabs[0].agentStatus).toEqual(
|
||||
expect.objectContaining({ state: 'waiting' })
|
||||
)
|
||||
|
||||
unsubscribe()
|
||||
uninstallRepublish()
|
||||
})
|
||||
})
|
||||
-134
@@ -1,134 +0,0 @@
|
||||
// Drive real detector/plumbing through RPC to distinguish workers waiting on human prompts (STA-3714, STA-4513).
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { OrcaRuntimeService } from '../../../../../orca-runtime'
|
||||
import { OrchestrationDb } from '../../../../../orchestration/db/orchestration-db'
|
||||
import '../../../../../orchestration/db/contract-constants'
|
||||
import '../../../../../orchestration/db/messages/mailbox-routing-page'
|
||||
import '../../../../../orchestration/db/dispatch-row-writer'
|
||||
import '../../../../../orchestration/db/attempt-outcome-projection'
|
||||
import { ORCHESTRATION_METHODS } from '../../orchestration'
|
||||
import { createRootDispatch } from '../../../../../orchestration/db/root-dispatch-test-fixture'
|
||||
|
||||
vi.mock('electron', () => ({
|
||||
BrowserWindow: { fromId: vi.fn(() => null) },
|
||||
webContents: { fromId: vi.fn(() => null) },
|
||||
ipcMain: { on: vi.fn(), removeListener: vi.fn() },
|
||||
app: { getPath: vi.fn(() => '/tmp') }
|
||||
}))
|
||||
|
||||
const LEAF_ID = '22222222-2222-4222-8222-222222222222'
|
||||
const TAB_ID = 'tab-worker'
|
||||
const WORKTREE_ID = 'wt-worker'
|
||||
const PTY_ID = 'pty-worker'
|
||||
|
||||
// Captured verbatim from cursor-agent 2026.08.11-e8db854 driven through Orca.
|
||||
function fixture(name: string): string {
|
||||
return readFileSync(join(__dirname, '../../../../../__fixtures__', `${name}.txt`), 'utf8')
|
||||
}
|
||||
|
||||
function workerShowMethod() {
|
||||
const method = ORCHESTRATION_METHODS.find(
|
||||
(candidate) => candidate.name === 'orchestration.workerShow'
|
||||
)
|
||||
if (!method) {
|
||||
throw new Error('Missing method orchestration.workerShow')
|
||||
}
|
||||
return method
|
||||
}
|
||||
|
||||
describe('worker-show interactive wait (STA-3714, STA-4513)', () => {
|
||||
let db: OrchestrationDb | undefined
|
||||
|
||||
afterEach(() => db?.close())
|
||||
|
||||
async function showWorkerPaneServing(paneOutput: string, opts?: { breakIdentity?: boolean }) {
|
||||
const runtime = new OrcaRuntimeService(null)
|
||||
const internals = runtime as unknown as {
|
||||
resolveTerminalWorkspaceLaunchScope: (selector: string) => Promise<unknown>
|
||||
}
|
||||
vi.spyOn(internals, 'resolveTerminalWorkspaceLaunchScope').mockResolvedValue({
|
||||
id: WORKTREE_ID,
|
||||
path: '/repo/app',
|
||||
connectionId: null,
|
||||
repo: null,
|
||||
folderWorkspace: null
|
||||
})
|
||||
runtime.setPtyController({
|
||||
spawn: vi.fn().mockResolvedValue({ id: PTY_ID, incarnationId: 'inc-1' }),
|
||||
write: () => true,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => 'cursor-agent'
|
||||
})
|
||||
const terminal = await runtime.createTerminal(`id:${WORKTREE_ID}`, {
|
||||
tabId: TAB_ID,
|
||||
leafId: LEAF_ID,
|
||||
title: 'worker'
|
||||
})
|
||||
runtime.attachWindow(1)
|
||||
runtime.syncWindowGraph(1, {
|
||||
tabs: [
|
||||
{
|
||||
tabId: TAB_ID,
|
||||
worktreeId: WORKTREE_ID,
|
||||
title: 'worker',
|
||||
activeLeafId: LEAF_ID,
|
||||
layout: null
|
||||
}
|
||||
],
|
||||
leaves: [
|
||||
{
|
||||
tabId: TAB_ID,
|
||||
worktreeId: WORKTREE_ID,
|
||||
leafId: LEAF_ID,
|
||||
paneRuntimeId: 1,
|
||||
ptyId: PTY_ID,
|
||||
// cursor-agent's spinner title, identical whether it runs or waits.
|
||||
paneTitle: '⠇ Cursor Agent'
|
||||
}
|
||||
]
|
||||
})
|
||||
runtime.onPtyData(PTY_ID, paneOutput, Date.now())
|
||||
|
||||
db = new OrchestrationDb(':memory:')
|
||||
runtime.setOrchestrationDb(db)
|
||||
const run = db.createRun({
|
||||
objective: 'supervise lanes',
|
||||
coordinatorHandle: 'term_coord',
|
||||
coordinatorPaneKey: 'tab_coord:leaf_coord'
|
||||
})
|
||||
const task = db.createTask({ spec: 'run the suite', runId: run.id })
|
||||
const paneKey = runtime.getTerminalPaneKey(terminal.handle)
|
||||
const incarnation = runtime.getTerminalProcessIncarnation(terminal.handle)
|
||||
if (!paneKey || !incarnation) {
|
||||
throw new Error('Runtime did not expose the worker pane identity.')
|
||||
}
|
||||
const dispatch = createRootDispatch(
|
||||
db,
|
||||
task.id,
|
||||
terminal.handle,
|
||||
paneKey,
|
||||
'launch-hash', // A dispatch recorded against a process that has since been replaced.
|
||||
opts?.breakIdentity === true ? `${incarnation}:replaced` : incarnation
|
||||
)
|
||||
|
||||
const method = workerShowMethod()
|
||||
return method.handler(method.params?.parse({ dispatch: dispatch.id }), { runtime })
|
||||
}
|
||||
|
||||
it('names the pending prompt on the observation a coordinator polls', async () => {
|
||||
const result = await showWorkerPaneServing(fixture('cursor-agent-approval-prompt'))
|
||||
|
||||
expect(result).toMatchObject({
|
||||
observation: {
|
||||
exactWorker: true,
|
||||
agentWait: {
|
||||
source: 'prompt-text',
|
||||
reason: 'agent-approval-prompt',
|
||||
since: expect.any(Number)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,232 +0,0 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { rmSync } from 'node:fs'
|
||||
import { DaemonPtyAdapter } from '../../../daemon/providers/adapter/daemon-pty-adapter'
|
||||
import { DaemonPtyRouter } from '../../../daemon/providers/routing/daemon-pty-router'
|
||||
import { DegradedDaemonPtyProvider } from '../../../daemon/providers/routing/degraded-daemon-pty-provider'
|
||||
import { LocalPtyProvider } from '../../../providers/local-pty/local-pty-provider'
|
||||
import {
|
||||
createMockSubprocess,
|
||||
startDaemonAdapterHarness,
|
||||
waitFor
|
||||
} from '../../../daemon/providers/adapter/testing/daemon-pty-adapter-test-harness'
|
||||
import type { IPtyProvider } from '../../../providers/pty/provider-contract'
|
||||
import { PtyWriteUnavailableError } from '../../../providers/pty/write-unavailable-error'
|
||||
|
||||
await import('../../orca-runtime-tests/runtime-test-module-registration.spec')
|
||||
await import('../../orca-runtime-tests/orca-runtime-test-lifecycle.spec')
|
||||
await import('../../orca-runtime-tests/runtime-test-module-registration.spec')
|
||||
const { OrcaRuntimeService } = await import('../../orca-runtime')
|
||||
const {
|
||||
HEADLESS_LEAF_ID,
|
||||
TEST_WORKTREE_ID,
|
||||
TEST_WORKTREE_PATH,
|
||||
makeRuntimeStoreWithWorkspaceSession,
|
||||
makeWorkspaceSessionWithHeadlessTerminal
|
||||
} = await import('../../orca-runtime-tests/orca-runtime-test-fixtures.spec')
|
||||
|
||||
const providerSession = { key: 'session_id' as const, id: 'codex-conversation' }
|
||||
|
||||
function mobileChat(provider: IPtyProvider) {
|
||||
const { runtimeStore } = makeRuntimeStoreWithWorkspaceSession(
|
||||
makeWorkspaceSessionWithHeadlessTerminal()
|
||||
)
|
||||
const runtime = new OrcaRuntimeService(runtimeStore)
|
||||
const spawn = vi.fn()
|
||||
const write = vi.fn(() => true)
|
||||
runtime.setPtyController({
|
||||
spawn,
|
||||
write,
|
||||
kill: () => true,
|
||||
getForegroundProcess: async () => null,
|
||||
hasPty: (id) => provider.hasPty?.(id) ?? null,
|
||||
getPtyAvailability: (id) => provider.getPtyAvailability?.(id) ?? null,
|
||||
listProcesses: () => provider.listProcesses()
|
||||
})
|
||||
runtime.attachWindow(1)
|
||||
let snapshotVersion = 0
|
||||
const publish = (ptyId: string) =>
|
||||
runtime.syncWindowGraph(1, {
|
||||
tabs: [
|
||||
{
|
||||
tabId: 'host-tab',
|
||||
worktreeId: TEST_WORKTREE_ID,
|
||||
title: 'Codex',
|
||||
activeLeafId: HEADLESS_LEAF_ID,
|
||||
layout: null
|
||||
}
|
||||
],
|
||||
leaves: [
|
||||
{
|
||||
tabId: 'host-tab',
|
||||
worktreeId: TEST_WORKTREE_ID,
|
||||
leafId: HEADLESS_LEAF_ID,
|
||||
paneRuntimeId: 1,
|
||||
ptyId,
|
||||
paneTitle: 'Codex'
|
||||
}
|
||||
],
|
||||
mobileSessionTabs: [
|
||||
{
|
||||
worktree: TEST_WORKTREE_ID,
|
||||
publicationEpoch: 'renderer',
|
||||
snapshotVersion: ++snapshotVersion,
|
||||
activeGroupId: null,
|
||||
activeTabId: 'host-tab',
|
||||
activeTabType: 'terminal',
|
||||
tabs: [
|
||||
{
|
||||
type: 'terminal',
|
||||
id: `host-tab::${HEADLESS_LEAF_ID}`,
|
||||
parentTabId: 'host-tab',
|
||||
leafId: HEADLESS_LEAF_ID,
|
||||
ptyId,
|
||||
title: 'Codex',
|
||||
isActive: true,
|
||||
agentStatus: {
|
||||
agentType: 'codex',
|
||||
state: 'waiting',
|
||||
prompt: '',
|
||||
updatedAt: 1,
|
||||
stateStartedAt: 1,
|
||||
stateHistory: [],
|
||||
paneKey: `host-tab:${HEADLESS_LEAF_ID}`,
|
||||
providerSession
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
const read = () => runtime.listMobileSessionTabs(`id:${TEST_WORKTREE_ID}`)
|
||||
const published = () => runtime['getMobileSessionTabsForWorktree'](TEST_WORKTREE_ID)
|
||||
return { runtime, publish, read, published, spawn, write }
|
||||
}
|
||||
|
||||
it.each(['direct', 'router', 'degraded'] as const)(
|
||||
'keeps an unattached retained conversation ready, then withholds a gone PTY through %s',
|
||||
async (route) => {
|
||||
const subprocesses: ReturnType<typeof createMockSubprocess>[] = []
|
||||
const harness = await startDaemonAdapterHarness(() => {
|
||||
const subprocess = createMockSubprocess()
|
||||
subprocesses.push(subprocess)
|
||||
return subprocess
|
||||
})
|
||||
const owner = new DaemonPtyAdapter(harness)
|
||||
try {
|
||||
await harness.adapter.spawn({
|
||||
sessionId: 'persisted-pty',
|
||||
cwd: TEST_WORKTREE_PATH,
|
||||
cols: 80,
|
||||
rows: 24
|
||||
})
|
||||
harness.adapter.dispose()
|
||||
const provider =
|
||||
route === 'direct'
|
||||
? owner
|
||||
: route === 'router'
|
||||
? new DaemonPtyRouter({ current: owner, legacy: [] })
|
||||
: new DegradedDaemonPtyProvider({
|
||||
current: owner,
|
||||
legacy: [],
|
||||
fallback: new LocalPtyProvider()
|
||||
})
|
||||
const chat = mobileChat(provider)
|
||||
chat.publish('persisted-pty')
|
||||
const before = await chat.read()
|
||||
expect(owner.hasPty('persisted-pty')).toBe(false)
|
||||
expect(before.tabs[0]).toMatchObject({ status: 'ready', terminal: expect.any(String) })
|
||||
const original = before.tabs[0]
|
||||
if (original?.type !== 'terminal') {
|
||||
throw new Error('Expected the retained terminal')
|
||||
}
|
||||
|
||||
await owner.attach('persisted-pty')
|
||||
expect((await chat.read()).tabs[0]).toHaveProperty('terminal', original.terminal)
|
||||
expect(subprocesses).toHaveLength(1)
|
||||
subprocesses[0]?._simulateExit(0)
|
||||
await waitFor(() => !owner.hasPty('persisted-pty'))
|
||||
expect((await chat.read()).tabs[0]).toMatchObject({
|
||||
status: 'pending-handle',
|
||||
terminal: null
|
||||
})
|
||||
chat.publish('persisted-pty')
|
||||
expect(chat.published().tabs[0]).toMatchObject({ status: 'pending-handle', terminal: null })
|
||||
|
||||
await owner.spawn({
|
||||
sessionId: 'replacement-pty',
|
||||
cwd: TEST_WORKTREE_PATH,
|
||||
cols: 80,
|
||||
rows: 24
|
||||
})
|
||||
chat.publish('replacement-pty')
|
||||
const restored = (await chat.read()).tabs[0]
|
||||
expect(restored).toMatchObject({
|
||||
id: original.id,
|
||||
status: 'ready',
|
||||
terminal: expect.any(String),
|
||||
agentStatus: expect.objectContaining({ providerSession })
|
||||
})
|
||||
expect(restored).not.toHaveProperty('terminal', original.terminal)
|
||||
expect(chat.spawn).not.toHaveBeenCalled()
|
||||
expect(chat.write).not.toHaveBeenCalled()
|
||||
expect(subprocesses.every((subprocess) => subprocess.write.mock.calls.length === 0)).toBe(
|
||||
true
|
||||
)
|
||||
} finally {
|
||||
owner.dispose()
|
||||
harness.adapter.dispose()
|
||||
await harness.server.shutdown()
|
||||
rmSync(harness.dir, { recursive: true, force: true })
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
it('withholds an attached PTY during endpoint recovery and restores its same conversation without replay', async () => {
|
||||
const subprocess = createMockSubprocess()
|
||||
const harness = await startDaemonAdapterHarness(() => subprocess)
|
||||
const respawn = vi.fn(async () => {})
|
||||
const owner = new DaemonPtyAdapter({ ...harness, respawn })
|
||||
try {
|
||||
await owner.spawn({ sessionId: 'persisted-pty', cwd: TEST_WORKTREE_PATH, cols: 80, rows: 24 })
|
||||
const chat = mobileChat(owner)
|
||||
chat.publish('persisted-pty')
|
||||
const original = (await chat.read()).tabs[0]
|
||||
if (original?.type !== 'terminal') {
|
||||
throw new Error('Expected the attached terminal')
|
||||
}
|
||||
expect(original.status).toBe('ready')
|
||||
const unavailable = new Promise<void>((resolve) => owner.onWriteUnavailable(() => resolve()))
|
||||
owner['client'].disconnect()
|
||||
expect(() => owner.write('persisted-pty', 'refused')).toThrow(PtyWriteUnavailableError)
|
||||
await unavailable
|
||||
expect(owner.hasPty('persisted-pty')).toBe(true)
|
||||
expect(chat.published().tabs[0]).toMatchObject({ status: 'pending-handle', terminal: null })
|
||||
|
||||
// Successful inventory reconnects transport but does not clear the remount fence.
|
||||
expect((await chat.read()).tabs[0]).toMatchObject({ status: 'pending-handle', terminal: null })
|
||||
await owner.attach('persisted-pty')
|
||||
expect((await chat.read()).tabs[0]).toMatchObject({
|
||||
id: original.id,
|
||||
status: 'ready',
|
||||
terminal: original.terminal,
|
||||
agentStatus: expect.objectContaining({ providerSession })
|
||||
})
|
||||
expect(subprocess.write).not.toHaveBeenCalled()
|
||||
expect(chat.spawn).not.toHaveBeenCalled()
|
||||
expect(chat.write).not.toHaveBeenCalled()
|
||||
expect(respawn).not.toHaveBeenCalled()
|
||||
|
||||
respawn.mockRejectedValue(new Error('daemon remains unavailable'))
|
||||
await harness.server.shutdown()
|
||||
await waitFor(() => owner.getDaemonIdentity() === null)
|
||||
expect((await chat.read()).tabs[0]).toMatchObject({ status: 'pending-handle', terminal: null })
|
||||
expect(owner.hasPty('persisted-pty')).toBe(true)
|
||||
expect(subprocess.write).not.toHaveBeenCalled()
|
||||
expect(chat.spawn).not.toHaveBeenCalled()
|
||||
} finally {
|
||||
owner.dispose()
|
||||
harness.adapter.dispose()
|
||||
await harness.server.shutdown()
|
||||
rmSync(harness.dir, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
@@ -1,93 +0,0 @@
|
||||
import { describe, expect, it, vi } from 'vitest'
|
||||
import type { IPtyProvider } from '../../../providers/pty/provider-contract'
|
||||
import type { Repo } from '../../../../shared/repo-types'
|
||||
import type { OrcaRuntimeService } from '../../orca-runtime'
|
||||
import { stopMissingWorktreeTerminals } from './missing-worktree-terminal-reconciliation'
|
||||
import { createAdapter } from '../../../daemon/providers/routing/testing/daemon-pty-router-test-fixture'
|
||||
import { DaemonPtyRouter } from '../../../daemon/providers/routing/daemon-pty-router'
|
||||
|
||||
function createProvider(sessionIds: string[]): IPtyProvider {
|
||||
return {
|
||||
listProcesses: vi.fn(async () =>
|
||||
sessionIds.map((id) => ({ id, cwd: '/workspace', title: 'shell' }))
|
||||
),
|
||||
shutdown: vi.fn(async () => {})
|
||||
} as unknown as IPtyProvider
|
||||
}
|
||||
|
||||
function createRuntime(): OrcaRuntimeService {
|
||||
return {
|
||||
stopTerminalsForWorktree: vi.fn(async () => ({ stopped: 0 }))
|
||||
} as unknown as OrcaRuntimeService
|
||||
}
|
||||
|
||||
const localRepo: Repo = {
|
||||
id: 'repo-1',
|
||||
path: '/workspace/repo',
|
||||
displayName: 'Repo',
|
||||
badgeColor: '#000',
|
||||
addedAt: 1
|
||||
}
|
||||
|
||||
describe('stopMissingWorktreeTerminals', () => {
|
||||
it('stops only locally owned worktrees absent from the authoritative scan', async () => {
|
||||
const deletedId = 'repo-1::/workspace/deleted'
|
||||
const survivingId = 'repo-1::/workspace/surviving'
|
||||
const provider = createProvider([
|
||||
`${deletedId}@@deleted-session`,
|
||||
`${survivingId}@@surviving-session`
|
||||
])
|
||||
const runtime = createRuntime()
|
||||
|
||||
const result = await stopMissingWorktreeTerminals(
|
||||
localRepo,
|
||||
[deletedId, survivingId, 'repo-2::/workspace/other'],
|
||||
[survivingId],
|
||||
{
|
||||
runtime,
|
||||
getLocalProvider: () => provider
|
||||
}
|
||||
)
|
||||
|
||||
expect(result).toEqual({ stoppedWorktreeIds: [deletedId] })
|
||||
expect(provider.shutdown).toHaveBeenCalledWith(
|
||||
`${deletedId}@@deleted-session`,
|
||||
expect.objectContaining({ immediate: true })
|
||||
)
|
||||
expect(provider.shutdown).not.toHaveBeenCalledWith(
|
||||
`${survivingId}@@surviving-session`,
|
||||
expect.anything()
|
||||
)
|
||||
})
|
||||
|
||||
it.each([false, true])(
|
||||
'stops multiple workspaces through scoped daemon listings (unrelated duplicate: %s)',
|
||||
async (duplicate) => {
|
||||
const ids = ['repo-1::/workspace/a', 'repo-1::/workspace/b']
|
||||
const targetSessions = ids.map((id) => `${id}@@session`)
|
||||
const currentSessions = [...targetSessions, ...(duplicate ? ['unrelated'] : [])]
|
||||
const current = createAdapter('current', currentSessions)
|
||||
const legacy = createAdapter('legacy', duplicate ? ['unrelated'] : [])
|
||||
const provider = new DaemonPtyRouter({ current, legacy: [legacy] })
|
||||
|
||||
await expect(
|
||||
stopMissingWorktreeTerminals(localRepo, ids, [], {
|
||||
runtime: createRuntime(),
|
||||
getLocalProvider: () => provider
|
||||
})
|
||||
).resolves.toEqual({ stoppedWorktreeIds: ids })
|
||||
|
||||
expect(currentSessions).toEqual(duplicate ? ['unrelated'] : [])
|
||||
expect(
|
||||
vi
|
||||
.mocked(current.shutdown)
|
||||
.mock.calls.map(([id]) => id)
|
||||
.sort()
|
||||
).toEqual(targetSessions)
|
||||
expect(legacy.shutdown).not.toHaveBeenCalled()
|
||||
if (!duplicate) {
|
||||
expect(current.listProcesses).toHaveBeenCalledOnce()
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user