mirror of
https://github.com/stablyai/orca.git
synced 2026-11-01 01:27:35 +00:00
* perf(git): stop blocking main on the open-on-remote git cascade
`getRemoteFileUrl` ran up to 6 sequential `gitExecFileSync` calls on the Electron
main thread — `remote get-url`, then `getDefaultBaseRef`'s `symbolic-ref` plus up
to four `rev-parse --verify` probes — each with its own 15s timeout and no yield
between them.
A complete async twin already existed (`getDefaultBaseRefAsync` ->
`resolveDefaultBaseRefViaExec`, sharing DEFAULT_BASE_REF_PROBES), so the sync
cascade is deleted rather than converted. `getRemoteUrl`, `getRemoteFileUrl` and
`getRemoteCommitUrl` become async; all four downstream callers were already async
(`filesystem-git-url-handlers` inside `ipcMain.handle`, `runtime-git-diff-commands`
async methods) and the provider contract already typed both wrappers
`Promise<string | null>`, so no new async plumbing was needed.
Removes 3 of the 10 `gitExecFileSync` sites and the confusing name collision with
the unrelated async `getDefaultBaseRef` in hosted-review-creation-git-state.
The base-ref regression tests keep their coverage, repointed at the public async
`getBaseRefDefault`.
* perf(git): resolve the repo root in one sync spawn instead of two
getGitRepoRoot ran `rev-parse --is-inside-work-tree` and then `rev-parse
--show-toplevel` as separate blocking spawns. Each sync git call holds the main
thread for up to its whole 15s timeout, so the spawn count is the cost — and this
function is called twice per "Add Project" on a linked worktree, once directly and
once through getLinkedWorktreeMainRepoRoot's self-recursion.
Combined into one invocation. Safe only here: in a bare repo the combined form
exits non-zero, and both that throw and the plain `false` already land on the same
marker-scan fallback. probeGitRepo deliberately does NOT combine — it has to read
`false` cleanly to go on and detect a bare repo, which the combined form's exit 128
would misread as indeterminate.
* perf(git): rebuild only the repos whose authorized roots actually changed
One worktree create called `invalidateAuthorizedRootsCache()`, which dirties every
registered owner. The next authorization-requiring IPC then rebuilt by listing EVERY
repo — and the rebuild never consulted `dirty` when choosing what to list, so `dirty`
gated only whether a rebuild ran, not its scope. At 58 repos that is 58
`git worktree list` spawns, roughly ten seconds of git wall-clock through an
admission budget of four, to rediscover roots one repo changed.
Both halves were needed; scoping the invalidation alone changed nothing.
- `markAuthorizedRootsOwnerDirty` dirties a single owner, reusing the per-owner
primitives `registerWorktreeRootsForRepo` already used. It leaves `baseRevision`
and the per-repo revision map alone — that pair is the global side-effect-token
fence, and bumping it would retire in-flight tokens for untouched repos.
- `rebuildAuthorizedRootsCache(store, onlyDirty)` re-lists only owners that are
dirty, have no listing yet, or still hold recovered roots (those are retired by
comparison against a fresh listing, so skipping them would strand them as
authorized). Only `ensureAuthorizedRootsCache` passes `onlyDirty`; an explicit
rebuild keeps re-listing everything because callers use it to force a refresh —
`filesystem-auth.test.ts` pins that contract.
`invalidateAuthorizedRootsCacheForRepo` wraps the primitive and falls back to the
global form for an unknown owner or a missing store, rather than silently skipping an
invalidation and leaving a stale allowlist. Applied to the worktree-create path.
Changes that can alter the owner SET (store swap, host/WSL re-routing, nested-repo
import, folder->git upgrade) stay global. Removal paths are not converted yet.
The allowlist contents are unchanged and the failure direction is a false denial
rather than a false allow. The relist predicate is split into its own module so it is
testable alone and the cache file stays inside its line budget without a suppression.
* test(perf): measure what git orchestration actually costs the main thread
The existing churn probe (ORCA_MAIN_THREAD_DIAGNOSTICS=1) reported spawn-initiation
cost for git/gh/glab only — its 7 call sites all sit inside git/command-runner — so
it was blind to `spawnProcess`/`runProcess`, the repo's own mandated wrapper, and to
the blocking `execFileSync('ps')` per PTY resize. That understated total churn across
115 main call sites.
- `spawn-observer.ts`: a settable seam, since shared code cannot import src/main.
Unregistered in the daemon/relay/CLI, where it costs one boolean check.
- `spawnProcess` brackets `nodeSpawn` and reports; exec-file-capture's own report is
removed because it routes through runProcess and would double-count.
- `posix-pty-foreground-group` now reports its full blocking duration. Note this
lands on the daemon, not main, whenever the daemon hosts the PTY.
- `ORCA_UNMINIFIED_MAIN=1` build flag, because a minified main bundle cannot
attribute CPU-profile self time to real function names. Defaults unchanged.
- `main-thread-git-cost.spec.ts` + `analyze-main-cpuprofile.mjs`: sweeps concurrency
against real registered repos, captures the churn lines and a V8 CPU profile of
main per phase.
What it found, which is why this is worth keeping: at the width-4 admission ceiling
(~90 git:status/s) main sees ZERO event-loop gaps over 50ms and a worst gap of 23ms,
and is 85% idle. Git orchestration does not stall the main thread. Of the cost it
does incur, spawn-init is 58%, parse 5%, stdout drain 4%.
* test(perf): name the inspector params type the anti-slop gate requires
The broad `object` parameter trips anti-slop(no-object-parameters); the only
Profiler call that passes params sends `{ interval }`.
321 lines
14 KiB
TypeScript
321 lines
14 KiB
TypeScript
// @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests.
|
|
import { OrcaRuntimeWithGetWorktreeTerminalProvisioningHost } from './orca-runtime-get-worktree-terminal-provisioning-host'
|
|
import type { RuntimeManagedWorktreeCreateArgs } from './runtime-managed-worktree-create-types'
|
|
import type { CreateWorktreeResult } from '../../shared/worktree/create-types'
|
|
import { isTuiAgentEnabled } from '../../shared/tui-agent-selection'
|
|
import { isFolderRepo } from '../../shared/repo-kind'
|
|
import { resolveWorktreeCreateRoute } from '../worktree-create-execution-host-route'
|
|
import { ExecutionHostNotDispatchableError } from '../providers/execution-host-provider-dispatch'
|
|
import { createRuntimeFolderWorktree } from './runtime-folder-worktree-create'
|
|
import { createRuntimeLocalManagedWorktree } from './runtime-local-worktree-create'
|
|
import type { PreparationRearmHolder } from '../worktree-create-preparation'
|
|
import { prepareRuntimeLocalWorktreeSetup } from './runtime-local-worktree-setup'
|
|
import { invalidateAuthorizedRootsCacheForRepo } from '../ipc/filesystem-auth'
|
|
import { startRuntimeLocalWorktreeTerminals } from './runtime-local-worktree-terminal-startup'
|
|
|
|
export class OrcaRuntimeWithCreateManagedWorktree extends OrcaRuntimeWithGetWorktreeTerminalProvisioningHost {
|
|
async createManagedWorktree(
|
|
args: RuntimeManagedWorktreeCreateArgs
|
|
): Promise<CreateWorktreeResult> {
|
|
// Why a holder fired in `finally`: consuming a prepared checkout empties a pool slot, so a
|
|
// create that fails anywhere after that — include copy, push target, terminal startup — must
|
|
// still arm the replacement. On success it fires last, once the startup terminals are up.
|
|
const rearm: PreparationRearmHolder = { fire: () => {} }
|
|
try {
|
|
return await this.performManagedWorktreeCreate(args, rearm)
|
|
} finally {
|
|
rearm.fire()
|
|
}
|
|
}
|
|
|
|
private async performManagedWorktreeCreate(
|
|
args: RuntimeManagedWorktreeCreateArgs,
|
|
rearm: PreparationRearmHolder
|
|
): Promise<CreateWorktreeResult> {
|
|
if (!this.store) {
|
|
throw new Error('runtime_unavailable')
|
|
}
|
|
|
|
const repo = await this.resolveRepoSelector(args.repoSelector)
|
|
const createSettings = this.store.getSettings()
|
|
const requestedAgent = args.startupAgent ?? args.createdWithAgent
|
|
const requestedAgentEnabled =
|
|
requestedAgent !== undefined
|
|
? isTuiAgentEnabled(requestedAgent, createSettings.disabledTuiAgents)
|
|
: false
|
|
if ((args.startup || args.startupAgent) && requestedAgent && !requestedAgentEnabled) {
|
|
throw new Error('Selected agent is disabled. Choose an enabled agent before creating.')
|
|
}
|
|
if (
|
|
args.startup &&
|
|
args.startupDraftPaste &&
|
|
!isTuiAgentEnabled(args.startupDraftPaste.agent, createSettings.disabledTuiAgents)
|
|
) {
|
|
throw new Error('Selected agent is disabled. Choose an enabled agent before creating.')
|
|
}
|
|
const agentStartup =
|
|
!args.startup && args.startupAgent
|
|
? this.buildStartupForAgent(
|
|
repo,
|
|
args.startupAgent,
|
|
args.startupPrompt,
|
|
args.startupLaunchPreferences,
|
|
{
|
|
...(args.startupAgentArgs !== undefined ? { agentArgs: args.startupAgentArgs } : {}),
|
|
...(args.startupLaunchSource ? { launchSource: args.startupLaunchSource } : {})
|
|
}
|
|
)
|
|
: null
|
|
const draftStartup =
|
|
!args.startup && !agentStartup && args.startupDraft
|
|
? await this.buildStartupForDraft(
|
|
repo,
|
|
args.startupDraft,
|
|
requestedAgent,
|
|
args.startupLaunchSource
|
|
)
|
|
: null
|
|
const effectiveStartup = args.startup ?? agentStartup?.startup ?? draftStartup?.startup
|
|
const effectiveStartupFollowup = agentStartup?.followup
|
|
const effectiveCreatedWithAgent = args.startup
|
|
? args.createdWithAgent
|
|
: (agentStartup?.agent ??
|
|
draftStartup?.agent ??
|
|
(requestedAgentEnabled ? requestedAgent : undefined))
|
|
const effectiveDraftPaste = args.startupDraftPaste ?? draftStartup?.draftPaste
|
|
// Resolve the execution host once, shared with the `worktrees:create` IPC entry point so the
|
|
// two cannot answer differently for the same repo. Reading the raw `connectionId` field routes
|
|
// an `executionHostId: 'ssh:*'`-only repo down the local path, which runs `git worktree add` on
|
|
// the client against a remote path.
|
|
const createRoute = resolveWorktreeCreateRoute(repo)
|
|
if (isFolderRepo(repo)) {
|
|
// A folder workspace is a registration, not a filesystem create, so it is host-agnostic.
|
|
return createRuntimeFolderWorktree({
|
|
request: args,
|
|
repo,
|
|
startup: effectiveStartup,
|
|
startupFollowup: effectiveStartupFollowup,
|
|
createdWithAgent: effectiveCreatedWithAgent,
|
|
draftPaste: effectiveDraftPaste,
|
|
deps: {
|
|
store: this.store,
|
|
ptySpawnAvailable: Boolean(this.ptyController?.spawn),
|
|
createTerminal: (selector, options) => this.createTerminal(selector, options),
|
|
pasteDraft: (handle, draft) => this.pasteStartupDraftWhenReady(handle, draft),
|
|
sendFollowup: (handle, followup) => this.sendStartupFollowupWhenReady(handle, followup),
|
|
invalidateResolvedWorktrees: () => this.invalidateResolvedWorktreeCache(),
|
|
notifyWorktreesChanged: (repoId) => this.notifyWorktreesChanged(repoId),
|
|
emitCreated: (event) => this.emitWorktreeLifecycle(event),
|
|
activate: (repoId, worktreeId, setup, startup) =>
|
|
this.notifyActivateWorktree(
|
|
repoId,
|
|
worktreeId,
|
|
setup,
|
|
startup,
|
|
undefined,
|
|
args.navigation
|
|
)
|
|
}
|
|
})
|
|
}
|
|
const lineageInput =
|
|
args.lineage || args.comment ? { ...args.lineage, comment: args.comment } : undefined
|
|
const lineageResolution = await this.resolveLineageForWorktreeCreate(lineageInput)
|
|
if (createRoute.kind === 'runtime') {
|
|
throw new ExecutionHostNotDispatchableError(createRoute.hostId)
|
|
}
|
|
if (createRoute.kind === 'ssh') {
|
|
// `createRoute.repo` carries the resolved connection in `connectionId`, because the
|
|
// remote-create pipeline still reads `repo.connectionId!` at every depth. See the workaround
|
|
// note in worktree-create-execution-host-route.ts.
|
|
const result = await this.createManagedRemoteWorktree(createRoute.repo, {
|
|
...args,
|
|
activate: args.activate,
|
|
...(effectiveStartup ? { startup: effectiveStartup } : {}),
|
|
...(effectiveStartupFollowup ? { startupFollowup: effectiveStartupFollowup } : {}),
|
|
...(effectiveCreatedWithAgent ? { createdWithAgent: effectiveCreatedWithAgent } : {}),
|
|
...(effectiveDraftPaste ? { startupDraftPaste: effectiveDraftPaste } : {})
|
|
})
|
|
const recordedLineage = this.recordCreatedWorktreeLineage(result.worktree, lineageResolution)
|
|
this.emitWorktreeLifecycle({
|
|
kind: 'created',
|
|
worktreeId: result.worktree.id,
|
|
path: result.worktree.path,
|
|
branch: result.worktree.branch
|
|
})
|
|
return {
|
|
...result,
|
|
worktree: {
|
|
...result.worktree,
|
|
parentWorktreeId: recordedLineage.lineage?.parentWorktreeId ?? null,
|
|
childWorktreeIds: result.worktree.childWorktreeIds ?? [],
|
|
lineage: recordedLineage.lineage,
|
|
workspaceLineage: recordedLineage.workspaceLineage
|
|
},
|
|
...(lineageInput
|
|
? {
|
|
lineage: recordedLineage.lineage,
|
|
workspaceLineage: recordedLineage.workspaceLineage,
|
|
warnings: recordedLineage.warnings
|
|
}
|
|
: {})
|
|
}
|
|
}
|
|
const { worktree, worktreePath, includeCopyWarning, created, addResult, metadataResult } =
|
|
await createRuntimeLocalManagedWorktree({
|
|
request: args,
|
|
repo,
|
|
store: this.requireStore(),
|
|
createdWithAgent: effectiveCreatedWithAgent,
|
|
hostedReviewExecutionContext: this.getHostedReviewExecutionOptions(repo),
|
|
resolveRemoteTrackingBase: (path, base, ...options) =>
|
|
this.resolveRemoteTrackingBase(path, base, ...options),
|
|
hasRemoteTrackingRef: (path, base, ...options) =>
|
|
this.hasRemoteTrackingRef(path, base, ...options),
|
|
refreshRemoteTrackingBase: (path, base, ...options) =>
|
|
this.getOrStartRemoteTrackingBaseRefresh(path, base, ...options),
|
|
fetchRemote: (path, remote, ...options) =>
|
|
this.fetchRemoteWithCache(path, remote, ...options),
|
|
onWorktreeMetadataPersisted: (persistedWorktree) =>
|
|
this.recordCreatedWorktreeLineage(persistedWorktree, lineageResolution),
|
|
rearm
|
|
})
|
|
const settings = createSettings
|
|
const { lineage, workspaceLineage, warnings: lineageWarnings } = metadataResult
|
|
|
|
let {
|
|
setup,
|
|
defaultTabs,
|
|
warning,
|
|
effectiveDecision,
|
|
hookFound,
|
|
shouldRunSetup,
|
|
didStartInProcessSetupHook
|
|
} = await prepareRuntimeLocalWorktreeSetup({
|
|
request: args,
|
|
repo,
|
|
worktreePath,
|
|
settings,
|
|
runtimeTarget: this.getLocalGitExecutionOptionArgs(repo)[0],
|
|
shouldUseSetupRunner:
|
|
this.authoritativeWindowId !== null ||
|
|
Boolean(effectiveStartup) ||
|
|
Boolean(this.ptyController?.spawn),
|
|
warning: includeCopyWarning
|
|
})
|
|
|
|
this.invalidateResolvedWorktreeCache()
|
|
this.invalidateWorktreeScanCacheForRepo(repo.id)
|
|
// Why: the filesystem-auth layer maintains a separate cache of registered
|
|
// worktree roots used by git IPC handlers (branchCompare, diff, status, etc.)
|
|
// to authorize paths. Without invalidating it here, CLI-created worktrees
|
|
// are not recognized and all git operations fail with "Access denied:
|
|
// unknown repository or worktree path".
|
|
// Scoped to this repo: the global form dirties every owner, so the next
|
|
// authorization-requiring IPC relists EVERY registered repo — ~58 `git worktree
|
|
// list` spawns, ~10s of git wall-clock through an admission budget of 4, to
|
|
// rediscover roots only this repo changed.
|
|
invalidateAuthorizedRootsCacheForRepo(this.store, repo)
|
|
|
|
this.notifyWorktreesChanged(repo.id)
|
|
const {
|
|
warning: terminalWarning,
|
|
returnedSetup,
|
|
didSpawnSetup,
|
|
didSpawnStartup,
|
|
setupTerminalHandle,
|
|
startupTerminalHandle,
|
|
startupTerminalTabId,
|
|
startupTerminalPaneKey,
|
|
startupTerminalPtyId
|
|
} = await startRuntimeLocalWorktreeTerminals({
|
|
request: args,
|
|
repo,
|
|
worktree,
|
|
setup,
|
|
defaultTabs,
|
|
startup: effectiveStartup,
|
|
startupFollowup: effectiveStartupFollowup,
|
|
createdWithAgent: effectiveCreatedWithAgent,
|
|
draftPaste: effectiveDraftPaste,
|
|
warning,
|
|
ports: {
|
|
canSpawn: Boolean(this.ptyController?.spawn),
|
|
createTerminal: (selector, options) => this.createTerminal(selector, options),
|
|
pasteDraft: (handle, draft) => this.pasteStartupDraftWhenReady(handle, draft),
|
|
sendFollowup: (handle, followup) => this.sendStartupFollowupWhenReady(handle, followup),
|
|
provision: (options) => this.provisionManagedWorktreeTerminals(options),
|
|
activate: (repoId, worktreeId, activationSetup, startup, activationDefaultTabs) =>
|
|
this.notifyActivateWorktree(
|
|
repoId,
|
|
worktreeId,
|
|
activationSetup,
|
|
startup,
|
|
activationDefaultTabs,
|
|
args.navigation
|
|
)
|
|
}
|
|
})
|
|
warning = terminalWarning
|
|
this.emitWorktreeLifecycle({
|
|
kind: 'created',
|
|
worktreeId: worktree.id,
|
|
path: worktree.path,
|
|
branch: worktree.branch
|
|
})
|
|
return {
|
|
worktree: {
|
|
...worktree,
|
|
parentWorktreeId: lineage?.parentWorktreeId ?? null,
|
|
childWorktreeIds: [],
|
|
lineage,
|
|
workspaceLineage,
|
|
git: created
|
|
},
|
|
...(lineageInput ? { lineage, workspaceLineage, warnings: lineageWarnings } : {}),
|
|
...(returnedSetup ? { setup: returnedSetup } : {}),
|
|
...(args.awaitTerminalProvisioning
|
|
? {
|
|
setupReceipt: {
|
|
requested: effectiveDecision,
|
|
hookFound,
|
|
startupPolicy: setup?.waitForAgentStartup
|
|
? ('wait-for-setup' as const)
|
|
: ('start-immediately' as const),
|
|
state: !hookFound
|
|
? ('not_configured' as const)
|
|
: effectiveDecision === 'skip' || !shouldRunSetup
|
|
? ('skipped' as const)
|
|
: // Why: the in-process hook is already executing, so reporting
|
|
// spawn_failed would strand callers that retry on it.
|
|
didSpawnSetup || didStartInProcessSetupHook
|
|
? ('running' as const)
|
|
: ('spawn_failed' as const),
|
|
...(setupTerminalHandle ? { terminalHandle: setupTerminalHandle } : {})
|
|
}
|
|
}
|
|
: {}),
|
|
...(defaultTabs ? { defaultTabs } : {}),
|
|
...(warning ? { warning } : {}),
|
|
...(addResult.localBaseRefRefresh
|
|
? { localBaseRefRefresh: addResult.localBaseRefRefresh }
|
|
: {}),
|
|
...(addResult.localBaseRefUpdateSuggestion
|
|
? { localBaseRefUpdateSuggestion: addResult.localBaseRefUpdateSuggestion }
|
|
: {}),
|
|
...(didSpawnStartup && startupTerminalHandle
|
|
? {
|
|
startupTerminal: {
|
|
spawned: true,
|
|
handle: startupTerminalHandle,
|
|
...(startupTerminalTabId ? { tabId: startupTerminalTabId } : {}),
|
|
...(startupTerminalPaneKey ? { paneKey: startupTerminalPaneKey } : {}),
|
|
...(startupTerminalPtyId ? { ptyId: startupTerminalPtyId } : {}),
|
|
surface: 'background' as const
|
|
}
|
|
}
|
|
: {})
|
|
}
|
|
}
|
|
}
|