refactor(runtime): check workspace host, file and persistence boundaries (#27945)

This commit is contained in:
Neil
2026-10-14 22:23:57 -07:00
committed by GitHub
parent 21ba1c399d
commit 0ec009a877
53 changed files with 1422 additions and 199 deletions
+1 -1
View File
@@ -58,7 +58,7 @@
2 src/main/runtime/orca-runtime-build-headless-mobile-session-browser-tabs.ts
1 src/main/runtime/session-tabs/structured-session-tab-close.ts
1 src/main/runtime/orca-runtime-get-runtime-id.ts
2 src/main/runtime/orca-runtime-persist-headless-terminal-title.ts
2 src/main/runtime/workspaces/workspace-file-session-targets.ts
1 src/main/runtime/session-tabs/terminal-surface-retirements.ts
1 src/main/runtime/client/runtime-client-state.ts
3 src/main/runtime/terminal/stop/worktree-terminal-stop.ts
@@ -144,6 +144,7 @@ describe('pr-e2e-layout-oracle-routing', () => {
'src/renderer/src/runtime/sync-runtime-graph.ts',
'src/renderer/src/runtime/web-session-tabs-sync/store-patch.ts',
'src/main/runtime/terminal/launch/terminal-split.ts',
'src/main/runtime/workspaces/workspace-file-session-targets.ts',
'src/main/runtime/session-graph/session-window-graph.ts',
'src/main/runtime/session-tabs/mobile-session-hydration.ts',
'src/main/runtime/session-tabs/mobile-session-reconciliation.ts',
+4 -3
View File
@@ -65,11 +65,12 @@ const LAYOUT_ORACLE_PREFIXES = [
'config/scripts/pr-layout-oracle-route.mjs'
]
const LAUNCH_LAYOUT_WRITERS = [
const RUNTIME_LAYOUT_WRITERS = [
'src/main/runtime/terminal/launch/terminal-create.ts',
'src/main/runtime/terminal/launch/terminal-split.ts',
'src/main/runtime/terminal/launch/terminal-split-surface.ts',
'src/main/runtime/terminal/stop/worktree-terminal-stop.ts'
'src/main/runtime/terminal/stop/worktree-terminal-stop.ts',
'src/main/runtime/workspaces/workspace-file-session-targets.ts'
]
// Runtime matches only the modules that create, move, close, persist or publish layout.
@@ -88,7 +89,7 @@ export function isLayoutOracleSource(file) {
return false
}
return (
LAUNCH_LAYOUT_WRITERS.includes(file) ||
RUNTIME_LAYOUT_WRITERS.includes(file) ||
LAYOUT_ORACLE_PREFIXES.some((prefix) => file.startsWith(prefix)) ||
LAYOUT_ORACLE_RUNTIME_MODULE.test(file)
)
-7
View File
@@ -14,24 +14,17 @@ src/main/runtime/orca-runtime-fit-override-listeners.ts
src/main/runtime/orca-runtime-focus-terminal.ts
src/main/runtime/orca-runtime-get-agent-session-execution-namespace.ts
src/main/runtime/orca-runtime-get-runtime-id.ts
src/main/runtime/orca-runtime-get-worktree-ps.ts
src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts
src/main/runtime/orca-runtime-has-live-or-persisted-serve-or-ssh-owned-pty-binding.ts
src/main/runtime/orca-runtime-linear-commands.ts
src/main/runtime/orca-runtime-list-managed-worktrees.ts
src/main/runtime/orca-runtime-notify-ssh-state-changed.ts
src/main/runtime/orca-runtime-perform-mobile-session-pty-records-refresh.ts
src/main/runtime/orca-runtime-persist-headless-terminal-title.ts
src/main/runtime/orca-runtime-preserved-branch-cleanup.ts
src/main/runtime/orca-runtime-prune-mobile-session-tab-group-layout.ts
src/main/runtime/orca-runtime-publish-pty-backed-mobile-session-terminal.ts
src/main/runtime/orca-runtime-reconcile-headless-mobile-session-browser-tabs.ts
src/main/runtime/orca-runtime-refuse-unattributed-mobile-session-tab-close.ts
src/main/runtime/orca-runtime-resolve-known-workspace-file-target.ts
src/main/runtime/orca-runtime-resolve-terminal-pane.ts
src/main/runtime/orca-runtime-resolve-terminal-split-source-authority.ts
src/main/runtime/orca-runtime-runtime-id.ts
src/main/runtime/orca-runtime-schedule-mobile-session-tabs-changed.ts
src/main/runtime/orca-runtime-state-fields.ts
src/main/runtime/orca-runtime-stop-requested-pty-ids.ts
src/main/runtime/orca-runtime-terminal-drivers.ts
@@ -44,7 +44,9 @@ export type FirstWorkBranchRenameEvent = {
}
export type FirstWorkBranchRenameDeps = {
getSettings: () => GlobalSettings
getSettings: () => Parameters<typeof resolveTextGenerationParams>[0] &
Parameters<typeof getConfiguredBranchPrefix>[0] &
Partial<Pick<GlobalSettings, 'autoRenameBranchFromWork'>>
getRepo: (repoId: string) => Repo | undefined
getAgentEnvResolvers: () => CommitMessageAgentEnvironmentResolvers | undefined
/** Current sidebar display name for the worktree, if one is stored. */
@@ -5,7 +5,6 @@
// worktree identity so meta, tabs, and the live PTY session carry over. It is
// best-effort and local-only — remote/Windows/locked/dest-taken all degrade to
// "folder kept" without disturbing the rename that already succeeded.
import type { GlobalSettings } from '../../shared/global-settings-types'
import type { Repo } from '../../shared/repo-types'
import {
FOLDER_WORKSPACE_INSTANCE_SEPARATOR,
@@ -17,7 +16,7 @@ import { planWorktreeFolderRename } from '../ipc/worktree-folder-rename-target'
export type FirstWorkFolderRenameDeps = {
getRepo: (repoId: string) => Repo | undefined
getSettings: () => GlobalSettings
getSettings: () => Parameters<typeof planWorktreeFolderRename>[0]['settings']
/** Re-key all worktreeId-keyed state from the old (path-derived) id to the new. */
migrateWorktreeIdentity: (oldWorktreeId: string, newWorktreeId: string) => void
/** Invalidate caches + tell the renderer the worktree's id changed (old->new) so
@@ -7,12 +7,22 @@ import { renameWorktreeFolderOnFirstWork } from './first-work-folder-rename'
import { moveWorktree } from '../git/worktree'
import { isWslMoveFolderWorkspaceBlocked } from '../persistence/moving-wsl-projects/wsl-move-background-backoff'
import type { Store } from '../persistence'
import type { RuntimeStore } from '../runtime/runtime-store-contract'
import type { OrcaRuntimeService } from '../runtime/orca-runtime'
const ENABLE_FIRST_WORK_FOLDER_RENAME = false
export function firstWorkRenameDeps(
store: Store,
store: Pick<
RuntimeStore,
| 'getSettings'
| 'getRepo'
| 'getWorktreeMeta'
| 'setWorktreeMeta'
| 'getFolderWorkspace'
| 'updateFolderWorkspace'
> &
Partial<Pick<Store, 'migrateWorktreeIdentity' | 'getWorktreeIdForTab'>>,
runtime: Pick<
OrcaRuntimeService,
| 'getCommitMessageAgentEnvironmentResolvers'
@@ -28,25 +38,26 @@ export function firstWorkRenameDeps(
getCurrentDisplayName: (worktreeId) => {
const scope = parseWorkspaceKey(worktreeId)
return scope?.type === 'folder'
? store.getFolderWorkspace(scope.folderWorkspaceId)?.name
? store.getFolderWorkspace!(scope.folderWorkspaceId)?.name
: store.getWorktreeMeta(worktreeId)?.displayName
},
getFolderWorkspacePath: (worktreeId) => {
const scope = parseWorkspaceKey(worktreeId)
return scope?.type === 'folder'
? store.getFolderWorkspace(scope.folderWorkspaceId)?.folderPath
? store.getFolderWorkspace!(scope.folderWorkspaceId)?.folderPath
: undefined
},
isFolderWorkspaceWslMoveBlocked: (worktreeId) => {
const scope = parseWorkspaceKey(worktreeId)
const folder =
scope?.type === 'folder' ? store.getFolderWorkspace(scope.folderWorkspaceId) : undefined
scope?.type === 'folder' ? store.getFolderWorkspace!(scope.folderWorkspaceId) : undefined
return !!folder && isWslMoveFolderWorkspaceBlocked(folder)
},
isPendingFirstAgentMessageRename: (worktreeId) => {
const scope = parseWorkspaceKey(worktreeId)
return scope?.type === 'folder'
? store.getFolderWorkspace(scope.folderWorkspaceId)?.pendingFirstAgentMessageRename === true
? store.getFolderWorkspace!(scope.folderWorkspaceId)?.pendingFirstAgentMessageRename ===
true
: store.getWorktreeMeta(worktreeId)?.pendingFirstAgentMessageRename === true
},
canRenameOrcaCreatedBranch: (worktreeId) => {
@@ -58,7 +69,7 @@ export function firstWorkRenameDeps(
rememberBranchRenameFailureOutput(worktreeId, null)
const scope = parseWorkspaceKey(worktreeId)
if (scope?.type === 'folder') {
store.updateFolderWorkspace(scope.folderWorkspaceId, {
store.updateFolderWorkspace!(scope.folderWorkspaceId, {
name: displayName,
pendingFirstAgentMessageRename: false,
firstAgentMessageRenameError: null
@@ -81,7 +92,7 @@ export function firstWorkRenameDeps(
renameWorktreeFolderOnFirstWork(worktreeId, newLeaf, {
getRepo: (repoId) => store.getRepo(repoId),
getSettings: () => store.getSettings(),
migrateWorktreeIdentity: (oldId, newId) => store.migrateWorktreeIdentity(oldId, newId),
migrateWorktreeIdentity: (oldId, newId) => store.migrateWorktreeIdentity!(oldId, newId),
notifyWorktreeRenamed: (repoId, oldId, newId) =>
runtime.notifyWorktreeFolderRenamed(repoId, oldId, newId),
pathExists: async (candidate) => existsSync(candidate),
@@ -94,13 +105,13 @@ export function firstWorkRenameDeps(
// Skip the write + push when unchanged — most settled worktrees never had an error to clear.
const scope = parseWorkspaceKey(worktreeId)
if (scope?.type === 'folder') {
const current = store.getFolderWorkspace(
const current = store.getFolderWorkspace!(
scope.folderWorkspaceId
)?.firstAgentMessageRenameError
if ((current ?? null) === (error ?? null)) {
return
}
store.updateFolderWorkspace(scope.folderWorkspaceId, {
store.updateFolderWorkspace!(scope.folderWorkspaceId, {
firstAgentMessageRenameError: error
})
runtime.notifyFolderWorkspaceChanged()
@@ -114,7 +125,7 @@ export function firstWorkRenameDeps(
// Why: the hook only knows the worktreeId, so derive the repoId notifyBranchRenamed expects.
runtime.notifyBranchRenamed(getRepoIdFromWorktreeId(worktreeId))
},
resolveWorktreeIdForTab: (tabId) => store.getWorktreeIdForTab(tabId),
resolveWorktreeIdForTab: (tabId) => store.getWorktreeIdForTab!(tabId),
onRenamed: (repoIdOrWorktreeId) => {
if (parseWorkspaceKey(repoIdOrWorktreeId)?.type === 'folder') {
runtime.notifyFolderWorkspaceChanged()
@@ -23,11 +23,13 @@ export type ClaudeStructuredAuthPolicy = {
* selection, not the platform default target, owns its auth.
*/
export function claudeStructuredAuthPolicyForSettings(
settings: Pick<
GlobalSettings,
| 'claudeManagedAccounts'
| 'activeClaudeManagedAccountId'
| 'activeClaudeManagedAccountIdsByRuntime'
settings: Partial<
Pick<
GlobalSettings,
| 'claudeManagedAccounts'
| 'activeClaudeManagedAccountId'
| 'activeClaudeManagedAccountIdsByRuntime'
>
>
): ClaudeStructuredAuthPolicy {
// Why the router first: it decides whether the account or System default runs, as for terminals.
+11 -1
View File
@@ -24,7 +24,17 @@ export function applyClaudeEnvPatch(
baseEnv: Record<string, string>,
patch: ClaudeEnvPatch,
options?: { stripAuthEnv?: boolean; platform?: NodeJS.Platform }
): Record<string, string> {
): Record<string, string>
export function applyClaudeEnvPatch(
baseEnv: Record<string, string | undefined>,
patch: ClaudeEnvPatch,
options?: { stripAuthEnv?: boolean; platform?: NodeJS.Platform }
): Record<string, string | undefined>
export function applyClaudeEnvPatch(
baseEnv: Record<string, string | undefined>,
patch: ClaudeEnvPatch,
options?: { stripAuthEnv?: boolean; platform?: NodeJS.Platform }
): Record<string, string | undefined> {
if (options?.stripAuthEnv) {
for (const key of CLAUDE_AUTH_ENV_VARS) {
delete baseEnv[key]
@@ -27,9 +27,8 @@ export function normalizeClaudeAccountSelectionTarget(
}
export function normalizeClaudeRuntimeSelection(
settings: Pick<
GlobalSettings,
'activeClaudeManagedAccountId' | 'activeClaudeManagedAccountIdsByRuntime'
settings: Partial<
Pick<GlobalSettings, 'activeClaudeManagedAccountId' | 'activeClaudeManagedAccountIdsByRuntime'>
>
): ClaudeManagedAccountRuntimeSelection {
return {
@@ -42,9 +41,8 @@ export function normalizeClaudeRuntimeSelection(
}
export function getSelectedClaudeAccountIdForTarget(
settings: Pick<
GlobalSettings,
'activeClaudeManagedAccountId' | 'activeClaudeManagedAccountIdsByRuntime'
settings: Partial<
Pick<GlobalSettings, 'activeClaudeManagedAccountId' | 'activeClaudeManagedAccountIdsByRuntime'>
>,
target?: ClaudeAccountSelectionTarget | null
): string | null {
+2 -3
View File
@@ -22,9 +22,8 @@ export type {
} from '../../shared/codex-selection-lane'
export function normalizeCodexRuntimeSelection(
settings: Pick<
GlobalSettings,
'activeCodexManagedAccountId' | 'activeCodexManagedAccountIdsByRuntime'
settings: Partial<
Pick<GlobalSettings, 'activeCodexManagedAccountId' | 'activeCodexManagedAccountIdsByRuntime'>
>
): CodexManagedAccountRuntimeSelection {
return {
+2 -2
View File
@@ -17,7 +17,7 @@ import {
* check-ref-format rejects.
*/
export function getConfiguredBranchPrefix(
settings: BranchPrefixSettings,
settings: Parameters<typeof selectBranchPrefixInput>[0],
gitUsername: string | null
): string | null {
const raw = selectBranchPrefixInput(settings, gitUsername)
@@ -29,7 +29,7 @@ export function getConfiguredBranchPrefix(
*/
export function computeBranchName(
sanitizedName: string,
settings: BranchPrefixSettings,
settings: Parameters<typeof selectBranchPrefixInput>[0],
gitUsername: string | null
): string {
const prefix = getConfiguredBranchPrefix(settings, gitUsername)
@@ -45,3 +45,19 @@ describe('structured chat configured Arguments', () => {
).toThrow('Arguments are invalid')
})
})
describe('future structured provider argument lookup', () => {
it('keeps configured arguments and empty fallback for an unregistered provider', () => {
const agentDefaultArgs = { claude: '', 'future-provider': '--label "future value"' }
expect(structuredAgentConfiguredArgs('future-provider', { agentDefaultArgs })).toEqual([
'--label',
'future value'
])
expect(structuredAgentConfiguredArgs('future-provider', {})).toEqual([])
})
it('retains the existing inherited default-map lookup behavior', () => {
expect(structuredAgentConfiguredArgs('constructor', {})).toEqual(['undefined'])
expect(structuredAgentConfiguredArgs('toString', {})).toEqual([])
})
})
@@ -1,4 +1,3 @@
import type { TuiAgent } from '../../shared/tui-agent'
import type { GlobalSettings } from '../../shared/global-settings-types'
import { resolveTuiAgentLaunchArgs } from '../../shared/tui-agent-launch-defaults'
import { tokenizeStartupCommand } from '../../shared/tui-agent-startup-shell'
@@ -6,7 +5,7 @@ import { resolveLocalWindowsAgentStartupShell } from '../../shared/windows-termi
/** Use the same grouping rules as terminal launches before the provider filters its owned flags. */
export function structuredAgentConfiguredArgs(
agent: TuiAgent,
agent: string,
settings: Partial<Pick<GlobalSettings, 'agentDefaultArgs' | 'terminalWindowsShell'>>,
platform: NodeJS.Platform = process.platform
): string[] {
@@ -2,8 +2,7 @@ import type { IPtyProvider } from '../providers/pty-provider-contract'
import type { Repo } from '../../shared/repo-types'
import { splitWorktreeId } from '../../shared/worktree/id'
import { mapWithConcurrency } from '../../shared/map-with-concurrency'
import type { OrcaRuntimeService } from './orca-runtime'
import { killAllProcessesForWorktree } from './worktree-teardown'
import { killAllProcessesForWorktree, type WorktreeTeardownRuntime } from './worktree-teardown'
const MISSING_WORKTREE_TEARDOWN_CONCURRENCY = 4
@@ -60,7 +59,7 @@ function hostFence(
}
type MissingWorktreeTerminalReconciliationDeps = {
runtime: OrcaRuntimeService
runtime: WorktreeTeardownRuntime
getLocalProvider: () => IPtyProvider | null
onPtyStopped?: (ptyId: string) => void
}
@@ -1,6 +1,6 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { selectFreshAgentRowForMobileTab } from './runtime-hook-agent-row-selection'
import { OrcaRuntimeWithScheduleMobileSessionTabsChanged } from './orca-runtime-schedule-mobile-session-tabs-changed'
import { OrcaRuntimeWithScheduleMobileSessionTabsChanged } from './session-tabs/session-tabs-change-publication'
import type { TabGroupLayoutNode } from '../../shared/tab-types'
import type {
RuntimeMobileSessionSnapshotTab,
@@ -781,3 +781,19 @@ describe('launch runtime dispatch and allocation phases', () => {
}
})
})
describe('workspace-create-idempotency characterization', () => {
describe('worktree mutation promise identity', () => {
it('retains the original promise for a reused key even when callers violate its result-type precondition', async () => {
const runtime = new OrcaRuntimeService(null)
const created = Promise.resolve(42)
const later = vi.fn(() => Promise.resolve('different result'))
const first = runtime.dedupeWorktreeCreate('repo', 'agent.launch:mutation', () => created)
const reused = runtime.dedupeWorktreeCreate('repo', 'agent.launch:mutation', later)
expect(first).toBe(created)
expect(reused).toBe(first)
expect(later).not.toHaveBeenCalled()
await expect(reused).resolves.toBe(42)
})
})
})
@@ -1,6 +1,6 @@
import './rpc/unused-default-rpc-methods.test-fixture'
import { afterEach, describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeWithListManagedWorktrees } from './orca-runtime-list-managed-worktrees'
import { OrcaRuntimeWithListManagedWorktrees } from './workspaces/managed-workspace-operations'
const scanWorkspacePortsMock = vi.hoisted(() => vi.fn())
@@ -35,13 +35,7 @@ import { releaseRetriedWorkerTerminal } from './retried-worker-terminal-release'
import { requireWorkerAuthority, type WorkerEffect } from './worker-topology'
import { prepareLocalWorkerStart } from './worker-start-validation'
import { deliverAndSettleWorkerStartReadiness } from './worker-start-readiness-settlement'
type WorkerStartMutation = {
callerFingerprint: string
requestId: string
method: string
payloadHash: string
}
import type { WorkerStartMutation } from './worker-start-mutation'
export async function startLocalWorker(args: {
params: WorkerStartInput
@@ -114,11 +108,12 @@ export async function startLocalWorker(args: {
existingPlacement: 'current or an exact existing folder workspace'
})
}
let resolvedWorktree = creationWorktree
? undefined
: requestedWorktree === 'current'
? await runtime.showManagedTerminalWorkspace(`id:${coordinatorWorktreeId}`)
: await runtime.showManagedTerminalWorkspace(requestedWorktree)
let resolvedWorktree: Awaited<ReturnType<typeof placeWorkerAgent>>['worktree'] | undefined =
creationWorktree
? undefined
: requestedWorktree === 'current'
? await runtime.showManagedTerminalWorkspace(`id:${coordinatorWorktreeId}`)
: await runtime.showManagedTerminalWorkspace(requestedWorktree)
if (terminal) {
await assertExplicitWorkerUsable({
runtime,
@@ -0,0 +1,6 @@
export type WorkerStartMutation = {
callerFingerprint: string
requestId: string
method: string
payloadHash: string
}
@@ -5,7 +5,7 @@ import { Store } from '../persistence/loading-store/store'
import { ProfileStateSqliteAuthority } from '../persistence/profile-state/profile-state-sqlite-authority'
import { RuntimeRemoteFetchController } from './runtime-remote-fetch-controller'
import type { RemoteTrackingBase } from './runtime-remote-fetch-controller'
import type { OrcaRuntimeWithNotifySshStateChanged } from './orca-runtime-notify-ssh-state-changed'
import type { OrcaRuntimeWithNotifySshStateChanged } from './workspaces/workspace-host-notifications'
import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types'
import type { PreparationRearmHolder } from '../worktree-create-preparation'
import type { RuntimeWorkspaceCreateEvents } from '../workspace-create-telemetry'
@@ -89,6 +89,14 @@ export type RuntimeStore = {
getMobileClientTabSelections?: Store['getMobileClientTabSelections']
setMobileClientTabSelections?: Store['setMobileClientTabSelections']
getSettings(): {
autoRenameBranchFromWork?: GlobalSettings['autoRenameBranchFromWork']
nativeChatAutoName?: GlobalSettings['nativeChatAutoName']
notifications?: GlobalSettings['notifications']
claudeManagedAccounts?: GlobalSettings['claudeManagedAccounts']
activeClaudeManagedAccountId?: GlobalSettings['activeClaudeManagedAccountId']
activeClaudeManagedAccountIdsByRuntime?: GlobalSettings['activeClaudeManagedAccountIdsByRuntime']
activeCodexManagedAccountId?: GlobalSettings['activeCodexManagedAccountId']
activeCodexManagedAccountIdsByRuntime?: GlobalSettings['activeCodexManagedAccountIdsByRuntime']
agentWorkspaceTrustEnabled?: GlobalSettings['agentWorkspaceTrustEnabled']
codexManagedAccounts?: GlobalSettings['codexManagedAccounts']
nestedWorkerMaxDepth?: GlobalSettings['nestedWorkerMaxDepth']
@@ -9,9 +9,9 @@ import type { OrcaRuntimeWithRecordPtyWorktree as RecordPtyWorktreeMethods } fro
import type { RuntimeSessionTabsInventoryPublication as SessionTabsInventoryPublicationMethods } from '../session-tabs/session-tabs-inventory-publication'
import type { OrcaRuntimeWithRefreshFloatingWorkspacePtyLiveness as RefreshFloatingWorkspacePtyLivenessMethods } from '../terminal/lifecycle/floating-session-liveness'
import type { OrcaRuntimeWithTouchMobileSessionTabsForWorktree as TouchMobileSessionTabsForWorktreeMethods } from '../orca-runtime-touch-mobile-session-tabs-for-worktree'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../session-tabs/session-tabs-change-publication'
import type { OrcaRuntimeWithSyncMobileSessionTabs as SyncMobileSessionTabsMethods } from '../session-tabs/mobile-session-reconciliation'
import { OrcaRuntimeWithNotifySshStateChanged } from '../orca-runtime-notify-ssh-state-changed'
import { OrcaRuntimeWithNotifySshStateChanged } from '../workspaces/workspace-host-notifications'
import { HEADLESS_RUNTIME_WINDOW_ID } from '../../../shared/runtime-types'
import type { ExecutionHostId } from '../../../shared/execution-host'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
@@ -1,6 +1,10 @@
import { describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from './orca-runtime'
import { getSessionTabsInventoryPublication } from './session-tabs/session-tabs-inventory-test-fixture'
import type { RuntimeStore } from './runtime-store-contract'
import { getDefaultWorkspaceSession } from '../../shared/constants'
import type { WorkspaceSessionState } from '../../shared/workspace-session-state-types'
import type { DurableProfileStateMutation } from '../persistence/loading-store/store-runtime-state'
function createInventoryRuntime(): OrcaRuntimeService {
const runtime = new OrcaRuntimeService()
@@ -411,3 +415,409 @@ describe('authoritative session tab inventory publication', () => {
}
})
})
describe('workspace-persistence-construction characterization', () => {
type Runtime = InstanceType<typeof OrcaRuntimeService>
const WORKTREE_ID = 'repo::/workspace'
function constructRuntime(observe: (runtime: Runtime) => void): Runtime {
class ConstructorObservedRuntime extends OrcaRuntimeService {
protected override get canonicalFetchKeyCache(): ReadonlyMap<string, string> {
const cache = super.canonicalFetchKeyCache
observe(this)
return cache
}
}
return new ConstructorObservedRuntime(null)
}
function getNativeMethod(
runtime: Runtime,
name: string
): (this: Runtime, ...args: unknown[]) => unknown {
let prototype = Object.getPrototypeOf(runtime)
while (prototype) {
const method = Object.getOwnPropertyDescriptor(prototype, name)?.value
if (typeof method === 'function') {
return method
}
prototype = Object.getPrototypeOf(prototype)
}
throw new Error(`Missing runtime method: ${name}`)
}
describe('workspace persistence during full runtime construction', () => {
// The fetch-cache getter runs in the base constructor before later runtime fields initialize.
it('returns a native promise for absent active-workspace context', async () => {
let observed: Promise<unknown> | undefined
const observe = vi.fn((runtime: Runtime) => {
expect(runtime['store']).toBeNull()
observed = runtime.resolveActiveWorktreeContext()
expect(observed).toBeInstanceOf(Promise)
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
await expect(observed).resolves.toBeNull()
})
it('collects no public tab ids when no snapshot exists', () => {
const observe = vi.fn((runtime: Runtime) => {
const ids = runtime['collectPublicMobileSessionTabIds'](undefined)
expect(ids).toBeInstanceOf(Set)
expect(ids.size).toBe(0)
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
it('resolves no host tab id when no snapshot exists', () => {
const observe = vi.fn((runtime: Runtime) => {
expect(runtime['resolveMobileSessionHostTabId'](undefined, 'absent-tab')).toBeNull()
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
it('normalizes an empty group order without a snapshot', () => {
const observe = vi.fn((runtime: Runtime) => {
const order = runtime['normalizeMobileSessionTabOrder'](
undefined,
{ id: 'empty-group', activeTabId: null, tabOrder: [] },
[]
)
expect(order).toEqual([])
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
it('leaves a title unchanged when persistence is unavailable', () => {
const observe = vi.fn((runtime: Runtime) => {
expect(runtime['store']).toBeNull()
expect(runtime['persistHeadlessTerminalTitle'](WORKTREE_ID, 'absent-tab', 'Title')).toBe(
undefined
)
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
it('finds no split source in an empty runtime', () => {
const observe = vi.fn((runtime: Runtime) => {
expect(
runtime['resolveTerminalSplitSourceAuthority'](
WORKTREE_ID,
'absent-tab',
'absent-leaf',
'absent-pty'
)
).toBeNull()
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
it('rejects destructive removal asynchronously when no local provider exists', async () => {
let observed: Promise<unknown> | undefined
const observe = vi.fn((runtime: Runtime) => {
observed = runtime['stopPtysForDestructiveWorktreeRemoval'](WORKTREE_ID)
expect(observed).toBeInstanceOf(Promise)
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
await expect(observed).rejects.toThrow(
`PTY provider unavailable for worktree deletion: ${WORKTREE_ID}`
)
})
it('rejects resume-record clearing asynchronously when persistence is unavailable', async () => {
let observed: Promise<unknown> | undefined
const observe = vi.fn((runtime: Runtime) => {
const clear = getNativeMethod(runtime, 'clearWorktreeTerminalResumeRecords')
const result = clear.call(runtime, WORKTREE_ID, 'local', ['absent-tab'])
expect(result).toBeInstanceOf(Promise)
if (!(result instanceof Promise)) {
throw new Error('Resume clearing must return a native promise')
}
observed = result
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
await expect(observed).rejects.toThrow('workspace_session_unavailable')
})
it('keeps the root mutation map and pending promise after construction finishes', async () => {
const created = Promise.resolve('created')
const run = vi.fn(() => created)
let mapDuringConstruction: unknown
let promiseDuringConstruction: Promise<string> | undefined
const observe = vi.fn((runtime: Runtime) => {
mapDuringConstruction = runtime['worktreeCreateByMutationId']
expect(mapDuringConstruction).toBeInstanceOf(Map)
expect(Object.getOwnPropertyDescriptor(runtime, 'worktreeCreateByMutationId')).toEqual({
value: mapDuringConstruction,
writable: true,
enumerable: true,
configurable: true
})
promiseDuringConstruction = runtime.dedupeWorktreeCreate('repo', 'mutation', run)
expect(promiseDuringConstruction).toBe(created)
expect(runtime['worktreeCreateByMutationId'].get('repo\0mutation')).toBe(created)
})
const runtime = constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
expect(runtime['worktreeCreateByMutationId']).toBe(mapDuringConstruction)
expect(runtime['worktreeCreateByMutationId'].get('repo\0mutation')).toBe(created)
const duplicate = vi.fn(() => Promise.resolve('duplicate'))
expect(runtime.dedupeWorktreeCreate('repo', 'mutation', duplicate)).toBe(created)
expect(run).toHaveBeenCalledOnce()
expect(duplicate).not.toHaveBeenCalled()
await expect(promiseDuringConstruction).resolves.toBe('created')
})
it('removes metadata through the narrow store capabilities during construction', () => {
const worktreeId = 'constructor-metadata::/workspace'
const removeWorktreeMeta = vi.fn()
const store: Pick<RuntimeStore, 'getWorktreeMeta' | 'removeWorktreeMeta' | 'getRepos'> = {
getWorktreeMeta: () => undefined,
removeWorktreeMeta,
getRepos: () => []
}
let removedMapDuringConstruction: unknown
let snapshotsDuringConstruction: unknown
let acceptedSnapshotsDuringConstruction: unknown
const observe = vi.fn((runtime: Runtime) => {
expect(runtime['store']).toBeNull()
removedMapDuringConstruction = runtime['removedMobileSessionWorktreeIds']
snapshotsDuringConstruction = runtime['mobileSessionTabsByWorktree']
acceptedSnapshotsDuringConstruction = runtime['acceptedRendererMobileSnapshotByWorktree']
expect(Object.getOwnPropertyDescriptor(runtime, 'removedMobileSessionWorktreeIds')).toEqual(
{
value: removedMapDuringConstruction,
writable: true,
enumerable: true,
configurable: true
}
)
const remove = getNativeMethod(runtime, 'removeWorktreeMetadataAndHistory')
expect(remove.call(runtime, store, worktreeId, 'local')).toBeUndefined()
expect(runtime['removedMobileSessionWorktreeIds'].get(worktreeId)).toEqual({})
expect(removeWorktreeMeta).toHaveBeenCalledExactlyOnceWith(worktreeId, 'local')
})
const runtime = constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
expect(runtime['removedMobileSessionWorktreeIds']).toBe(removedMapDuringConstruction)
expect(runtime['mobileSessionTabsByWorktree']).toBe(snapshotsDuringConstruction)
expect(runtime['acceptedRendererMobileSnapshotByWorktree']).toBe(
acceptedSnapshotsDuringConstruction
)
expect(runtime['removedMobileSessionWorktreeIds'].get(worktreeId)).toEqual({})
expect(runtime['mobileSessionTabsByWorktree'].has(worktreeId)).toBe(false)
expect(runtime['acceptedRendererMobileSnapshotByWorktree'].has(worktreeId)).toBe(false)
})
it('preserves the native metadata-removal failure for an invalid null-store call', () => {
const observe = vi.fn((runtime: Runtime) => {
const remove = getNativeMethod(runtime, 'removeWorktreeMetadataAndHistory')
// This intentionally unsupported input distinguishes body execution from a missing late owner.
expect(() => remove.call(runtime, null, WORKTREE_ID)).toThrow(
new TypeError(
process.versions.bun
? "null is not an object (evaluating 'store.getWorktreeMeta')"
: "Cannot read properties of null (reading 'getWorktreeMeta')"
)
)
})
constructRuntime(observe)
expect(observe).toHaveBeenCalledOnce()
})
})
})
describe('workspace-terminal-resume-records characterization', () => {
type ResumeStore = Pick<
RuntimeStore,
'getWorkspaceSession' | 'setWorkspaceSession' | 'runDurableMutation'
>
const WORKTREE_ID = 'repo::/workspace'
const HOST_ID = 'runtime:original-partition'
function clear(runtime: InstanceType<typeof OrcaRuntimeService>): Promise<void> {
return runtime['clearWorktreeTerminalResumeRecords'](WORKTREE_ID, HOST_ID, ['closed'])
}
describe('workspace terminal resume mutation ownership', () => {
it('reads the current mutation and rollback stores with their original receivers', async () => {
const runtime = new OrcaRuntimeService(null)
const reads: string[] = []
let runMutation = (): void => {
throw new Error('mutation was not reserved')
}
let rollback = (): void => {
throw new Error('rollback was not installed')
}
let finish = (): void => {
throw new Error('mutation was not staged')
}
const before: WorkspaceSessionState = {
...getDefaultWorkspaceSession(),
terminalPtyIncarnationsByPaneKey: { 'closed:leaf': 'old', 'other:leaf': 'other' }
}
let mutationSession = before
let rollbackSession = before
const reservation: ResumeStore = {
getWorkspaceSession: () => {
throw new Error('reservation store read')
},
setWorkspaceSession: () => {
throw new Error('reservation store write')
},
runDurableMutation<Value>(
mutate: () => DurableProfileStateMutation<Value>
): Promise<Value> {
expect(this).toBe(reservation)
const deferred = Promise.withResolvers<Value>()
runMutation = () => {
const staged = mutate()
rollback =
staged.rollback ??
(() => {
throw new Error('rollback absent')
})
finish = () => deferred.resolve(staged.value)
}
return deferred.promise
}
}
const mutation: ResumeStore = {
getWorkspaceSession(hostId) {
expect(this).toBe(mutation)
expect(hostId).toBe(HOST_ID)
return mutationSession
},
setWorkspaceSession(session, hostId) {
expect(this).toBe(mutation)
expect(hostId).toBe(HOST_ID)
mutationSession = session
}
}
const recovery: ResumeStore = {
getWorkspaceSession(hostId) {
expect(this).toBe(recovery)
expect(hostId).toBe(HOST_ID)
return rollbackSession
},
setWorkspaceSession(session, hostId) {
expect(this).toBe(recovery)
expect(hostId).toBe(HOST_ID)
rollbackSession = session
}
}
let current: ResumeStore | null = reservation
let phase = 'reserve'
Object.defineProperty(runtime, 'store', {
get: () => {
reads.push(phase)
return current
}
})
const pending = clear(runtime)
expect(reads).toEqual(['reserve', 'reserve', 'reserve', 'reserve'])
current = mutation
phase = 'mutation'
runMutation()
expect(reads.filter((entry) => entry === 'mutation')).toHaveLength(3)
expect(mutationSession.terminalPtyIncarnationsByPaneKey).toEqual({ 'other:leaf': 'other' })
rollbackSession = {
...mutationSession,
activeWorktreeId: 'another-worktree',
terminalPtyIncarnationsByPaneKey: {
...mutationSession.terminalPtyIncarnationsByPaneKey,
'new:leaf': 'new'
}
}
current = recovery
phase = 'rollback'
rollback()
expect(reads.filter((entry) => entry === 'rollback')).toHaveLength(2)
expect(rollbackSession.activeWorktreeId).toBe('another-worktree')
expect(rollbackSession.terminalPtyIncarnationsByPaneKey).toEqual({
'closed:leaf': 'old',
'other:leaf': 'other',
'new:leaf': 'new'
})
finish()
await pending
})
it.each([
{ label: 'null', store: null, reads: 1 },
{ label: 'missing reader', store: {}, reads: 1 },
{
label: 'missing writer',
store: { getWorkspaceSession: () => getDefaultWorkspaceSession() },
reads: 2
},
{
label: 'missing reservation',
store: {
getWorkspaceSession: () => getDefaultWorkspaceSession(),
setWorkspaceSession: () => {}
},
reads: 3
}
])('preserves admission refusal and lookup count for $label', async ({ store, reads }) => {
const runtime = new OrcaRuntimeService(null)
let lookups = 0
Object.defineProperty(runtime, 'store', {
get: () => {
lookups += 1
return store
}
})
await expect(clear(runtime)).rejects.toThrow('workspace_session_unavailable')
expect(lookups).toBe(reads)
})
it('keeps callback-time missing methods at the original late failure boundary', async () => {
const runtime = new OrcaRuntimeService(null)
const store: ResumeStore = {
getWorkspaceSession: () => getDefaultWorkspaceSession(),
setWorkspaceSession: () => {},
async runDurableMutation<Value>(
mutate: () => DurableProfileStateMutation<Value>
): Promise<Value> {
store.getWorkspaceSession = undefined
return mutate().value
}
}
Object.defineProperty(runtime, 'store', { get: () => store })
await expect(clear(runtime)).rejects.toThrow(
'this.store.getWorkspaceSession is not a function'
)
})
it('keeps a store disappearing during admission at the original property lookup', async () => {
const runtime = new OrcaRuntimeService(null)
const initial: ResumeStore = {}
let current: ResumeStore | null = initial
Object.defineProperty(initial, 'getWorkspaceSession', {
get: () => {
current = null
return () => getDefaultWorkspaceSession()
}
})
let lookups = 0
Object.defineProperty(runtime, 'store', {
get: () => {
lookups += 1
return current
}
})
await expect(clear(runtime)).rejects.toThrow(
/^(?:Cannot read properties of null \(reading 'setWorkspaceSession'\)|null is not an object \(evaluating 'this\.store\.setWorkspaceSession'\))$/
)
expect(lookups).toBe(2)
})
})
})
@@ -12,7 +12,7 @@ import {
import { randomUUID } from 'node:crypto'
import { withSelectedTabs } from '../headless-tab-group-selection'
import type { TabGroupLayoutNode } from '../../../shared/tab-types'
import type { OrcaRuntimeWithPersistHeadlessTerminalTitle } from '../orca-runtime-persist-headless-terminal-title'
import type { OrcaRuntimeWithPersistHeadlessTerminalTitle } from '../workspaces/workspace-file-session-targets'
import type { OrcaRuntimeWithPruneMobileSessionTabGroupLayout } from '../orca-runtime-prune-mobile-session-tab-group-layout'
export abstract class RuntimeHeadlessTabGroups extends RuntimeHeadlessTabProperties {
@@ -1,6 +1,6 @@
import type { RuntimeHeadlessTabProperties as HeadlessTabPropertiesMethods } from './headless-tab-properties'
import type { RuntimeHeadlessTabGroups as HeadlessTabGroupsMethods } from './headless-tab-group-moves'
import type { OrcaRuntimeWithPersistHeadlessTerminalTitle as PersistHeadlessTerminalTitleMethods } from '../orca-runtime-persist-headless-terminal-title'
import type { OrcaRuntimeWithPersistHeadlessTerminalTitle as PersistHeadlessTerminalTitleMethods } from '../workspaces/workspace-file-session-targets'
import type { OrcaRuntimeWithRestoreLivePairedRendererSessionOwnedMobileTerminals as RestoreLivePairedRendererSessionOwnedMobileTerminalsMethods } from './renderer-session-terminal-rescue'
import { OrcaRuntimeWithCloseStructuredAgentSessionTab } from './structured-session-tab-close'
import type {
@@ -1,4 +1,4 @@
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from './session-tabs-change-publication'
import { OrcaRuntimeWithMergePreservedHeadlessMobileSessionTabs } from './mobile-session-preservation'
import type {
RuntimeMobileSessionSnapshotTab,
@@ -1,16 +1,15 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab } from './session-tabs/mobile-session-publication-preservation'
import { OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab } from './mobile-session-publication-preservation'
import type {
BrowserTabInfo,
RuntimeMobileSessionClientTab,
RuntimeMobileSessionMarkdownTab,
RuntimeMobileSessionTabGroup,
RuntimeMobileSessionTabsResult
} from '../../shared/runtime-types'
import { UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH } from '../../shared/runtime-types'
import { getRuntimeBrowserPageRegistry } from './runtime-browser-page-registry'
} from '../../../shared/runtime-types'
import { UNPUBLISHED_WORKTREE_PUBLICATION_EPOCH } from '../../../shared/runtime-types'
import { getRuntimeBrowserPageRegistry } from '../runtime-browser-page-registry'
export class OrcaRuntimeWithScheduleMobileSessionTabsChanged extends OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab {
export abstract class OrcaRuntimeWithScheduleMobileSessionTabsChanged extends OrcaRuntimeWithStoredMobileSnapshotHasStalePreservedTab {
protected scheduleMobileSessionTabsChanged(worktreeId: string): void {
this.pendingMobileSessionTabsChangeSequenceByWorktree.set(
worktreeId,
@@ -1,5 +1,5 @@
import type { RuntimeSessionTabsInventoryPublication as SessionTabsInventoryPublicationMethods } from './session-tabs-inventory-publication'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from './session-tabs-change-publication'
import type { OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree as ResolveBrowserNetworkExecutionHostForWorktreeMethods } from '../terminal/launch/terminal-workspace-scope'
import type { OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession as HydrateHeadlessMobileSessionTabsFromWorkspaceSessionMethods } from './mobile-session-hydration'
import type { OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs as BuildHeadlessMobileSessionBrowserTabsMethods } from '../orca-runtime-build-headless-mobile-session-browser-tabs'
@@ -1,6 +1,6 @@
import type { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce as RestoreStructuredAgentSessionTabsOnceMethods } from './structured-session-tab-restoration'
import { agentSessionRefusalError } from '../../../shared/agent-session-wire-refusals'
import { OrcaRuntimeWithGetWorktreePs } from '../orca-runtime-get-worktree-ps'
import { OrcaRuntimeWithGetWorktreePs } from '../workspaces/workspace-process-listing'
import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
import {
resolveCommittedStructuredAgentSessionAdoptionIntent,
@@ -1,4 +1,4 @@
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from '../orca-runtime-schedule-mobile-session-tabs-changed'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged as ScheduleMobileSessionTabsChangedMethods } from './session-tabs-change-publication'
import { defaultAgentChatLabel } from '../../../shared/agent-session-chat-label'
import { OrcaRuntimeWithGetStructuredAgentSessionCreateSupport } from './structured-session-create-support'
import { getStructuredAgentSessionHost } from '../../native-chat/agent-session-wire/structured-agent-session-registry'
@@ -68,9 +68,13 @@ export async function resolveStructuredCodexAccountHomePath(
export function resolveStructuredCodexAccountKind(
home: string,
settings: Pick<
GlobalSettings,
'codexManagedAccounts' | 'activeCodexManagedAccountId' | 'activeCodexManagedAccountIdsByRuntime'
settings: Partial<
Pick<
GlobalSettings,
| 'codexManagedAccounts'
| 'activeCodexManagedAccountId'
| 'activeCodexManagedAccountIdsByRuntime'
>
>
): AgentSessionAccountKind | undefined {
const same = (other: string): boolean =>
@@ -192,3 +192,16 @@ describe('readStructuredAttentionWorkspaceLabels', () => {
expect(labels).toEqual({ worktreeLabel: 'Notes' })
})
})
it('preserves the missing notification settings failure before dispatch', () => {
let reads = 0
const h = harness({
readNotificationSettings() {
reads += 1
return undefined
}
})
expect(() => h.delivery.deliver(PROMPT, SUMMARY)).toThrow(TypeError)
expect(reads).toBe(1)
expect(h.sent).toEqual([])
})
@@ -30,7 +30,7 @@ import { getRuntimeDesktopSurface } from './runtime-desktop-surface'
export type StructuredAttentionWorkspaceLabels = { repoLabel?: string; worktreeLabel?: string }
export type StructuredAttentionMobileDeliveryDeps = {
readNotificationSettings: () => NotificationSettings
readNotificationSettings: () => NotificationSettings | undefined
/** In-memory metadata only: a lookup that could wait would let the alert outlive its prompt. */
readWorkspaceLabels: (scope: AgentSessionExecutionLocation) => StructuredAttentionWorkspaceLabels
/** In-memory only, like the labels: whether the desktop's Filters switches hide this workspace. */
@@ -85,7 +85,7 @@ export function createStructuredAttentionMobileDelivery(
return
}
const scope = edge.type === 'prompt' ? edge.prompt.scope : edge.completion.scope
const settings = deps.readNotificationSettings()
const settings = deps.readNotificationSettings()!
if (!settings.notifyHiddenWorkspaces && deps.isWorkspaceHidden(scope)) {
return
}
@@ -1,4 +1,3 @@
import type { Store } from '../persistence'
import { LOCAL_EXECUTION_HOST_ID, type ExecutionHostId } from '../../shared/execution-host'
import type { CommitMessageAgentEnvironmentResolvers } from '../text-generation/commit-message-agent-environment'
import type { StructuredChatNamingDeps } from '../native-chat/structured-chat-naming'
@@ -22,7 +21,10 @@ type StructuredChatNamingRuntime = {
}
export function structuredChatNamingDeps(
getStore: () => Pick<Store, 'getSettings'>,
getStore: () => {
getSettings: () => Parameters<typeof resolveTextGenerationParams>[0] &
ReturnType<StructuredChatNamingDeps['getSettings']>
},
runtime: StructuredChatNamingRuntime,
logger: StructuredAgentSessionLogger
): StructuredChatNamingDeps {
@@ -24,10 +24,21 @@ import {
turnText
} from './structured-chat-coordinator-mail-rig.test-fixture'
import type { placeWorkerAgent } from './rpc/methods/orchestration/worker/worker-start-agent-placement'
type WorkspaceLookup = {
showManagedTerminalWorkspace: (
...args: Parameters<typeof runtime.showManagedTerminalWorkspace>
) => Promise<Awaited<ReturnType<typeof placeWorkerAgent>>['worktree']>
}
const WORKER = formatOrcaSessionAddress(testOrcaSessionId(PEER_CHAT))
beforeEach(() => {
vi.spyOn(runtime, 'showManagedTerminalWorkspace').mockImplementation(async (selector) => ({
vi.spyOn<WorkspaceLookup, 'showManagedTerminalWorkspace'>(
runtime,
'showManagedTerminalWorkspace'
).mockImplementation(async (selector) => ({
id: selector.replace(/^id:/, ''),
repoId: 'repo'
}))
@@ -1,22 +1,28 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithSplitPtyBackedTerminal } from './terminal/launch/terminal-split-surface'
import type { OrcaRuntimeWithPruneMobileSessionTabGroupLayout } from '../../orca-runtime-prune-mobile-session-tab-group-layout'
import { OrcaRuntimeWithSplitPtyBackedTerminal } from './terminal-split-surface'
import {
resolveTerminalSessionWorktreeId,
runtimeWorktreeIdsEqual
} from './runtime-worktree-path-identity'
import { makePaneKey } from '../../shared/stable-pane-id'
import { terminalLayoutContainsLeaf } from '../../shared/workspace-session-pane-ownership'
} from '../../runtime-worktree-path-identity'
import { makePaneKey } from '../../../../shared/stable-pane-id'
import { terminalLayoutContainsLeaf } from '../../../../shared/workspace-session-pane-ownership'
import type {
AgentTeamsTmuxCompatRequest,
AgentTeamsTmuxCompatResponse
} from './claude-agent-teams-service'
} from '../../claude-agent-teams-service'
import {
ensureClaudeAgentTeamsShimDir,
resolveClaudeAgentTeamsShimBin
} from './claude-agent-teams-shim-env'
import { applyClaudeEnvPatch } from '../claude-accounts/environment'
} from '../../claude-agent-teams-shim-env'
import { applyClaudeEnvPatch } from '../../../claude-accounts/environment'
export abstract class OrcaRuntimeWithResolveTerminalSplitSourceAuthority extends OrcaRuntimeWithSplitPtyBackedTerminal {
abstract getTerminalHandleForPaneKey(
...args: Parameters<
OrcaRuntimeWithPruneMobileSessionTabGroupLayout['getTerminalHandleForPaneKey']
>
): ReturnType<OrcaRuntimeWithPruneMobileSessionTabGroupLayout['getTerminalHandleForPaneKey']>
export class OrcaRuntimeWithResolveTerminalSplitSourceAuthority extends OrcaRuntimeWithSplitPtyBackedTerminal {
protected resolveTerminalSplitSourceAuthority(
worktreeId: string,
tabId: string,
@@ -1,4 +1,4 @@
import { OrcaRuntimeWithResolveKnownWorkspaceFileTarget } from '../../orca-runtime-resolve-known-workspace-file-target'
import { OrcaRuntimeWithResolveKnownWorkspaceFileTarget } from '../../workspaces/known-workspace-file-target'
import type { PtyIncarnationId } from '../../../../shared/pty-incarnation'
import { getPtyExecutionHost } from '../../../../shared/terminal-execution-host'
import {
@@ -1,4 +1,4 @@
import { OrcaRuntimeWithResolveTerminalSplitSourceAuthority } from '../../orca-runtime-resolve-terminal-split-source-authority'
import { OrcaRuntimeWithResolveTerminalSplitSourceAuthority } from '../launch/terminal-split-source-authority'
import {
type ResolvedWorktree,
runtimeWorktreeIdentityKey,
@@ -1,6 +1,6 @@
/* eslint-disable unicorn/no-useless-spread */
import { OrcaRuntimeWithCreateRuntimeOwnedMobileSessionTerminal } from '../launch/mobile-terminal-owned-create'
import type { OrcaRuntimeWithResolveTerminalSplitSourceAuthority } from '../../orca-runtime-resolve-terminal-split-source-authority'
import type { OrcaRuntimeWithResolveTerminalSplitSourceAuthority } from '../launch/terminal-split-source-authority'
import type {
RuntimeMobileSessionCreateTerminalResult,
RuntimeMobileSessionTerminalClientTab
@@ -1,18 +1,59 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithPersistHeadlessTerminalTitle } from './orca-runtime-persist-headless-terminal-title'
import type { RuntimeWorkspaceCatalog } from './workspace-catalog'
import type { OrcaRuntimeWithListManagedWorktrees } from './managed-workspace-operations'
import type { OrcaRuntimeWithTransitionGraphReloadToTerminalState } from '../session-graph/session-graph-authority'
import type { OrcaRuntimeWithBindPtyIncarnationHandle } from '../terminal/identity/terminal-handle-bindings'
import type { OrcaRuntimeWithInvalidateAllHandlesForPty } from '../terminal/lifecycle/pty-handle-invalidation'
import { OrcaRuntimeWithPersistHeadlessTerminalTitle } from './workspace-file-session-targets'
import {
getConnectionExecutionHostId,
LOCAL_EXECUTION_HOST_ID,
type ExecutionHostId
} from '../../shared/execution-host'
import type { ResolvedWorktree } from './runtime-worktree-path-identity'
import { resolveWorktreeHostRouting } from './worktree-launch-host-repo'
import { findRuntimeWorkspaceFileOwner } from '../../shared/runtime-workspace-file-owner'
import type { RuntimeMobileSessionTabsResult } from '../../shared/runtime-types'
} from '../../../shared/execution-host'
import type { ResolvedWorktree } from '../runtime-worktree-path-identity'
import { resolveWorktreeHostRouting } from '../worktree-launch-host-repo'
import { findRuntimeWorkspaceFileOwner } from '../../../shared/runtime-workspace-file-owner'
import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types'
import { randomUUID } from 'node:crypto'
import type { PtyIncarnationId } from '../../shared/pty-incarnation'
import type { PtyIncarnationId } from '../../../shared/pty-incarnation'
export abstract class OrcaRuntimeWithResolveKnownWorkspaceFileTarget extends OrcaRuntimeWithPersistHeadlessTerminalTitle {
protected abstract listResolvedWorktrees(
...args: Parameters<RuntimeWorkspaceCatalog['listResolvedWorktrees']>
): ReturnType<RuntimeWorkspaceCatalog['listResolvedWorktrees']>
protected abstract buildRuntimeVisibilitySourceMatchersByRepoId(
...args: Parameters<
OrcaRuntimeWithListManagedWorktrees['buildRuntimeVisibilitySourceMatchersByRepoId']
>
): ReturnType<OrcaRuntimeWithListManagedWorktrees['buildRuntimeVisibilitySourceMatchersByRepoId']>
protected abstract isRuntimeWorktreeVisible(
...args: Parameters<OrcaRuntimeWithListManagedWorktrees['isRuntimeWorktreeVisible']>
): ReturnType<OrcaRuntimeWithListManagedWorktrees['isRuntimeWorktreeVisible']>
protected abstract resolveFolderWorkspaceConnectionId(
...args: Parameters<
OrcaRuntimeWithTransitionGraphReloadToTerminalState['resolveFolderWorkspaceConnectionId']
>
): ReturnType<
OrcaRuntimeWithTransitionGraphReloadToTerminalState['resolveFolderWorkspaceConnectionId']
>
protected abstract invalidatePtyIncarnationHandle(
...args: Parameters<OrcaRuntimeWithBindPtyIncarnationHandle['invalidatePtyIncarnationHandle']>
): ReturnType<OrcaRuntimeWithBindPtyIncarnationHandle['invalidatePtyIncarnationHandle']>
protected abstract invalidateAllHandlesForPty(
...args: Parameters<OrcaRuntimeWithInvalidateAllHandlesForPty['invalidateAllHandlesForPty']>
): ReturnType<OrcaRuntimeWithInvalidateAllHandlesForPty['invalidateAllHandlesForPty']>
protected abstract isTerminalHandleAdoptionBlocked(
...args: Parameters<
OrcaRuntimeWithInvalidateAllHandlesForPty['isTerminalHandleAdoptionBlocked']
>
): ReturnType<OrcaRuntimeWithInvalidateAllHandlesForPty['isTerminalHandleAdoptionBlocked']>
protected abstract replaceSyntheticTerminalHandlesForRestoredPty(
...args: Parameters<
OrcaRuntimeWithInvalidateAllHandlesForPty['replaceSyntheticTerminalHandlesForRestoredPty']
>
): ReturnType<
OrcaRuntimeWithInvalidateAllHandlesForPty['replaceSyntheticTerminalHandlesForRestoredPty']
>
export class OrcaRuntimeWithResolveKnownWorkspaceFileTarget extends OrcaRuntimeWithPersistHeadlessTerminalTitle {
protected async resolveKnownWorkspaceFileTarget(
absolutePath: string,
executionHostId: ExecutionHostId
@@ -1,15 +1,14 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce } from './session-tabs/structured-session-tab-restoration'
import { DEFAULT_WORKTREE_LIST_LIMIT } from './orca-runtime-postlude'
import type { RuntimeWorktreeListResult } from '../../shared/runtime-types'
import type { DetectedWorktreeListResult, Worktree } from '../../shared/worktree/types'
import type { Repo } from '../../shared/repo-types'
import { stopMissingWorktreeTerminals } from './missing-worktree-terminal-reconciliation'
import type { RuntimeCommandSurfaceHost } from './orca-runtime-core'
import type { WorktreeVisibilitySourceMatcher } from '../../shared/worktree/visibility-sources'
import type { RuntimeStore } from './runtime-store-contract'
import { getWorkspaceAttachments } from '../../shared/workspace-attachments'
import type { TerminalWorkspaceLaunchScope } from './runtime-legacy-worker-terminal-recovery-types'
import type { OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree } from '../terminal/launch/terminal-workspace-scope'
import { OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce } from '../session-tabs/structured-session-tab-restoration'
import { DEFAULT_WORKTREE_LIST_LIMIT } from '../orca-runtime-postlude'
import type { RuntimeWorktreeListResult } from '../../../shared/runtime-types'
import type { DetectedWorktreeListResult, Worktree } from '../../../shared/worktree/types'
import type { Repo } from '../../../shared/repo-types'
import { stopMissingWorktreeTerminals } from '../missing-worktree-terminal-reconciliation'
import type { WorktreeVisibilitySourceMatcher } from '../../../shared/worktree/visibility-sources'
import type { RuntimeStore } from '../runtime-store-contract'
import { getWorkspaceAttachments } from '../../../shared/workspace-attachments'
import type { TerminalWorkspaceLaunchScope } from '../runtime-legacy-worker-terminal-recovery-types'
import type {
WorkspacePortHostKillRequest,
WorkspacePortHostScanResult,
@@ -17,21 +16,29 @@ import type {
WorkspacePortKillResult,
WorkspacePortProbe,
WorkspacePortScanResult
} from '../../shared/workspace-ports'
} from '../../../shared/workspace-ports'
import {
filterWorkspacePortProbes,
killWorkspacePort,
scanWorkspacePortProbes
} from '../ports/workspace-port-ownership'
} from '../../ports/workspace-port-ownership'
import {
killWorkspacePortOnExecutionHost,
scanWorkspacePortsOnExecutionHost,
type WorkspacePortExecutionHostDeps
} from '../ports/workspace-port-execution-host'
import { resolveWorktreeHostRouting } from './worktree-launch-host-repo'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
} from '../../ports/workspace-port-execution-host'
import { resolveWorktreeHostRouting } from '../worktree-launch-host-repo'
import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
export abstract class OrcaRuntimeWithListManagedWorktrees extends OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce {
protected abstract resolveTerminalWorkspaceLaunchTarget(
...args: Parameters<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['resolveTerminalWorkspaceLaunchTarget']
>
): ReturnType<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['resolveTerminalWorkspaceLaunchTarget']
>
export class OrcaRuntimeWithListManagedWorktrees extends OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce {
listManagedWorktrees(
repoSelector?: string,
limit = DEFAULT_WORKTREE_LIST_LIMIT,
@@ -84,7 +91,7 @@ export class OrcaRuntimeWithListManagedWorktrees extends OrcaRuntimeWithRestoreS
knownWorktreeIds,
detected.worktrees.map((worktree) => worktree.id),
{
runtime: this as RuntimeCommandSurfaceHost<this>,
runtime: this,
getLocalProvider: () => this.getLocalProvider(),
onPtyStopped: this.onPtyStopped ?? undefined
}
@@ -1,4 +1,4 @@
import { OrcaRuntimeWithListManagedWorktrees } from '../orca-runtime-list-managed-worktrees'
import { OrcaRuntimeWithListManagedWorktrees } from './managed-workspace-operations'
import type { RuntimeNavigationTarget } from '../../../shared/runtime-navigation'
import {
navigationTargetsClients,
@@ -1,23 +1,54 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { RuntimeHeadlessTabGroups } from './session-tabs/headless-tab-group-moves'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged } from '../session-tabs/session-tabs-change-publication'
import type { OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree } from '../terminal/launch/terminal-workspace-scope'
import type { OrcaRuntimeWithTransitionGraphReloadToTerminalState } from '../session-graph/session-graph-authority'
import { RuntimeHeadlessTabGroups } from '../session-tabs/headless-tab-group-moves'
import type {
BrowserTabInfo,
RuntimeMarkdownReadTabResult,
RuntimeMarkdownSaveTabResult,
RuntimeMobileSessionTabGroup,
RuntimeMobileSessionTabsSnapshot
} from '../../shared/runtime-types'
import type { ResolvedWorktree } from './runtime-worktree-path-identity'
import type { Repo } from '../../shared/repo-types'
} from '../../../shared/runtime-types'
import type { ResolvedWorktree } from '../runtime-worktree-path-identity'
import type { Repo } from '../../../shared/repo-types'
import {
getConnectionExecutionHostId,
LOCAL_EXECUTION_HOST_ID,
type ExecutionHostId
} from '../../shared/execution-host'
import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-options'
import { resolveWorktreeHostRouting } from './worktree-launch-host-repo'
import { isFloatingWorkspaceSelector } from '../../shared/floating-workspace-worktree'
} from '../../../shared/execution-host'
import { getLocalProjectWorktreeGitOptions } from '../../project-runtime-git-options'
import { resolveWorktreeHostRouting } from '../worktree-launch-host-repo'
import { isFloatingWorkspaceSelector } from '../../../shared/floating-workspace-worktree'
export abstract class OrcaRuntimeWithPersistHeadlessTerminalTitle extends RuntimeHeadlessTabGroups {
protected abstract getListedBrowserTabsByPageId(worktreeId: string): Map<string, BrowserTabInfo>
protected abstract resolveMobileMarkdownWorktreeId(
...args: Parameters<
OrcaRuntimeWithScheduleMobileSessionTabsChanged['resolveMobileMarkdownWorktreeId']
>
): ReturnType<OrcaRuntimeWithScheduleMobileSessionTabsChanged['resolveMobileMarkdownWorktreeId']>
protected abstract floatingWorkspaceToResolvedWorktree(
...args: Parameters<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['floatingWorkspaceToResolvedWorktree']
>
): ReturnType<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['floatingWorkspaceToResolvedWorktree']
>
protected abstract resolveFloatingWorkspacePath(
...args: Parameters<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['resolveFloatingWorkspacePath']
>
): ReturnType<
OrcaRuntimeWithResolveBrowserNetworkExecutionHostForWorktree['resolveFloatingWorkspacePath']
>
protected abstract resolveFolderWorkspaceLaunchScope(
...args: Parameters<
OrcaRuntimeWithTransitionGraphReloadToTerminalState['resolveFolderWorkspaceLaunchScope']
>
): ReturnType<
OrcaRuntimeWithTransitionGraphReloadToTerminalState['resolveFolderWorkspaceLaunchScope']
>
// Persist a manual terminal rename so a headless rebuild keeps the title
// instead of reverting to the generated/default one.
protected persistHeadlessTerminalTitle(
@@ -1,18 +1,46 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { OrcaRuntimeWithGetStatus } from './orca-runtime-get-status'
import type { SshConnectionState } from '../../shared/ssh-types'
import { getPublicSshState } from './public-ssh-state'
import { splitWorktreeId } from '../../shared/worktree/id'
import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
import type { WorktreeStartupLaunch } from '../../shared/worktree/launch-types'
import type { RuntimeNavigationTarget } from '../../shared/runtime-navigation'
import { navigationTargetsClients, navigationTargetsHost } from '../../shared/runtime-navigation'
import { toRuntimeActivateWorktreeEvent } from '../../shared/runtime-client-events'
import type { AgentBrowserBridge } from '../browser/agent-browser-bridge'
import type { BrowserBackend } from '../browser/browser-backend'
import type { EmulatorBridge } from '../emulator/emulator-bridge'
import type { OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession } from '../session-tabs/mobile-session-hydration'
import type { OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs } from '../orca-runtime-build-headless-mobile-session-browser-tabs'
import type { OrcaRuntimeWithScheduleMobileSessionTabsChanged } from '../session-tabs/session-tabs-change-publication'
import type { RuntimeWorkspaceRepoScanRefresh } from './workspace-repo-scan-refresh'
import { OrcaRuntimeWithGetStatus } from '../orca-runtime-get-status'
import type { SshConnectionState } from '../../../shared/ssh-types'
import { getPublicSshState } from '../public-ssh-state'
import { splitWorktreeId } from '../../../shared/worktree/id'
import type { CreateWorktreeResult } from '../../../shared/worktree/create-types'
import type { WorktreeStartupLaunch } from '../../../shared/worktree/launch-types'
import type { RuntimeNavigationTarget } from '../../../shared/runtime-navigation'
import { navigationTargetsClients, navigationTargetsHost } from '../../../shared/runtime-navigation'
import { toRuntimeActivateWorktreeEvent } from '../../../shared/runtime-client-events'
import type { AgentBrowserBridge } from '../../browser/agent-browser-bridge'
import type { BrowserBackend } from '../../browser/browser-backend'
import type { EmulatorBridge } from '../../emulator/emulator-bridge'
export abstract class OrcaRuntimeWithNotifySshStateChanged extends OrcaRuntimeWithGetStatus {
protected abstract hydrateHeadlessMobileSessionTabsFromWorkspaceSession(
...args: Parameters<
OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession['hydrateHeadlessMobileSessionTabsFromWorkspaceSession']
>
): ReturnType<
OrcaRuntimeWithHydrateHeadlessMobileSessionTabsFromWorkspaceSession['hydrateHeadlessMobileSessionTabsFromWorkspaceSession']
>
protected abstract refreshMobileSessionPtyRecords(
...args: Parameters<
OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs['refreshMobileSessionPtyRecords']
>
): ReturnType<
OrcaRuntimeWithBuildHeadlessMobileSessionBrowserTabs['refreshMobileSessionPtyRecords']
>
protected abstract notifyMobileSessionTabsChangedNow(
...args: Parameters<
OrcaRuntimeWithScheduleMobileSessionTabsChanged['notifyMobileSessionTabsChangedNow']
>
): ReturnType<
OrcaRuntimeWithScheduleMobileSessionTabsChanged['notifyMobileSessionTabsChangedNow']
>
protected abstract invalidateSshWorktreeScanCacheInternal(
...args: Parameters<RuntimeWorkspaceRepoScanRefresh['invalidateSshWorktreeScanCacheInternal']>
): ReturnType<RuntimeWorkspaceRepoScanRefresh['invalidateSshWorktreeScanCacheInternal']>
abstract invalidateResolvedWorktreeCache(): void
abstract invalidateWorktreeScanCacheForRepo(repoId: string): void
@@ -0,0 +1,49 @@
import type { RuntimeWorkspaceCatalog } from './workspace-catalog'
import type { OrcaRuntimeWithRefreshPtyWorktreeRecordsFromController } from '../terminal/lifecycle/pty-worktree-refresh'
import type { RuntimeAccountController } from '../runtime-account-controller'
import type { OrcaRuntimeWithPruneMobileSessionTabGroupLayout } from '../orca-runtime-prune-mobile-session-tab-group-layout'
import type { OrcaRuntimeWithGetStructuredAgentSessionCreateSupport } from '../session-tabs/structured-session-create-support'
import type { OrcaRuntimeWithGetPtyRecordForPaneKey } from '../orchestration/terminal-pane-mail-routing'
import { OrcaRuntimeWithStartTuiIdleVisibleReadProbe } from '../terminal/wait/terminal-wait-visible-probe'
/** Members the worktree listing reads from later runtime layers; declarations only. */
export abstract class OrcaRuntimeWithWorktreePsDependencies extends OrcaRuntimeWithStartTuiIdleVisibleReadProbe {
protected abstract listResolvedWorktreeSnapshot(
...args: Parameters<RuntimeWorkspaceCatalog['listResolvedWorktreeSnapshot']>
): ReturnType<RuntimeWorkspaceCatalog['listResolvedWorktreeSnapshot']>
protected abstract refreshPtyWorktreeRecordsFromController(
...args: Parameters<
OrcaRuntimeWithRefreshPtyWorktreeRecordsFromController['refreshPtyWorktreeRecordsFromController']
>
): ReturnType<
OrcaRuntimeWithRefreshPtyWorktreeRecordsFromController['refreshPtyWorktreeRecordsFromController']
>
abstract notifyFolderWorkspaceChanged(): void
abstract notifyBranchRenamed(repoId: string): void
abstract notifyWorktreeFolderRenamed(
repoId: string,
oldWorktreeId: string,
newWorktreeId: string
): void
declare getCommitMessageAgentEnvironmentResolvers: RuntimeAccountController['getCommitMessageAgentEnvironment']
abstract refreshStructuredConversationTabTitle(
...args: Parameters<
OrcaRuntimeWithPruneMobileSessionTabGroupLayout['refreshStructuredConversationTabTitle']
>
): ReturnType<
OrcaRuntimeWithPruneMobileSessionTabGroupLayout['refreshStructuredConversationTabTitle']
>
abstract resolveStructuredAgentAccountHome(
...args: Parameters<
OrcaRuntimeWithGetStructuredAgentSessionCreateSupport['resolveStructuredAgentAccountHome']
>
): ReturnType<
OrcaRuntimeWithGetStructuredAgentSessionCreateSupport['resolveStructuredAgentAccountHome']
>
abstract onStructuredSessionStatusForMail(
...args: Parameters<OrcaRuntimeWithGetPtyRecordForPaneKey['onStructuredSessionStatusForMail']>
): ReturnType<OrcaRuntimeWithGetPtyRecordForPaneKey['onStructuredSessionStatusForMail']>
abstract onStructuredSessionTabHidden(
...args: Parameters<OrcaRuntimeWithGetPtyRecordForPaneKey['onStructuredSessionTabHidden']>
): ReturnType<OrcaRuntimeWithGetPtyRecordForPaneKey['onStructuredSessionTabHidden']>
}
@@ -1,64 +1,65 @@
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
import { resolveStructuredCodexAccountKind } from './structured-agent-account-home'
import { collectRuntimeWorktreeAgentSources } from './runtime-worktree-agent-sources'
import { OrcaRuntimeWithStartTuiIdleVisibleReadProbe } from './terminal/wait/terminal-wait-visible-probe'
import { DEFAULT_WORKTREE_PS_LIMIT } from './orca-runtime-postlude'
import type { RuntimeWorktreePsResult } from '../../shared/runtime-types'
import { buildRuntimeWorktreePsSummaries } from './runtime-worktree-ps-summaries'
import { resolveStructuredCodexAccountKind } from '../structured-agent-account-home'
import { collectRuntimeWorktreeAgentSources } from '../runtime-worktree-agent-sources'
import { OrcaRuntimeWithWorktreePsDependencies } from './workspace-process-listing-dependencies'
import { DEFAULT_WORKTREE_PS_LIMIT } from '../orca-runtime-postlude'
import type { RuntimeWorktreePsResult } from '../../../shared/runtime-types'
import { buildRuntimeWorktreePsSummaries } from '../runtime-worktree-ps-summaries'
import {
buildRuntimeWorktreeSummaryPathIndex,
createRuntimeWorktreePsSummaryLookup
} from './runtime-worktree-summary-paths'
} from '../runtime-worktree-summary-paths'
import {
applyRuntimeWorktreePsSessionActivity,
applyRuntimeWorktreePsTerminalActivity
} from './runtime-worktree-ps-activity'
import { applyRuntimeWorktreePsUnverifiableTerminals } from './runtime-worktree-ps-unverifiable-terminals'
import { attachRuntimeWorktreeAgentRows } from './runtime-worktree-agent-rows'
import { compareWorktreePs } from './runtime-worktree-status-projection'
import type { Repo } from '../../shared/repo-types'
import { enrichMissingRepoGitRemoteIdentities } from '../repo-git-remote-identity-enrichment'
import { ensureStructuredAgentSessionHost as installStructuredAgentSessionHost } from './structured-agent-session-runtime'
} from '../runtime-worktree-ps-activity'
import { applyRuntimeWorktreePsUnverifiableTerminals } from '../runtime-worktree-ps-unverifiable-terminals'
import { attachRuntimeWorktreeAgentRows } from '../runtime-worktree-agent-rows'
import { compareWorktreePs } from '../runtime-worktree-status-projection'
import type { Repo } from '../../../shared/repo-types'
import type { StructuredAgentId } from '../../../shared/agent-session-provider-handle'
import type { GlobalSettings } from '../../../shared/global-settings-types'
import { enrichMissingRepoGitRemoteIdentities } from '../../repo-git-remote-identity-enrichment'
import { ensureStructuredAgentSessionHost as installStructuredAgentSessionHost } from '../structured-agent-session-runtime'
import {
createStructuredAttentionMobileDelivery,
readStructuredAttentionWorkspaceLabels
} from './structured-agent-session-mobile-attention'
} from '../structured-agent-session-mobile-attention'
import {
createStructuredAgentSessionLogger,
neverThrowingStructuredAgentSessionLogger
} from '../native-chat/agent-session-wire/structured-agent-session-logger'
import { openAgentSessionRecordStoreOnce } from './agent-session-record-store-slot'
import type { AgentSessionRecordStore } from './agent-session-record-store'
import { maybeAutoRenameWorkspaceOnFirstStructuredTurn } from '../agent-hooks/first-work-structured-session-rename'
import { firstWorkRenameDeps } from '../agent-hooks/first-work-rename-runtime'
import { createStructuredChatNamingHandler } from '../native-chat/structured-chat-naming'
import { structuredChatNamingDeps } from './structured-chat-naming-runtime'
import { getProfileUserDataPath } from '../orca-profiles/profile-storage-paths'
import { LOCAL_EXECUTION_HOST_ID } from '../../shared/execution-host'
import { buildWorktreeListingPage } from './worktree-listing-host-scope'
} from '../../native-chat/agent-session-wire/structured-agent-session-logger'
import { openAgentSessionRecordStoreOnce } from '../agent-session-record-store-slot'
import type { AgentSessionRecordStore } from '../agent-session-record-store'
import { maybeAutoRenameWorkspaceOnFirstStructuredTurn } from '../../agent-hooks/first-work-structured-session-rename'
import { firstWorkRenameDeps } from '../../agent-hooks/first-work-rename-runtime'
import { createStructuredChatNamingHandler } from '../../native-chat/structured-chat-naming'
import { structuredChatNamingDeps } from '../structured-chat-naming-runtime'
import { getProfileUserDataPath } from '../../orca-profiles/profile-storage-paths'
import { LOCAL_EXECUTION_HOST_ID } from '../../../shared/execution-host'
import { buildWorktreeListingPage } from '../worktree-listing-host-scope'
import {
structuredWorkerOpenTaskTitle,
structuredWorkerOwesWork
} from './structured-worker-custody'
} from '../structured-worker-custody'
import {
resolvedTuiAgentArgsBypassPermissions,
resolveTuiAgentLaunchEnv
} from '../../shared/tui-agent-launch-defaults'
import { isTuiAgent } from '../../shared/tui-agent-config'
import { nativeChatShellEnvironmentPolicy } from '../../shared/native-chat-shell-environment'
import { claudeStructuredAuthPolicyForSettings } from '../claude-accounts/claude-structured-auth-policy'
import { resolveStructuredAgentCommand } from '../native-chat/structured-agent-command-resolution'
import { structuredAgentConfiguredArgs } from '../native-chat/structured-agent-configured-args'
} from '../../../shared/tui-agent-launch-defaults'
import { isTuiAgent } from '../../../shared/tui-agent-config'
import { nativeChatShellEnvironmentPolicy } from '../../../shared/native-chat-shell-environment'
import { claudeStructuredAuthPolicyForSettings } from '../../claude-accounts/claude-structured-auth-policy'
import { resolveStructuredAgentCommand } from '../../native-chat/structured-agent-command-resolution'
import { structuredAgentConfiguredArgs } from '../../native-chat/structured-agent-configured-args'
import { claudeCliFlagSupport } from '../../claude/claude-cli-flag-support'
import {
runtimeHiddenHostIds,
runtimeHiddenRepoIds,
runtimeVisibleRepos,
withoutHiddenHosts
} from './runtime-visible-catalog'
import { claudeCliFlagSupport } from '../claude/claude-cli-flag-support'
import { isStructuredAttentionWorkspaceHidden } from './structured-attention-hidden-workspace'
} from '../runtime-visible-catalog'
import { isStructuredAttentionWorkspaceHidden } from '../structured-attention-hidden-workspace'
export class OrcaRuntimeWithGetWorktreePs extends OrcaRuntimeWithStartTuiIdleVisibleReadProbe {
export abstract class OrcaRuntimeWithGetWorktreePs extends OrcaRuntimeWithWorktreePsDependencies {
async getWorktreePs(
limit = DEFAULT_WORKTREE_PS_LIMIT,
sourceDefaultsSupported = true
@@ -154,7 +155,10 @@ export class OrcaRuntimeWithGetWorktreePs extends OrcaRuntimeWithStartTuiIdleVis
/** The agent's configured env, with the account a desktop that owns this host's accounts chose
* laid over it, as its own launches here carry. */
protected structuredAgentLaunchEnv(agent: StructuredAgentId, settings) {
protected structuredAgentLaunchEnv(
agent: StructuredAgentId,
settings: Pick<GlobalSettings, 'agentDefaultEnv'>
) {
return {
...resolveTuiAgentLaunchEnv(agent, settings.agentDefaultEnv),
...this.accounts.hostLaunchEnvFor(agent)
@@ -1,10 +1,23 @@
import { describe, expect, it } from 'vitest'
import { describe, expect, it, vi } from 'vitest'
import { OrcaRuntimeService } from '../orca-runtime-test-mocks.spec'
import { store, TEST_WORKTREE_ID } from '../orca-runtime-test-fixtures.spec'
import type { Repo } from '../../../shared/repo-types'
import type { ExecutionHostId } from '../../../shared/execution-host'
import type { ResolvedWorktree } from '../runtime-worktree-path-identity'
import type { ResolvedWorktreeSnapshot } from '../runtime-resolved-worktree-cache'
import type { DetectedWorktreeListResult } from '../../../shared/worktree/types'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import {
FLOATING_TERMINAL_WORKTREE_ID,
getDefaultWorkspaceSession
} from '../../../shared/constants'
import type { WorkspacePortExecutionHostDeps } from '../../ports/workspace-port-execution-host'
import type { WorkspacePortProbe } from '../../../shared/workspace-ports'
import type { BrowserTabInfo } from '../../../shared/runtime-browser-contracts'
import type { RuntimeMobileSessionTabsSnapshot } from '../../../shared/runtime-session-contracts'
import type { WorkspaceSessionState } from '../../../shared/workspace-session-state-types'
import { firstWorkRenameDeps } from '../../agent-hooks/first-work-rename-runtime'
const repo: Repo = {
id: 'workspace-query',
@@ -192,3 +205,552 @@ describe('workspace query prototype dispatch', () => {
expect(result.worktrees.map((row) => row.path)).toEqual(['/remote/stored-worktree'])
})
})
describe('workspace-detected-listing-capture characterization', () => {
describe('detected workspace listing during repo resolution', () => {
it('captures the listing method before awaiting resolution and keeps its runtime receiver', async () => {
const pending = Promise.withResolvers<Repo>()
const events: string[] = []
const repo: Repo = {
id: 'repo',
path: '/repo',
displayName: 'repo',
badgeColor: 'blue',
addedAt: 0
}
const result: DetectedWorktreeListResult = {
repoId: repo.id,
authoritative: true,
source: 'git',
worktrees: []
}
const runtime = new (class extends OrcaRuntimeService {
override resolveRepoSelectorForConnection(selector: string, connectionId?: string | null) {
events.push('resolve')
expect([selector, connectionId]).toEqual(['repo', 'ssh:box'])
return pending.promise
}
})()
const original = vi.fn(function (
this: OrcaRuntimeService,
resolved: Repo,
sourceDefaultsSupported?: boolean
) {
events.push('invoke original')
expect(this).toBe(runtime)
expect([resolved, sourceDefaultsSupported]).toEqual([repo, false])
return Promise.resolve(result)
})
const replacement = vi.fn(() => Promise.resolve({ ...result, repoId: 'replacement' }))
let listing: (
repo: Repo,
sourceDefaultsSupported?: boolean
) => Promise<DetectedWorktreeListResult> = original
Object.defineProperty(runtime, 'listDetectedWorktreesForResolvedRepo', {
get: () => {
events.push('capture listing')
return listing
}
})
const listingPromise = runtime.listDetectedManagedWorktrees('repo', 'ssh:box', false)
listing = replacement
pending.resolve(repo)
await expect(listingPromise).resolves.toBe(result)
expect(events).toEqual(['capture listing', 'resolve', 'invoke original'])
expect(original).toHaveBeenCalledOnce()
expect(replacement).not.toHaveBeenCalled()
})
})
describe('workspace methods captured before request property reads', () => {
it('captures port probes before reading repoId', async () => {
const runtime = new OrcaRuntimeService()
const events: string[] = []
const original = vi.fn(async () => [])
const replacement = vi.fn(async () => [])
let probes = original
Object.defineProperty(runtime, 'getWorkspacePortProbes', {
get: () => {
events.push('capture probes')
return probes
}
})
await runtime.killWorkspacePort({
pid: 4242,
port: 3020,
get repoId() {
events.push('read repoId')
probes = replacement
return 'repo'
}
})
expect(events).toEqual(['capture probes', 'read repoId'])
expect(original).toHaveBeenCalledWith('repo')
expect(original.mock.contexts[0]).toBe(runtime)
expect(replacement).not.toHaveBeenCalled()
})
it('captures file routing before reading the requested workspace', async () => {
const runtime = new OrcaRuntimeService()
const events: string[] = []
const original = vi.fn(async () => ({ executionHostId: 'ssh:box' }))
const replacement = vi.fn(async () => ({ executionHostId: 'ssh:box' }))
let routing = original
Object.defineProperty(runtime, 'resolveRuntimeFileTarget', {
get: () => {
events.push('capture routing')
return routing
}
})
await runtime.killWorkspacePortOnHost({
executionHostId: 'ssh:box',
pid: 4242,
port: 3020,
get worktree() {
events.push('read worktree')
routing = replacement
return 'folder:remote'
}
})
expect(events).toEqual(['capture routing', 'read worktree'])
expect(original).toHaveBeenCalledWith('folder:remote')
expect(original.mock.contexts[0]).toBe(runtime)
expect(replacement).not.toHaveBeenCalled()
})
it('captures scan invalidation before reading the resolved repo id', async () => {
const runtime = new OrcaRuntimeService()
const events: string[] = []
const original = vi.fn()
const replacement = vi.fn()
let invalidate = original
const repo: Repo = {
get id() {
events.push('read repo id')
invalidate = replacement
return 'repo'
},
path: '/repo',
displayName: 'repo',
badgeColor: 'blue',
addedAt: 0
}
Object.defineProperty(runtime, 'invalidateWorktreeScanCacheForRepo', {
get: () => {
events.push('capture invalidation')
return invalidate
}
})
Object.defineProperty(runtime, 'resolveRepoSelectorForConnection', {
value: async () => repo
})
const detected: DetectedWorktreeListResult = {
repoId: 'repo',
authoritative: false,
source: 'metadata-fallback',
worktrees: []
}
Object.defineProperty(runtime, 'listDetectedWorktreesForResolvedRepo', {
value: async () => detected
})
await expect(runtime.teardownMissingManagedWorktreeTerminals('repo', [])).resolves.toEqual({
stoppedWorktreeIds: []
})
expect(events).toEqual(['capture invalidation', 'read repo id'])
expect(original).toHaveBeenCalledWith('repo')
expect(original.mock.contexts[0]).toBe(runtime)
expect(replacement).not.toHaveBeenCalled()
})
})
})
describe('workspace-file-notification-construction characterization', () => {
const cases = [
['resolveRuntimeGitTarget', 'reject', 'runtime_unavailable'],
['resolveRuntimeFileTarget', 'reject', 'runtime_unavailable'],
['resolveKnownWorkspaceFileTarget', 'promise', null],
['readMobileMarkdownTab', 'reject', 'tab_not_found'],
['saveMobileMarkdownTab', 'reject', 'tab_not_found'],
['resolveMobileMarkdownWorktreeId', 'reject', 'tab_not_found'],
['invalidateSshWorktreeScanCache', 'sync', undefined],
['notifyWorktreesChangedForRemoteClients', 'sync', undefined],
['publishWorktreeRemovalChange', 'sync', undefined],
['notifyWorktreeCatalogChangedForRemoteClients', 'sync', undefined],
['notifyReposChangedForRemoteClients', 'sync', undefined],
['notifyActivateWorktree', 'sync', undefined],
['notifyClientsActivateWorktree', 'sync', undefined],
['onMobileSessionTabsChanged', 'subscription', undefined]
] as const
type ProbeName = (typeof cases)[number][0]
let probeName: ProbeName = 'notifyReposChangedForRemoteClients'
let result: unknown
let synchronousFailure: unknown
let unsubscribe: (() => void) | undefined
let seamCalls = 0
class ConstructorProbe extends OrcaRuntimeService {
protected override get canonicalFetchKeyCache() {
seamCalls += 1
try {
result = this.inspectDuringConstruction(probeName)
} catch (error) {
synchronousFailure = error
}
return super.canonicalFetchKeyCache
}
listenerCount(): number {
return this.mobileSessionTabListeners.size
}
private inspectDuringConstruction(name: ProbeName): unknown {
switch (name) {
case 'resolveRuntimeGitTarget':
return this.resolveRuntimeGitTarget('id:folder-proof')
case 'resolveRuntimeFileTarget':
return this.resolveRuntimeFileTarget('id:folder-proof')
case 'resolveKnownWorkspaceFileTarget':
return this.resolveKnownWorkspaceFileTarget(join(tmpdir(), 'file-proof'), 'local')
case 'readMobileMarkdownTab':
return this.readMobileMarkdownTab('id:folder-proof', 'tab-proof')
case 'saveMobileMarkdownTab':
return this.saveMobileMarkdownTab(
'id:folder-proof',
'tab-proof',
'version-proof',
'content'
)
case 'resolveMobileMarkdownWorktreeId':
return this.resolveMobileMarkdownWorktreeId('id:folder-proof', 'tab-proof')
case 'invalidateSshWorktreeScanCache':
return this.invalidateSshWorktreeScanCache('host-proof')
case 'notifyWorktreesChangedForRemoteClients':
return this.notifyWorktreesChangedForRemoteClients('repo-proof')
case 'publishWorktreeRemovalChange':
return this.publishWorktreeRemovalChange('repo-proof')
case 'notifyWorktreeCatalogChangedForRemoteClients':
return this.notifyWorktreeCatalogChangedForRemoteClients('repo-proof')
case 'notifyReposChangedForRemoteClients':
return this.notifyReposChangedForRemoteClients()
case 'notifyActivateWorktree':
return this.notifyActivateWorktree('repo-proof', 'folder-proof')
case 'notifyClientsActivateWorktree':
return this.notifyClientsActivateWorktree('repo-proof', 'folder-proof')
case 'onMobileSessionTabsChanged':
unsubscribe = this.onMobileSessionTabsChanged(() => undefined, 'constructor-proof')
return unsubscribe
}
}
}
describe('workspace file notifications during runtime construction', () => {
it.each(cases)('preserves %s before later fields initialize', async (name, phase, expected) => {
probeName = name
result = undefined
synchronousFailure = undefined
seamCalls = 0
const runtime = new ConstructorProbe(null)
expect(seamCalls).toBe(1)
expect(synchronousFailure).toBeUndefined()
if (phase === 'sync') {
expect(result).not.toBeInstanceOf(Promise)
expect(result).toEqual(expected)
return
}
if (phase === 'subscription') {
expect(typeof result).toBe('function')
expect(runtime.listenerCount()).toBe(1)
unsubscribe?.()
expect(runtime.listenerCount()).toBe(0)
return
}
expect(result).toBeInstanceOf(Promise)
await (phase === 'reject'
? expect(result).rejects.toThrow(expected)
: expect(result).resolves.toEqual(expected))
})
})
})
describe('workspace-file-target-capture characterization', () => {
describe('floating workspace file target resolution', () => {
it('captures the converter before awaiting the path and keeps its runtime receiver', async () => {
const runtime = new OrcaRuntimeService()
const pending = Promise.withResolvers<string>()
const events: string[] = []
const convert = runtime['floatingWorkspaceToResolvedWorktree']
const original = vi.fn(function (this: OrcaRuntimeService, path: string) {
events.push('invoke original')
expect(this).toBe(runtime)
return convert.call(this, path)
})
const replacement = vi.fn((path: string) => convert.call(runtime, path))
let converter: typeof convert = original
Object.defineProperty(runtime, 'floatingWorkspaceToResolvedWorktree', {
get: () => {
events.push('capture converter')
return converter
}
})
Object.defineProperty(runtime, 'resolveFloatingWorkspacePath', {
value: () => {
events.push('resolve path')
return pending.promise
}
})
const result = runtime['resolveRuntimeFileTarget'](FLOATING_TERMINAL_WORKTREE_ID)
converter = replacement
const path = join(tmpdir(), 'floating')
pending.resolve(path)
await expect(result).resolves.toMatchObject({
worktree: { path },
executionHostId: 'local'
})
expect(events).toEqual(['capture converter', 'resolve path', 'invoke original'])
expect(original).toHaveBeenCalledWith(path)
expect(replacement).not.toHaveBeenCalled()
})
})
})
describe('workspace-host-ports-construction characterization', () => {
describe('workspace ports during runtime construction', () => {
it('creates lazy port capabilities before later constructor fields initialize', async () => {
let ports: WorkspacePortExecutionHostDeps | undefined
class ConstructionRuntime extends OrcaRuntimeService {
protected override get canonicalFetchKeyCache(): ReadonlyMap<string, string> {
ports = this.getWorkspacePortHostDeps()
return super.canonicalFetchKeyCache
}
}
expect(() => new ConstructionRuntime(null)).not.toThrow()
expect(Object.keys(ports ?? {})).toEqual(['getLocalProbes'])
await expect(ports?.getLocalProbes()).rejects.toThrow('runtime_unavailable')
})
it('keeps the original asynchronous missing-store refusal during construction', async () => {
let probes: Promise<WorkspacePortProbe[]> | undefined
class ConstructionRuntime extends OrcaRuntimeService {
protected override get canonicalFetchKeyCache(): ReadonlyMap<string, string> {
probes = this.getWorkspacePortProbes()
return super.canonicalFetchKeyCache
}
}
expect(() => new ConstructionRuntime(null)).not.toThrow()
await expect(probes).rejects.toThrow('runtime_unavailable')
})
})
})
describe('workspace-session-read-order characterization', () => {
const WORKTREE_ID = 'repo::/workspace'
describe('workspace session callable lookup order', () => {
it('keeps the native refusal for a launcher without a workspace selector', async () => {
const runtime = new OrcaRuntimeService(null)
await expect(
runtime.resolveOrchestrationAgentLauncherForTarget('missing-launcher', {})
).rejects.toBeInstanceOf(TypeError)
})
it('captures the browser reader before evaluating the snapshot workspace getter', () => {
const runtime = new OrcaRuntimeService(null)
const order: string[] = []
const replacement = vi.fn(() => new Map<string, BrowserTabInfo>())
const original = function (
this: InstanceType<typeof OrcaRuntimeService>,
worktreeId: string
) {
expect(this).toBe(runtime)
expect(worktreeId).toBe(WORKTREE_ID)
order.push('original call')
return new Map<string, BrowserTabInfo>()
}
Object.defineProperty(runtime, 'getListedBrowserTabsByPageId', {
configurable: true,
get: () => {
order.push('reader lookup')
return original
}
})
const snapshot: RuntimeMobileSessionTabsSnapshot = {
worktree: WORKTREE_ID,
publicationEpoch: 'seed',
snapshotVersion: 1,
tabs: [],
activeGroupId: null,
activeTabId: null,
activeTabType: null
}
Object.defineProperty(snapshot, 'worktree', {
get: () => {
order.push('argument lookup')
Object.defineProperty(runtime, 'getListedBrowserTabsByPageId', { value: replacement })
return WORKTREE_ID
}
})
expect(runtime['collectPublicMobileSessionTabIds'](snapshot)).toEqual(new Set())
expect(order).toEqual(['reader lookup', 'argument lookup', 'original call'])
expect(replacement).not.toHaveBeenCalled()
})
it('captures the title writer before constructing a session with getter-backed fields', () => {
const runtime = new OrcaRuntimeService(null)
const order: string[] = []
const replacement = vi.fn()
const original = function (
this: InstanceType<typeof OrcaRuntimeService>,
worktreeId: string,
session: WorkspaceSessionState
) {
expect(this).toBe(runtime)
expect(worktreeId).toBe(WORKTREE_ID)
expect(session.tabsByWorktree[WORKTREE_ID]?.[0]?.customTitle).toBe('renamed')
order.push('original write')
}
const session = getDefaultWorkspaceSession()
const tabs: WorkspaceSessionState['tabsByWorktree'] = {
[WORKTREE_ID]: [
{
id: 'tab',
ptyId: null,
worktreeId: WORKTREE_ID,
title: 'Terminal',
customTitle: null,
color: null,
sortOrder: 0,
createdAt: 1
}
]
}
let tabReads = 0
Object.defineProperty(session, 'tabsByWorktree', {
enumerable: true,
get: () => {
tabReads += 1
if (tabReads === 2) {
order.push('session spread')
Object.defineProperty(runtime, 'setWorkspaceSessionForWorktree', { value: replacement })
}
return tabs
}
})
Object.defineProperty(runtime, 'store', { value: { setWorkspaceSession: () => {} } })
Object.defineProperty(runtime, 'getWorkspaceSessionForWorktree', { value: () => session })
Object.defineProperty(runtime, 'setWorkspaceSessionForWorktree', {
configurable: true,
get: () => {
order.push('writer lookup')
return original
}
})
runtime['persistHeadlessTerminalTitle'](WORKTREE_ID, 'tab', 'renamed')
expect(order).toEqual(['writer lookup', 'session spread', 'original write'])
expect(replacement).not.toHaveBeenCalled()
})
})
})
describe('first-work rename lazy Store capabilities', () => {
type RenameStore = Parameters<typeof firstWorkRenameDeps>[0]
type RenameDeps = ReturnType<typeof firstWorkRenameDeps>
function renameStore(): RenameStore {
return {
getSettings: () => ({
workspaceDir: '/workspace',
nestWorkspaces: false,
refreshLocalBaseRefOnWorktreeCreate: false,
branchPrefix: 'none',
branchPrefixCustom: ''
}),
getRepo: () => undefined,
getWorktreeMeta: () => undefined,
setWorktreeMeta: vi.fn()
}
}
const missingCalls: {
name: string
invoke: (deps: RenameDeps) => unknown
provideFolder?: true
}[] = [
{ name: 'display name', invoke: (deps) => deps.getCurrentDisplayName('folder:sample') },
{ name: 'folder path', invoke: (deps) => deps.getFolderWorkspacePath?.('folder:sample') },
{
name: 'pending rename',
invoke: (deps) => deps.isPendingFirstAgentMessageRename?.('folder:sample')
},
{ name: 'display write', invoke: (deps) => deps.setDisplayName('folder:sample', 'New') },
{ name: 'error read', invoke: (deps) => deps.setRenameError('folder:sample', 'Failure') },
{
name: 'error write',
provideFolder: true,
invoke: (deps) => deps.setRenameError('folder:sample', 'Failure')
},
{ name: 'tab owner', invoke: (deps) => deps.resolveWorktreeIdForTab('tab') }
]
it.each(missingCalls)(
'preserves the direct missing $name capability failure',
({ invoke, provideFolder }) => {
const store = renameStore()
if (provideFolder) {
store.getFolderWorkspace = () => undefined
}
const runtime = new OrcaRuntimeService(null)
let notified = false
runtime.notifyFolderWorkspaceChanged = () => {
notified = true
}
const deps = firstWorkRenameDeps(store, runtime)
expect(() => invoke(deps)).toThrow(TypeError)
expect(notified).toBe(false)
expect(deps.renameWorktreeFolder).toBeUndefined()
}
)
it('looks up the current writer on each call and retains Store and Root receivers', () => {
const store = renameStore()
const runtime = new OrcaRuntimeService(null)
const order: string[] = []
runtime.notifyFolderWorkspaceChanged = function () {
expect(this).toBe(runtime)
order.push('notify')
}
const deps = firstWorkRenameDeps(store, runtime)
const write: NonNullable<RenameStore['updateFolderWorkspace']> = function (
this: RenameStore,
id,
updates
) {
expect(this).toBe(store)
expect(arguments).toHaveLength(2)
expect(id).toBe('sample')
expect(updates).toEqual({
name: 'New',
pendingFirstAgentMessageRename: false,
firstAgentMessageRenameError: null
})
order.push('write')
return null
}
Object.defineProperty(store, 'updateFolderWorkspace', {
configurable: true,
get() {
order.push('lookup')
return write
}
})
deps.setDisplayName('folder:sample', 'New')
expect(order).toEqual(['lookup', 'write', 'notify'])
delete store.updateFolderWorkspace
expect(() => deps.setDisplayName('folder:sample', 'Again')).toThrow(TypeError)
expect(order).toEqual(['lookup', 'write', 'notify'])
})
})
@@ -3,7 +3,6 @@ import type { ConversationNameContext } from '../../shared/conversation-name-gen
import type { CommitMessageDraftContext } from '../../shared/commit-message-generation'
import { LOCAL_COMMIT_MESSAGE_HOST_KEY } from '../../shared/commit-message-host-key'
import type { CommandTemplateBackslash } from '../../shared/commit-message-prompt'
import type { GlobalSettings } from '../../shared/global-settings-types'
import type {
GeneratedPullRequestFields,
PullRequestDraftContext
@@ -63,7 +62,7 @@ export function trimGeneratedCommitMessage(message: string): string {
}
export function resolveCommitMessageSettings(
settings: GlobalSettings,
settings: Parameters<typeof resolveSourceControlAiForOperation>[0]['settings'],
discoveryHostKey = LOCAL_COMMIT_MESSAGE_HOST_KEY,
operation: AiTextOperation = 'commitMessage',
repo?: Pick<Repo, 'sourceControlAi'> | null
@@ -78,7 +77,7 @@ export function resolveCommitMessageSettings(
}
export function resolveTextGenerationParams(
settings: GlobalSettings,
settings: Parameters<typeof resolveSourceControlAiForOperation>[0]['settings'],
discoveryHostKey = LOCAL_COMMIT_MESSAGE_HOST_KEY,
operation: AiTextOperation = 'commitMessage',
repo?: Pick<Repo, 'sourceControlAi'> | null
+4
View File
@@ -143,3 +143,7 @@ describe('assertBranchPrefixValid', () => {
)
})
})
it('keeps an unknown prefix strategy as the original implicit undefined result', () => {
expect(selectBranchPrefixInput({ branchPrefix: 'future-prefix' }, 'user')).toBeUndefined()
})
+2 -2
View File
@@ -12,9 +12,9 @@ export type BranchPrefixSettings = {
* the renderer's live settings feedback agree on which field each strategy uses.
*/
export function selectBranchPrefixInput(
settings: BranchPrefixSettings,
settings: Omit<BranchPrefixSettings, 'branchPrefix'> & { branchPrefix: string },
gitUsername: string | null
): string | null {
): string | null | void {
switch (settings.branchPrefix) {
case 'git-username':
return gitUsername
+2 -5
View File
@@ -64,11 +64,8 @@ export type ResolveSourceControlAiResult =
| { ok: false; error: string }
type ResolveSourceControlAiInput = {
settings: Pick<
GlobalSettings,
'defaultTuiAgent' | 'agentCmdOverrides' | 'commitMessageAi' | 'sourceControlAi'
> &
Partial<Pick<GlobalSettings, 'disabledTuiAgents'>>
settings: Pick<GlobalSettings, 'commitMessageAi' | 'sourceControlAi'> &
Partial<Pick<GlobalSettings, 'agentCmdOverrides' | 'defaultTuiAgent' | 'disabledTuiAgents'>>
repo?: Pick<Repo, 'sourceControlAi'> | null
operation: AiTextOperation
discoveryHostKey?: string
+5 -4
View File
@@ -14,7 +14,8 @@ const UNSUPPORTED_TUI_AGENT_ARGS: Partial<Record<TuiAgent, readonly string[]>> =
kilo: ['--dangerously-skip-permissions']
}
export const DEFAULT_TUI_AGENT_ARGS: Partial<Record<TuiAgent, string>> = YOLO_TUI_AGENT_ARGS
export const DEFAULT_TUI_AGENT_ARGS: Partial<Record<TuiAgent, string>> &
Readonly<Record<string, string | undefined>> = YOLO_TUI_AGENT_ARGS
export const DEFAULT_TUI_AGENT_ENV: Partial<Record<string, Record<string, string>>> =
YOLO_TUI_AGENT_ENV
@@ -107,7 +108,7 @@ export function normalizeTuiAgentEnvRecord(
return normalized
}
export function getTuiAgentDefaultArgs(agent: TuiAgent): string {
export function getTuiAgentDefaultArgs(agent: string): string {
return DEFAULT_TUI_AGENT_ARGS[agent] ?? ''
}
@@ -116,8 +117,8 @@ export function getTuiAgentDefaultEnv(agent: string): Record<string, string> {
}
export function resolveTuiAgentLaunchArgs(
agent: TuiAgent,
configuredArgs: Partial<Record<TuiAgent, string>> | null | undefined
agent: string,
configuredArgs: Readonly<Record<string, string | undefined>> | null | undefined
): string {
if (
configuredArgs &&