From 755c1562c3afea7dcedfa2102f40dbf0ed4034be Mon Sep 17 00:00:00 2001 From: Neil Date: Mon, 14 Sep 2026 16:25:51 -0700 Subject: [PATCH] fix(lint): enable anti-slop/no-pass-through-type-alias `anti-slop/no-pass-through-type-alias` rejects a type alias whose entire right-hand side is a bare reference to another named type, including the generic form where every type parameter is forwarded positionally and unchanged (`type A = B`). Those aliases add a second name for one type: readers have to resolve the indirection, "go to definition" lands on a rename rather than the shape, and the two names drift apart in review. Flipped the rule from "off" to "error" in `config/oxlint-anti-slop.json` and fixed all 195 violations reported across `src`, `config`, `tests`, and `mobile`. Fix approach, in order of preference per site: - Delete the alias and use the target type directly at every reference, updating imports. This covers the large majority of the 195. - Where the alias name was the better or more widely used name, rename the target declaration to the alias name instead of renaming call sites (for example `GitUncommittedEntry` -> `GitStatusEntry` in `src/shared/git-status-types.ts`). - Where a pass-through sat in front of a type that was itself only used through that alias, collapse the pair into a single declaration that keeps the real shape (intersection, `Pick`/`Omit`, or union) under one name. No alias was converted into an equivalent `interface X extends Y` to dodge the rule, and no new pass-through was introduced. No suppressions were added. The vendored anti-slop plugin source under `config/oxlint-plugins/anti-slop/` is excluded from the audit by the `--ignore-pattern` flag in `audit:anti-slop`, and stays byte-identical to upstream. Verified: - `npx oxlint --config config/oxlint-anti-slop.json --ignore-pattern 'config/oxlint-plugins/anti-slop/**' src config tests mobile` exits 0 (195 -> 0; baseline counted on a scratch worktree of `nwparker/2x-lint` with the rule flipped on). - `node config/scripts/run-typecheck-projects-in-parallel.mjs` exits 0. - `cd mobile && pnpm typecheck` exits 0 (the parallel script covers only the three desktop tsconfigs). - Root vitest over the changed files and their sibling tests: 210 files, 4072 passed, 1 skipped. - Mobile vitest over the changed files and their sibling tests: 36 files, 445 passed. - `npx oxlint` with the repo's default config over every changed file (root and mobile) exits 0. - `npx oxfmt --write` run over the changed files in both workspaces. --- config/oxlint-anti-slop.json | 2 +- mobile/scripts/mock-server-git-state.ts | 6 +- .../components/MobileDictationSetupSheet.tsx | 6 +- mobile/src/components/MobilePRSidebar.tsx | 6 +- .../NewWorkspaceSetupScriptField.tsx | 6 +- .../src/components/NewWorktreeFormSheet.tsx | 6 +- mobile/src/components/VoiceModelList.tsx | 9 +- .../pr-sidebar/MobilePrViewPanel.tsx | 4 +- .../pr-sidebar/PrSidebarCreateEmptyState.tsx | 4 +- .../use-new-workspace-create-submit.ts | 8 +- .../use-new-workspace-setup-script.ts | 8 +- .../dictation/mobile-dictation-setup.test.ts | 12 +- .../src/dictation/mobile-dictation-setup.ts | 15 +- .../src/files/mobile-file-preview-request.ts | 9 +- mobile/src/layout/responsive-layout.ts | 4 +- mobile/src/session/TerminalPaneView.tsx | 4 +- .../src/session/mobile-diff-review-loaders.ts | 12 +- .../session/mobile-diff-review-positioning.ts | 4 +- .../session/mobile-diff-review-queue.test.ts | 8 +- .../src/session/mobile-diff-review-queue.ts | 32 +- mobile/src/session/mobile-diff-review-rpc.ts | 42 +- .../mobile-diff-review-screen-model.ts | 8 +- .../src/session/mobile-session-route-types.ts | 7 +- mobile/src/session/mobile-terminal-records.ts | 10 +- .../use-mobile-pr-branch-context.test.ts | 8 +- .../session/use-mobile-pr-branch-context.ts | 16 +- .../use-mobile-session-close-actions.ts | 5 +- .../use-mobile-session-screen-state.ts | 10 +- .../use-mobile-session-tab-action-targets.ts | 7 +- ...-mobile-session-terminal-create-actions.ts | 5 +- .../use-mobile-session-terminal-input.ts | 5 +- .../use-mobile-session-terminal-list.ts | 4 +- .../src/settings/voice-settings-operations.ts | 8 +- mobile/src/settings/voice-settings-screen.tsx | 9 +- .../mobile-branch-compare.test.ts | 10 +- .../source-control/mobile-branch-compare.ts | 24 +- .../mobile-branch-entry-format.ts | 4 +- .../mobile-commit-failure-recovery.ts | 8 +- .../mobile-git-read-operations.ts | 4 +- .../source-control/mobile-git-status.test.ts | 16 +- .../src/source-control/mobile-git-status.ts | 45 +- ...bile-hosted-review-create-intent-runner.ts | 14 +- .../mobile-hosted-review-create-intent.ts | 24 +- .../mobile-hosted-review-git-preparation.ts | 6 +- ...obile-hosted-review-remote-prerequisite.ts | 8 +- .../mobile-open-pr-prefill.test.ts | 6 +- .../source-control/mobile-open-pr-prefill.ts | 8 +- .../source-control/mobile-path-sort.test.ts | 5 +- .../source-control/mobile-pr-create.test.ts | 7 +- mobile/src/source-control/mobile-pr-create.ts | 13 +- .../src/source-control/mobile-review-route.ts | 4 +- .../mobile-source-control-actions.test.ts | 8 +- .../mobile-source-control-actions.ts | 4 +- ...bile-source-control-primary-action.test.ts | 4 +- .../mobile-source-control-primary-action.ts | 26 +- .../mobile-source-control-screen-state.ts | 38 +- .../use-mobile-create-pr-runner.ts | 4 +- .../source-control/use-mobile-git-requests.ts | 13 +- ...-mobile-source-control-create-pr-action.ts | 4 +- .../use-mobile-source-control-loaders.ts | 10 +- .../use-mobile-source-control-openers.ts | 17 +- .../use-mobile-source-control-runners.ts | 4 +- .../use-mobile-source-control-state.ts | 10 +- mobile/src/tasks/blank-workspace-create.ts | 9 +- mobile/src/tasks/composer-linked-work-item.ts | 4 +- mobile/src/tasks/github-check-summary.ts | 4 +- mobile/src/tasks/github-project-reference.ts | 9 +- .../src/tasks/mobile-composer-source-types.ts | 6 +- .../tasks/mobile-linear-group-sorted.test.ts | 4 +- mobile/src/tasks/mobile-linear-sort.test.ts | 9 +- .../src/tasks/mobile-task-navigation.test.ts | 8 +- mobile/src/tasks/mobile-task-navigation.ts | 24 +- mobile/src/tasks/mobile-tasks-dependencies.ts | 2 +- mobile/src/tasks/mobile-tasks-item-mapping.ts | 4 +- mobile/src/tasks/mobile-tasks-options.tsx | 6 +- .../mobile-tasks-project-workspace-types.ts | 4 +- .../mobile-tasks-provider-detail-types.ts | 2 +- .../src/tasks/mobile-tasks-reviewer-linear.ts | 18 +- .../tasks/mobile-tasks-view-state-types.ts | 4 +- mobile/src/tasks/source-workspace-create.ts | 10 +- .../use-mobile-tasks-item-detail-loading.tsx | 4 +- .../use-mobile-tasks-linear-item-actions.tsx | 4 +- ...e-mobile-tasks-project-loading-actions.tsx | 11 +- ...le-tasks-provider-view-projection.test.tsx | 14 +- ...bile-tasks-workspace-and-project-state.tsx | 4 +- mobile/src/tasks/workspace-create-params.ts | 12 +- mobile/src/terminal/TerminalWebView.tsx | 696 +++++++++--------- mobile/src/terminal/terminal-file-url-tap.ts | 10 +- mobile/src/terminal/terminal-live-input.ts | 4 +- mobile/src/terminal/terminal-path-tap.ts | 11 +- .../src/terminal/terminal-webview-contract.ts | 4 +- .../terminal/terminal-webview-url-tap.test.ts | 6 +- mobile/src/transport/client-context.tsx | 6 +- .../transport/host-edit-navigation.test.ts | 10 +- mobile/src/transport/host-edit-navigation.ts | 15 +- src/cli/handlers/emulator.ts | 4 +- .../agent-hooks/first-work-branch-rename.ts | 6 +- .../session-first-user-prompt-read.ts | 4 +- .../ai-vault/session-scanner-background.ts | 11 +- ...ion-scanner-opencode-sqlite-bounds.test.ts | 10 +- ...canner-opencode-sqlite-coexistence.test.ts | 4 +- ...ssion-scanner-opencode-sqlite-open.test.ts | 4 +- .../session-scanner-opencode-sqlite.test.ts | 12 +- .../ai-vault/session-scanner-service-spawn.ts | 13 +- .../claude-login-process-termination.ts | 5 +- .../claude-agent-sdk-exit-proof.test.ts | 5 +- .../claude/claude-agent-sdk-exit-proof.ts | 4 +- ...laude-agent-sdk-root-kill-fallback.test.ts | 4 +- .../claude/claude-child-exit-proof-ladder.ts | 4 +- .../claude/claude-child-root-termination.ts | 4 +- .../claude-stream-json-connection.test.ts | 9 +- .../codex-accounts/codex-account-identity.ts | 4 +- .../codex-account-registration.ts | 7 +- src/main/codex-accounts/service.ts | 5 +- .../codex-usage-event-attribution.ts | 8 +- src/main/codex-usage/scanner.ts | 14 +- src/main/codex/codex-app-server-client.ts | 14 +- .../codex-app-server-process-teardown.ts | 4 +- .../codex-app-server-process-tree-kill.ts | 7 +- .../daemon/daemon-pty-process-inspection.ts | 10 +- src/main/daemon/daemon-pty-router.ts | 4 +- .../degraded-daemon-pty-provider.test.ts | 4 +- .../android/android-input-commands.ts | 4 +- .../backends/android-emulator-backend.ts | 4 +- .../emulator/backends/emulator-backend.ts | 4 +- .../emulator/backends/ios-emulator-backend.ts | 5 +- src/main/emulator/emulator-bridge.ts | 4 +- src/main/emulator/emulator-gesture-sender.ts | 4 +- src/main/git/branch-rename.test.ts | 30 +- src/main/git/branch-rename.ts | 18 +- src/main/git/status-test-harness.ts | 22 +- .../client/check/check-job-log-tails.ts | 5 +- .../client/check/get-pr-check-details.ts | 6 +- src/main/github/client/check/get-pr-checks.ts | 8 +- .../check/pr-checks-response-mapping.ts | 10 +- .../github/client/check/rerun-pr-checks.ts | 6 +- .../client/create/add-pr-review-comment.ts | 6 +- .../client/create/pull-request-template.ts | 6 +- .../hydrate-work-item-merge-metadata.ts | 4 +- .../detect/repository-merge-metadata.ts | 6 +- .../github/client/fetch/get-pr-comments.ts | 6 +- src/main/github/client/fetch/get-work-item.ts | 7 +- .../github/client/fetch/repo-slug-upstream.ts | 10 +- .../github/client/fetch/work-item-fetch.ts | 10 +- src/main/github/client/github-exec-scope.ts | 9 +- .../github/client/list/count-work-items.ts | 4 +- .../client/list/work-item-issue-page.ts | 4 +- .../client/list/work-item-list-request.ts | 14 +- .../github/client/list/work-item-pages.ts | 10 +- .../client/list/work-item-search-page.ts | 4 +- .../lookup/branch-lookup-derived-data.ts | 4 +- .../client/lookup/branch-lookup-resolution.ts | 12 +- .../github/client/lookup/pr-branch-lookup.ts | 17 +- .../client/lookup/pr-lookup-rate-limit.ts | 6 +- .../github/client/lookup/pr-number-lookup.ts | 12 +- .../lookup/pr-refresh-outcome-assembly.ts | 6 +- .../client/lookup/pr-stack-summary-cache.ts | 5 +- .../lookup/pull-request-lookup-hydration.ts | 4 +- .../client/lookup/pull-request-push-target.ts | 10 +- .../client/lookup/tracked-upstream-cache.ts | 6 +- src/main/github/client/map/work-item.ts | 4 +- src/main/github/client/merge/merge-pr.ts | 11 +- src/main/github/client/merge/pr-auto-merge.ts | 17 +- .../client/pull-request-lookup-candidates.ts | 7 +- .../client/update/pr-comment-reaction.ts | 6 +- src/main/github/client/update/pr-details.ts | 8 +- .../github/client/update/pr-file-viewed.ts | 6 +- src/main/github/client/update/pr-ready.ts | 5 +- src/main/github/client/update/pr-reviewers.ts | 8 +- src/main/github/client/update/pr-state.ts | 6 +- .../client/update/resolve-review-thread.ts | 6 +- src/main/github/gh-utils.ts | 4 +- .../github-api-repository-remote-probe.ts | 10 +- src/main/github/github-api-repository.ts | 17 +- .../github/github-owner-repo-selection.ts | 17 +- .../github/github-pr-stack-async-merge.ts | 10 +- src/main/github/github-pr-stack.ts | 13 +- src/main/github/github-repository-identity.ts | 12 +- src/main/github/issue-comment.ts | 4 +- src/main/github/issue-timeline.ts | 5 +- src/main/github/issue-work-item-details.ts | 8 +- .../github/merged-pr-commit-membership.ts | 4 +- src/main/github/pull-request-file-contents.ts | 16 +- src/main/github/pull-request-file-data.ts | 14 +- src/main/github/stacked-pr-creation.ts | 21 +- .../work-item-details-api-parity.test.ts | 9 +- src/main/github/work-item-details.ts | 16 +- src/main/github/work-item-participants.ts | 10 +- .../gitlab/gitlab-project-ref-resolution.ts | 26 +- src/main/gitlab/gl-utils.ts | 2 +- src/main/gitlab/issue-update.ts | 4 +- src/main/gitlab/issues.ts | 4 +- .../gitlab/merge-request-creation-lookup.ts | 4 +- src/main/gitlab/merge-request-lookup.ts | 4 +- .../merge-request-project-resolution.ts | 6 +- .../gitlab/merge-request-review-mutations.ts | 10 +- .../gitlab/merge-request-state-mutations.ts | 8 +- src/main/gitlab/merge-request-update.ts | 4 +- src/main/gitlab/mr-discussion-notes.ts | 4 +- src/main/gitlab/mr-file-diffs.ts | 4 +- src/main/gitlab/mr-reviewers-and-approvals.ts | 6 +- src/main/gitlab/pipeline-job-graph.ts | 14 +- src/main/gitlab/pipeline-job-mutations.ts | 6 +- src/main/gitlab/project-ref-inflight.ts | 8 +- src/main/gitlab/project-ref-parser.ts | 10 +- src/main/gitlab/work-item-details.ts | 8 +- src/main/gitlab/work-item-queries.ts | 6 +- .../ipc/filesystem-import-result-types.ts | 10 - src/main/ipc/filesystem-mutations.ts | 8 +- .../ipc/filesystem-runtime-upload-staging.ts | 16 +- src/main/ipc/gitlab-ci-job-handlers.ts | 6 +- .../gitlab-merge-request-mutation-handlers.ts | 6 +- src/main/ipc/gitlab-work-item-handlers.ts | 4 +- src/main/ipc/ssh-ipc-mock-shapes.ts | 3 - src/main/ipc/ssh-ipc-test-harness.ts | 9 +- src/main/ipc/telemetry.ts | 5 +- .../metadata/lineage-owner-resolution.ts | 9 +- .../metadata/workspace-lineage-filtering.ts | 9 +- src/main/memory/hydrate-local-pty-registry.ts | 14 +- .../journal-corruption-repair.test.ts | 2 +- .../journal-database.test.ts | 2 +- .../agent-session-journal/journal-database.ts | 8 +- .../journal-epoch-controller.ts | 2 +- .../journal-epoch-replacement.ts | 2 +- .../journal-epoch-rollover.ts | 2 +- .../agent-session-journal/journal-open.ts | 4 +- .../journal-repair-marker.ts | 6 +- .../journal-row-table.ts | 20 +- .../journal-row-writer.ts | 2 +- .../journal-store-close.ts | 2 +- .../journal-store-open.ts | 8 +- .../journal-store-schema.test.ts | 2 +- .../journal-store.test.ts | 5 +- .../agent-session-journal-recovery.test.ts | 5 +- ...tured-agent-session-handoff-restart-tui.ts | 13 +- ...tructured-agent-session-handoff-restart.ts | 4 +- .../opencode-usage-row-queries.ts | 14 +- .../opencode-usage-worktree-attribution.ts | 12 +- src/main/opencode-usage/scanner.test.ts | 10 +- src/main/opencode-usage/scanner.ts | 8 +- src/main/opencode-usage/schema-helpers.ts | 5 +- .../profile-cloud-dev-service.ts | 6 +- .../profile-project-retired-name-transfer.ts | 4 +- .../profile-project-source-removal.ts | 15 +- .../profile-project-state-file.ts | 8 +- .../profile-project-transfer-payload.ts | 13 +- .../profile-project-transfer-worktree-ids.ts | 6 +- .../orcad/electron-serve-browser-process.ts | 5 +- src/main/persistence/loading-store/store.ts | 3 +- src/main/ports/port-scan-command-client.ts | 3 +- .../posix-pane-foreground-fingerprint.test.ts | 16 +- src/main/providers/pty-process-inspection.ts | 8 +- .../ssh-pty-provider-rpc-operations.ts | 6 +- src/main/providers/ssh-pty-provider.ts | 4 +- src/main/rate-limits/claude-fetcher.ts | 10 +- src/main/rate-limits/codex-fetcher.ts | 4 +- .../service/service-configuration.ts | 4 +- src/main/rate-limits/service/service-state.ts | 4 +- src/main/rate-limits/service/service-types.ts | 2 +- ...-session-acquisition-failure-settlement.ts | 5 +- .../runtime/agent-session-record-store.ts | 8 +- .../mobile-session-tabs-notify-coalescer.ts | 5 +- ...h-remote-terminal-source-range-consumer.ts | 7 +- src/main/runtime/orca-runtime-core.ts | 16 +- src/main/runtime/orca-runtime-emulator.ts | 4 +- ...act-persisted-terminal-surface-identity.ts | 4 +- ...runtime-has-recent-terminal-output-path.ts | 5 +- ...-runtime-mark-pty-liveness-unverifiable.ts | 6 +- .../runtime/orca-runtime-mobile-took-floor.ts | 5 +- ...tore-structured-agent-session-tabs-once.ts | 4 +- src/main/runtime/orca-runtime-runtime-id.ts | 4 +- .../orca-runtime-test-fixtures.spec.ts | 4 +- .../orca-runtime-test-mocks/setup.spec.ts | 174 +++-- ...rca-runtime-test-scenario-builders.spec.ts | 25 +- src/main/runtime/orca-runtime.ts | 1 - ...ation-mailbox-notification-test-harness.ts | 4 +- .../context-only-dispatch-release.ts | 2 +- .../runtime/orchestration/db-messages.test.ts | 2 +- .../db-task-create-readiness.test.ts | 2 +- .../db-task-dispatch-invariant.test.ts | 4 +- .../db-task-dispatch-lifecycle-guards.test.ts | 4 +- .../db-task-dispatch-races.test.ts | 4 +- src/main/runtime/orchestration/db.test.ts | 6 +- src/main/runtime/orchestration/db.ts | 1 - .../db/attempt-observation-types.ts | 6 +- .../db/attempt-outcome-projection.ts | 6 +- .../orchestration/db/dispatch-row-writer.ts | 6 +- .../federated-stub-home-run-backfill.ts | 2 +- .../orchestration/db/lifecycle-transition.ts | 8 +- .../db/lifecycle-write-transaction-runner.ts | 2 +- .../orchestration/db/orchestration-db.ts | 2 +- .../orchestration/db/tasks/task-store.ts | 6 +- .../dispatch-failure-idempotency.test.ts | 2 +- ...ederation-acknowledgment-integrity.test.ts | 2 +- ...ederation-acknowledgment-migration.test.ts | 2 +- .../message-batch-atomicity.test.ts | 12 +- .../mutation-receipt-capacity.test.ts | 10 +- .../mutation-receipt-capacity.ts | 6 +- .../nested-worker-depth-migration.test.ts | 6 +- .../orchestration-adopted-run-binding.test.ts | 4 +- ...tion-creator-authority-performance.test.ts | 4 +- ...hestration-db-retention-pagination.test.ts | 4 +- .../orchestration-legacy-storage-db.test.ts | 4 +- ...chestration-run-list-compatibility.test.ts | 4 +- .../orchestration-schema-version-skew.ts | 26 +- .../run-coordinator-handle-migration.test.ts | 6 +- .../runtime/relay/desktop-relay-service.ts | 5 +- .../runtime/relay/relay-auth-coordinator.ts | 6 +- .../relay/relay-origin-pool-options.ts | 5 +- .../relay/relay-session-broker-contract.ts | 4 +- .../runtime/relay/relay-session-broker.ts | 7 +- src/main/runtime/rpc/dispatcher.ts | 4 +- .../agent-status-producer-census.test.ts | 2 +- .../worker/manual-dispatch-release.test.ts | 4 +- .../worker/worker-list-pagination.test.ts | 4 +- .../terminal/terminal-input-delivery.ts | 9 +- ...y-compatibility-dispatcher-test-fixture.ts | 2 +- ...on-legacy-compatibility-dispatcher.test.ts | 4 +- ...hestration-legacy-coordinator-race.test.ts | 6 +- ...estration-legacy-question-takeover.test.ts | 6 +- ...tration-legacy-takeover-dispatcher.test.ts | 2 +- ...stration-runtime-update-settlement.test.ts | 2 +- ...nds-browser-profile-import-from-browser.ts | 8 +- ...e-commands-active-runtime-text-searches.ts | 4 +- ...ile-commands-search-local-runtime-files.ts | 4 +- .../runtime/runtime-hosted-review-commands.ts | 3 +- ...gacy-worker-terminal-recovery-candidate.ts | 4 +- ...e-legacy-worker-terminal-recovery-types.ts | 6 +- src/main/runtime/runtime-notifier-contract.ts | 4 +- .../runtime-pty-controller-contract.ts | 4 +- .../runtime-terminal-driver-controller.ts | 23 +- .../structured-session-worktree-teardown.ts | 36 +- .../runtime/unstopped-pty-verification.ts | 10 +- src/main/skills/skill-package-identity.ts | 10 +- .../skills/skill-provider-destinations.ts | 6 +- .../stacked-hosted-review-creation.ts | 4 +- src/main/sqlite/sync-database.test.ts | 4 +- src/main/sqlite/sync-database.ts | 10 +- ...remote-orchestration-compatibility.test.ts | 10 +- src/main/startup/main-process-state.ts | 4 +- src/main/startup/main-window-actions.ts | 8 +- src/main/telemetry/client.ts | 5 +- src/main/telemetry/consent.ts | 7 +- .../commit-message-text-generation.ts | 9 +- ...source-control-text-generation-requests.ts | 10 +- src/main/window/renderer-recovery-prompt.ts | 4 +- src/main/workspace-cleanup-scan-snapshot.ts | 10 +- src/main/workspace-space-analysis-snapshot.ts | 10 +- .../dispatcher-capacity-degradation.test.ts | 6 +- .../dispatcher-client-close-cause.test.ts | 4 +- src/relay/dispatcher-client-state.ts | 4 +- src/relay/dispatcher-client-writer.test.ts | 6 +- src/relay/dispatcher-client-writer.ts | 26 +- src/relay/dispatcher-contract.ts | 4 +- src/relay/dispatcher-frame-codec.ts | 6 +- src/relay/dispatcher-json-payload.test.ts | 6 +- .../dispatcher-notification-publication.ts | 4 +- src/relay/dispatcher-producer-transport.ts | 8 +- src/relay/dispatcher-pty-publication.ts | 6 +- src/relay/dispatcher-rpc-routing.ts | 8 +- src/relay/dispatcher-writer-sink.ts | 4 +- src/relay/dispatcher.test.ts | 8 +- src/relay/dispatcher.ts | 2 +- src/relay/fs-handler-git-fallback.ts | 3 +- src/relay/fs-handler-utils.ts | 4 +- ...-pty-echo-backpressure.integration.test.ts | 4 +- ...-pty-echo-backpressure.integration.test.ts | 4 +- .../pty-handler-source-publication.test.ts | 10 +- src/relay/pty-source-credit-retention.ts | 6 +- ...ay-pty-consumer-owner-displacement.test.ts | 8 +- .../relay-pty-publication-admission.test.ts | 8 +- ...relay-pty-source-cancellation-exit.test.ts | 8 +- .../relay-pty-source-publication.test.ts | 20 +- ...lay-pty-source-recovery-completion.test.ts | 6 +- .../relay-pty-source-recovery-completion.ts | 6 +- ...-pty-source-recovery-interleavings.test.ts | 26 +- .../relay-pty-source-restore-retry.test.ts | 8 +- src/relay/relay-pty-source-send-scheduler.ts | 6 +- ...y-pty-source-superseded-activation.test.ts | 8 +- src/relay/workspace-space-scan.ts | 12 +- .../src/components/GitHubItemDialog.tsx | 2 +- .../components/NewWorkspaceComposerCard.tsx | 11 +- .../src/components/PullRequestPage.tsx | 2 +- .../TerminalWorkbenchContainer.test.tsx | 2 +- .../activity/activity-event-state.ts | 8 +- .../activity/activity-pane-events.ts | 13 +- .../activity/activity-thread-grouping.ts | 5 +- .../activity/activity-thread-presentation.ts | 4 +- .../activity/activity-thread-types.ts | 3 +- .../automations/automation-host-scheduler.ts | 20 +- .../annotate/markup-drawing-model.ts | 4 +- .../BrowserMobileDriverOverlay.tsx | 4 +- .../BrowserPaneOverlayLayer.test.tsx | 2 +- .../BrowserPaneOverlayLayer.tsx | 2 +- .../AgentDashboardMapView.tsx | 5 +- .../dashboard-popout/AgentKanbanBoard.tsx | 7 +- .../dashboard-popout/AgentTerminalDialog.tsx | 5 +- .../agent-map-worktree-packing.ts | 30 +- .../dashboard/AgentDashboardDrawer.tsx | 4 +- .../editor/editor-restart-save-handlers.ts | 7 +- .../migrate-restored-editor-file-owner.ts | 4 +- .../emulator-pane/emulator-screen-gesture.ts | 4 +- .../FloatingBrowserSlot.test.tsx | 4 +- .../floating-terminal/FloatingBrowserSlot.tsx | 2 +- .../FloatingTerminalPanel.shortcuts.test.tsx | 4 +- .../floating-terminal-panel-test-fixtures.ts | 6 +- .../use-floating-terminal-panel-items.ts | 2 +- .../conversation-tab-pr-sidebar.tsx | 4 +- .../discuss-item/conversation-tab.tsx | 4 +- .../gh-edit-section-mutations.ts | 4 +- .../edit-item-fields/gh-edit-section.tsx | 4 +- .../land-pull-request/pr-actions-panel.tsx | 4 +- .../land-pull-request/pr-reviewers-panel.tsx | 4 +- .../github-item-dialog-types.ts | 5 +- .../pr-file-viewed-change.ts | 4 +- .../use-github-item-dialog-details.ts | 4 +- .../github-item-dialog-issue-body.tsx | 4 +- .../github-item-dialog-pr-tabs.tsx | 4 +- .../github-project/ProjectItemSlugDialog.tsx | 4 +- .../slug-dialog/SlugDialogBody.tsx | 4 +- .../github-project/useProjectRowActions.ts | 10 +- .../github-project/useProjectViewTable.ts | 2 - .../link-actions/LinkActionPopover.tsx | 7 +- .../link-actions/link-action-request.ts | 6 +- .../src/components/mobile/MobileHero.tsx | 3 +- .../mobile/MobileHeroPairedDevices.tsx | 4 +- .../components/mobile/MobilePageContent.tsx | 4 +- .../mobile/use-mobile-page-paired-devices.ts | 8 +- ...tiveChatStructuredSession.test-harness.tsx | 25 +- .../background-task-header-content.ts | 10 +- .../native-chat/background-task-roster.ts | 14 +- .../native-chat-send-eligibility.ts | 6 +- .../NewWorkspaceComposerProjectSection.tsx | 6 +- .../new-workspace-composer-card-props.ts | 6 +- .../smart-workspace-name-field-model.ts | 4 +- .../use-smart-workspace-name-field-actions.ts | 6 +- ...smart-workspace-name-field-presentation.ts | 6 +- .../actions/merge-actions.ts | 4 +- .../pull-request-page/actions/panel.tsx | 4 +- .../pull-request-page/conversation/tab.tsx | 4 +- .../pull-request-page/edit/issue-updates.ts | 8 +- .../pull-request-page/edit/section.tsx | 4 +- .../pull-request-page/page-types.ts | 4 +- .../pull-request-page/page/tabs-shell.tsx | 4 +- .../pull-request-page/page/viewed-sync.ts | 4 +- .../pull-request-page/reviewers/panel.tsx | 4 +- .../components/right-sidebar/ChecksPanel.tsx | 4 +- .../right-sidebar/checks-panel-review.ts | 6 +- .../checks-panel/active-content.tsx | 4 +- .../checks-panel/gitlab-review-client.ts | 6 +- .../use-checks-panel-ai-acknowledgement.tsx | 4 +- ...ks-panel-check-and-review-actions.test.tsx | 14 +- .../use-checks-panel-context-state.tsx | 5 +- .../use-checks-panel-generation.tsx | 4 +- .../create-pull-request-dialog-field-model.ts | 4 +- .../github-refresh-error-copy.ts | 6 +- .../source-control-primary-action-types.ts | 27 +- .../source-control-primary-action.ts | 9 +- .../commit/commit-area-types.ts | 4 +- .../review/use-pull-request-generation.ts | 4 +- .../source-control/sync/action-error.ts | 4 +- .../source-control/sync/remote-refresh.ts | 4 +- ...se-create-pull-request-field-generation.ts | 6 +- .../src/components/settings/AccountsPane.tsx | 3 +- .../settings/MobilePairedDevicesSection.tsx | 4 +- .../components/settings/MobilePane.test.tsx | 6 +- .../settings/accounts-pane-account-actions.ts | 5 +- .../settings/accounts-pane-types.ts | 6 +- .../settings/integrations-pane-status.ts | 6 +- .../runtime-environment-host-details.ts | 10 +- .../sidebar/OrcaYamlTrustDialog.tsx | 8 +- .../sidebar/StatusIndicator.test.ts | 7 +- .../components/sidebar/StatusIndicator.tsx | 10 +- .../sidebar/project-group-header-drop.ts | 4 +- .../components/sidebar/project-header-drop.ts | 4 +- .../src/components/stats/ShareUsageButton.tsx | 4 +- .../mergeSnapshotAndSessions.test.ts | 29 +- .../status-bar/mergeSnapshotAndSessions.ts | 6 +- .../status-bar/resource-session-bindings.ts | 12 +- .../resource-session-inventory.test.ts | 4 +- .../status-bar/resource-session-inventory.ts | 8 +- .../status-bar/resource-usage-merge-types.ts | 8 +- .../use-resource-session-inventory.test.tsx | 18 +- .../status-bar/use-resource-usage-actions.ts | 5 +- .../use-resource-usage-derived-model.test.tsx | 4 +- .../use-resource-usage-derived-model.ts | 4 +- .../components/tab-bar/BrowserTab.test.tsx | 2 +- .../src/components/tab-bar/BrowserTab.tsx | 2 +- .../tab-bar/TerminalTabLeadingIcon.test.tsx | 4 +- .../tab-bar/TerminalTabLeadingIcon.tsx | 8 +- .../components/tab-bar/tab-bar-item-model.ts | 2 +- .../src/components/tab-bar/tab-bar-props.ts | 2 +- .../tab-bar/tab-title-tooltip.test.tsx | 2 +- .../tab-bar/terminal-tab-activity-status.ts | 7 +- .../tab-group/useTabGroupItemProjections.ts | 2 +- .../tab-group/useTabGroupWorkspaceModel.ts | 2 +- .../terminal-pane/MobileDriverOverlay.tsx | 4 +- ...-coordinator-hook-title-precedence.test.ts | 10 +- ...ordinator-pending-title-inspection.test.ts | 20 +- ...letion-coordinator-process-cadence.test.ts | 4 +- ...dinator-queued-inspection-disposal.test.ts | 6 +- ...ent-completion-coordinator-test-harness.ts | 4 +- .../agent-completion-coordinator-types.ts | 4 +- .../agent-completion-inspection-result.ts | 8 +- ...ent-completion-no-evidence-cadence.test.ts | 4 +- ...-completion-stale-evidence-backoff.test.ts | 8 +- .../deferred-split-pane-handoff.ts | 11 +- .../pane-foreground-agent-tracker.ts | 6 +- .../pane-foreground-process-reader.ts | 8 +- .../regular-terminal-focus-ownership.ts | 3 +- .../terminal-link-action-request.ts | 6 - .../terminal-osc-link-routing.ts | 12 +- .../terminal-pane-lifecycle-types.ts | 4 +- .../terminal-pane-mount-context.ts | 12 +- .../terminal-pane-mount-preparation.ts | 6 +- .../terminal-pane-pane-created.ts | 4 +- .../terminal-pane/terminal-pane-pane-links.ts | 10 +- .../terminal-url-link-hit-testing.ts | 16 +- .../terminal-pane/terminal-web-link-click.ts | 10 +- .../src/components/terminal/tab-type-cycle.ts | 10 +- ...use-task-page-linear-collection-effects.ts | 2 +- ...se-task-page-linear-custom-view-effects.ts | 6 +- .../WorkspaceEmojiSuggestionPopover.test.tsx | 4 +- .../WorkspaceEmojiSuggestionPopover.tsx | 6 +- .../useWorkspaceEmojiShortcodeInput.ts | 6 +- .../agent-hook-completion-notifications.ts | 4 +- .../src/hooks/useEditorExternalWatch.ts | 4 +- ...ents-runtime-environment-selectors.test.ts | 13 +- .../src/i18n/hosted-review-localized-copy.ts | 6 +- .../src/lib/browser-palette-page-entries.ts | 4 +- .../src/lib/codex-pane-restart-eligibility.ts | 8 +- src/renderer/src/lib/codex-session-restart.ts | 6 +- .../src/lib/composer-issue-command.ts | 4 +- .../src/lib/duplicate-browser-tab-options.ts | 4 +- .../src/lib/ensure-hooks-confirmed.ts | 6 +- .../src/lib/explicit-file-link-target.ts | 11 +- .../lib/floating-workspace-tab-creation.ts | 4 +- .../floating-workspace-terminal-actions.ts | 8 +- .../src/lib/folder-workspace-connection.ts | 6 +- src/renderer/src/lib/http-link-routing.ts | 4 +- src/renderer/src/lib/lazy-with-retry.test.ts | 4 +- src/renderer/src/lib/new-workspace.ts | 8 +- .../browser-mobile-driver-state.ts | 19 +- .../lib/pane-manager/mobile-driver-state.ts | 16 +- .../pane-terminal-foreground-queue-state.ts | 4 +- .../pane-terminal-output-flusher.ts | 10 +- .../pane-terminal-output-pipeline.ts | 14 +- .../pane-terminal-output-queue-registry.ts | 16 +- .../pane-terminal-output-scheduler.ts | 14 +- .../pane-terminal-output-writer.ts | 8 +- .../typing-latency/echo-instrumentation.ts | 18 +- .../src/lib/typing-latency/input-source.ts | 9 +- .../src/lib/windows-terminal-capabilities.ts | 18 +- .../lib/windows-terminal-capability-read.ts | 4 +- .../src/lib/workspace-emoji-shortcodes.ts | 12 +- .../src/lib/workspace-tab-palette-search.ts | 6 +- .../lib/workspace-terminal-host-authority.ts | 35 +- .../worktree-live-terminal-surface-owners.ts | 4 +- .../inventory-generation-fence.ts | 7 +- .../src/runtime/runtime-git-client-context.ts | 2 - .../src/runtime/runtime-git-client.ts | 1 - .../runtime/runtime-git-generation-client.ts | 3 +- .../runtime/runtime-terminal-inspection.ts | 8 +- .../agent-status-patch.ts | 6 +- .../agent-status-primitives.ts | 9 +- .../web-session-tabs-sync/layout-groups.ts | 12 +- .../publisher-identity-fences.ts | 11 +- .../runtime/web-session-tabs-sync/state.ts | 32 +- .../web-session-tabs-sync/terminal-build.ts | 11 +- .../terminal-surfaces.ts | 6 +- ...ession-terminal-orphan-recovery-surface.ts | 9 +- .../browser/browser-cookie-import-actions.ts | 7 +- .../slices/degraded-repo-hydration.test.ts | 4 +- .../slices/editor/types/editor-git-slice.ts | 6 +- src/renderer/src/store/slices/github.ts | 3 +- .../store/slices/hosted-review-cache-state.ts | 3 +- .../src/store/slices/new-markdown.test.ts | 4 +- .../slices/store-session-test-harness.ts | 6 +- src/renderer/src/store/types.ts | 2 +- .../web-runtime-client-export-parity.test.ts | 3 +- src/renderer/src/web/web-runtime-client.ts | 6 +- .../agent-status-run-alias-index.test.ts | 6 +- src/shared/agent-status-run-alias-index.ts | 11 +- src/shared/agent-status-subject.test.ts | 8 +- src/shared/agent-status-subject.ts | 24 +- src/shared/browser-workspace-types.ts | 2 - src/shared/child-process/process-spec.ts | 4 - src/shared/child-process/run-process.ts | 8 +- src/shared/claimed-agent-pty-owner.ts | 20 +- src/shared/clipboard-text.ts | 6 +- src/shared/editor-save-events.ts | 2 - src/shared/ephemeral-vm-recipe-runner.ts | 6 +- src/shared/folder-workspace-types.ts | 2 - src/shared/git-status-types.ts | 4 +- .../github/pull-request-refresh-types.ts | 3 - src/shared/github/pull-request-types.ts | 9 +- src/shared/hosted-review.ts | 4 +- src/shared/new-workspace/workspace-source.ts | 6 +- src/shared/renderer-restart-preparation.ts | 4 +- src/shared/runtime-browser-contracts.ts | 2 - .../runtime-client-export-parity.test.ts | 3 +- src/shared/runtime-session-contracts.ts | 13 +- src/shared/runtime-terminal-contracts.ts | 5 +- src/shared/runtime-types.ts | 3 +- src/shared/skill-bundle-install-contract.ts | 3 +- src/shared/source-control-ai-types.ts | 16 +- src/shared/window-shortcut-policy.ts | 8 +- 607 files changed, 2511 insertions(+), 2901 deletions(-) diff --git a/config/oxlint-anti-slop.json b/config/oxlint-anti-slop.json index 8010633e0fa..3f0f01d5534 100644 --- a/config/oxlint-anti-slop.json +++ b/config/oxlint-anti-slop.json @@ -32,7 +32,7 @@ "anti-slop/no-known-value-widening": "off", "anti-slop/no-module-mocking": "off", "anti-slop/no-object-parameters": "off", - "anti-slop/no-pass-through-type-alias": "off", + "anti-slop/no-pass-through-type-alias": "error", "anti-slop/no-reduce-accumulator-copy": "off", "anti-slop/no-reflect-apply": "off", "anti-slop/no-reflect-get": "off", diff --git a/mobile/scripts/mock-server-git-state.ts b/mobile/scripts/mock-server-git-state.ts index cdc2ce19e51..eb3343393f9 100644 --- a/mobile/scripts/mock-server-git-state.ts +++ b/mobile/scripts/mock-server-git-state.ts @@ -1,6 +1,6 @@ -import type { MobileGitStatusEntry } from '../src/source-control/mobile-git-status' +import type { GitStatusEntry } from '../../src/shared/git-status-types' -type FakeGitEntry = MobileGitStatusEntry & { +type FakeGitEntry = GitStatusEntry & { stagedFromUntracked?: boolean } @@ -30,7 +30,7 @@ let fakeAhead = 1 let fakeBehind = 0 let fakeHasUpstream = true -function toGitStatusEntry(entry: FakeGitEntry): MobileGitStatusEntry { +function toGitStatusEntry(entry: FakeGitEntry): GitStatusEntry { const { stagedFromUntracked: _stagedFromUntracked, ...statusEntry } = entry return statusEntry } diff --git a/mobile/src/components/MobileDictationSetupSheet.tsx b/mobile/src/components/MobileDictationSetupSheet.tsx index f4a81c02b8f..6c5ca00cd6d 100644 --- a/mobile/src/components/MobileDictationSetupSheet.tsx +++ b/mobile/src/components/MobileDictationSetupSheet.tsx @@ -1,3 +1,4 @@ +import type { RuntimeSpeechSetupState } from '../../../src/shared/runtime-types' import { useCallback, useEffect, useState } from 'react' import { ActivityIndicator, Pressable, StyleSheet, Switch, Text, View } from 'react-native' import { Check, Download } from 'lucide-react-native' @@ -11,8 +12,7 @@ import { fetchDictationSetup, isModelInFlight, setDictationConfig, - type MobileSpeechModel, - type MobileSpeechSetup + type MobileSpeechModel } from '../dictation/mobile-dictation-setup' const POLL_INTERVAL_MS = 1500 @@ -35,7 +35,7 @@ function formatSize(bytes: number | null): string { // Lets the user enable dictation and download a speech model on the paired // desktop, from the phone. Polls while a download is in flight. export function MobileDictationSetupSheet({ visible, client, onClose, onReady }: Props) { - const [setup, setSetup] = useState(null) + const [setup, setSetup] = useState(null) const [error, setError] = useState(null) const [busy, setBusy] = useState(null) const refresh = useCallback(async (): Promise => { diff --git a/mobile/src/components/MobilePRSidebar.tsx b/mobile/src/components/MobilePRSidebar.tsx index f98854682c9..013ed60f899 100644 --- a/mobile/src/components/MobilePRSidebar.tsx +++ b/mobile/src/components/MobilePRSidebar.tsx @@ -1,3 +1,4 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { ActivityIndicator, Pressable, ScrollView, Text, View } from 'react-native' import { RotateCw } from 'lucide-react-native' import { colors } from '../theme/mobile-theme' @@ -18,7 +19,6 @@ import { usePRBotAuthorOverrides } from '../session/use-pr-bot-author-overrides' import { buildFixChecksPrompt, buildResolveConflictsPrompt } from '../session/pr-ai-triage-prompt' import { prSidebarRenderBranch } from './mobile-pr-sidebar-presentation' import { mobilePrSidebarStyles as styles } from './pr-sidebar/mobile-pr-sidebar-styles' -import type { MobileGitStatusResult } from '../source-control/mobile-git-status' import { PRSidebarHeader } from './pr-sidebar/PRSidebarHeader' import { PRConflictingFilesSection } from './pr-sidebar/PRConflictingFilesSection' import { PRActionsSection } from './pr-sidebar/PRActionsSection' @@ -37,7 +37,7 @@ type Props = { connState: ConnectionState worktreeId: string gitBranch: string | null - gitStatus: MobileGitStatusResult | null + gitStatus: GitStatusResult | null headSha: string | null bottomInset?: number // Hub chrome already shows open-on-web; hide the in-body icon there. @@ -154,7 +154,7 @@ function PrSidebarContent({ connState: ConnectionState worktreeId: string gitBranch: string | null - gitStatus: MobileGitStatusResult | null + gitStatus: GitStatusResult | null actions: MobilePrActions commentActions: MobilePrCommentActions titleAction: MobilePrTitleAction diff --git a/mobile/src/components/NewWorkspaceSetupScriptField.tsx b/mobile/src/components/NewWorkspaceSetupScriptField.tsx index 84e28c5de33..2668753270a 100644 --- a/mobile/src/components/NewWorkspaceSetupScriptField.tsx +++ b/mobile/src/components/NewWorkspaceSetupScriptField.tsx @@ -1,5 +1,5 @@ import { Pressable, Switch, Text, View } from 'react-native' -import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' import { colors } from '../theme/mobile-theme' import { newWorktreeFormStyles as styles } from './new-worktree-form-styles' import type { SetupRunPolicy } from './new-worktree-modal-types' @@ -16,9 +16,9 @@ export function NewWorkspaceSetupScriptField({ command: string source: string | null runPolicy: SetupRunPolicy - decision: Exclude | null + decision: Exclude | null runSetup: boolean - onDecisionChange: (decision: Exclude) => void + onDecisionChange: (decision: Exclude) => void onRunSetupChange: (run: boolean) => void }) { return ( diff --git a/mobile/src/components/NewWorktreeFormSheet.tsx b/mobile/src/components/NewWorktreeFormSheet.tsx index f202d084c82..59454a06ce4 100644 --- a/mobile/src/components/NewWorktreeFormSheet.tsx +++ b/mobile/src/components/NewWorktreeFormSheet.tsx @@ -1,6 +1,6 @@ import { ActivityIndicator, Pressable, Text, TextInput, View } from 'react-native' import { ChevronDown, ChevronUp } from 'lucide-react-native' -import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' import type { WorkspaceSshGate } from '../tasks/workspace-ssh-gate' import type { useMobileComposerSource } from '../tasks/use-mobile-composer-source' import { colors } from '../theme/mobile-theme' @@ -37,7 +37,7 @@ export function NewWorktreeFormSheet(props: { setupCommand: string | null setupSource: string | null setupRunPolicy: SetupRunPolicy - setupDecisionChoice: Exclude | null + setupDecisionChoice: Exclude | null runSetup: boolean error: string creating: boolean @@ -52,7 +52,7 @@ export function NewWorktreeFormSheet(props: { onOpenAgent: () => void onShowAdvancedChange: (show: boolean) => void onNoteChange: (note: string) => void - onSetupDecisionChange: (decision: Exclude) => void + onSetupDecisionChange: (decision: Exclude) => void onRunSetupChange: (run: boolean) => void onCreate: () => void }) { diff --git a/mobile/src/components/VoiceModelList.tsx b/mobile/src/components/VoiceModelList.tsx index 4151f2591ad..70243469971 100644 --- a/mobile/src/components/VoiceModelList.tsx +++ b/mobile/src/components/VoiceModelList.tsx @@ -1,14 +1,11 @@ +import type { RuntimeSpeechSetupState } from '../../../src/shared/runtime-types' import { ActivityIndicator, Pressable, StyleSheet, Text, View } from 'react-native' import { Check, Download, Trash2 } from 'lucide-react-native' import { colors, radii, spacing, typography } from '../theme/mobile-theme' -import { - isModelInFlight, - type MobileSpeechModel, - type MobileSpeechSetup -} from '../dictation/mobile-dictation-setup' +import { isModelInFlight, type MobileSpeechModel } from '../dictation/mobile-dictation-setup' type Props = { - setup: MobileSpeechSetup + setup: RuntimeSpeechSetupState // Disabled mirrors desktop: the model list greys out when dictation is off. disabled: boolean busyAction: { modelId: string; type: 'download' | 'select' | 'delete' } | null diff --git a/mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx b/mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx index f587074e12e..89e685f2dd9 100644 --- a/mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx +++ b/mobile/src/components/pr-sidebar/MobilePrViewPanel.tsx @@ -1,9 +1,9 @@ +import type { GitStatusResult } from '../../../../src/shared/git-status-types' import { StyleSheet, View } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { colors } from '../../theme/mobile-theme' import type { ConnectionState } from '../../transport/types' import type { RpcClient } from '../../transport/rpc-client' -import type { MobileGitStatusResult } from '../../source-control/mobile-git-status' import type { MobilePrSidebarController } from '../../session/use-mobile-pr-sidebar-controller' import { MobilePRSidebar } from '../MobilePRSidebar' @@ -13,7 +13,7 @@ type Props = { worktreeId: string branch: string | null headSha: string | null - gitStatus: MobileGitStatusResult | null + gitStatus: GitStatusResult | null isGithubRepo?: boolean branchContextLoaded?: boolean controller: MobilePrSidebarController diff --git a/mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx b/mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx index cffde86ea5b..92dcacc9816 100644 --- a/mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx +++ b/mobile/src/components/pr-sidebar/PrSidebarCreateEmptyState.tsx @@ -1,10 +1,10 @@ +import type { GitStatusResult } from '../../../../src/shared/git-status-types' import { useEffect, useState } from 'react' import { ActivityIndicator, Pressable, Text, View } from 'react-native' import { GitPullRequestArrow, Link2, RefreshCw } from 'lucide-react-native' import { colors } from '../../theme/mobile-theme' import type { RpcClient } from '../../transport/rpc-client' import type { ConnectionState } from '../../transport/types' -import type { MobileGitStatusResult } from '../../source-control/mobile-git-status' import { getMobileCommitFailureStagedEntries, type MobileCommitFailureRecovery @@ -26,7 +26,7 @@ type Props = { client: RpcClient | null worktreeId: string gitBranch: string | null - gitStatus: MobileGitStatusResult | null + gitStatus: GitStatusResult | null connState: ConnectionState // Refetches the sidebar after create or an explicit empty-state refresh. onCreated: () => void diff --git a/mobile/src/components/use-new-workspace-create-submit.ts b/mobile/src/components/use-new-workspace-create-submit.ts index c9265f8ed21..4e565f60547 100644 --- a/mobile/src/components/use-new-workspace-create-submit.ts +++ b/mobile/src/components/use-new-workspace-create-submit.ts @@ -13,7 +13,7 @@ import { } from '../tasks/setup-hook-trust' import { createWorkspaceFromComposerSource } from '../tasks/source-workspace-create' import { normalizeWorkspaceAgent } from '../tasks/workspace-agent-selection' -import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' import type { WorkspaceSshGate } from '../tasks/workspace-ssh-gate' import type { useMobileComposerSource } from '../tasks/use-mobile-composer-source' import type { WorktreeCreateIdempotencySupport } from '../tasks/worktree-create-idempotency-policy' @@ -28,7 +28,7 @@ import type { NewWorktreeDrawerView } from './use-new-worktree-drawer-navigation import { getSuggestedCreatureName } from './worktree-name-suggestion' type CreateOptions = { - setupOverride?: Exclude + setupOverride?: Exclude approvedSetupContentHash?: string } @@ -51,7 +51,7 @@ export function useNewWorkspaceCreateSubmit(args: { setupCommand: string | null setupTrust: SetupHookTrust | null setupRunPolicy: SetupRunPolicy - setupDecisionChoice: Exclude | null + setupDecisionChoice: Exclude | null runSetup: boolean trustedOrcaHooks: PersistedTrustedOrcaHooks setTrustedOrcaHooks: (trust: PersistedTrustedOrcaHooks) => void @@ -118,7 +118,7 @@ export function useNewWorkspaceCreateSubmit(args: { undefined, args.retiredWorktreeNames ) - let setupDecision: WorkspaceCreateSetupDecision = 'inherit' + let setupDecision: SetupDecision = 'inherit' if (args.setupCommand) { if (options.setupOverride) { setupDecision = options.setupOverride diff --git a/mobile/src/components/use-new-workspace-setup-script.ts b/mobile/src/components/use-new-workspace-setup-script.ts index e564b8365e3..2b45c045a93 100644 --- a/mobile/src/components/use-new-workspace-setup-script.ts +++ b/mobile/src/components/use-new-workspace-setup-script.ts @@ -2,7 +2,7 @@ import { useEffect, useState } from 'react' import type { RpcClient } from '../transport/rpc-client' import type { RpcSuccess } from '../transport/types' import { normalizeSetupHookTrust } from '../tasks/setup-hook-trust' -import type { WorkspaceCreateSetupDecision } from '../tasks/workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' import type { MobileWorkspaceRepo, RepoHooksResponse, @@ -17,8 +17,8 @@ export function useNewWorkspaceSetupScript(args: { setupSource: string | null setupTrust: SetupHookDetails['trust'] setupRunPolicy: SetupHookDetails['runPolicy'] - setupDecisionChoice: Exclude | null - setSetupDecisionChoice: (decision: Exclude) => void + setupDecisionChoice: Exclude | null + setSetupDecisionChoice: (decision: Exclude) => void runSetup: boolean setRunSetup: (run: boolean) => void showAdvanced: boolean @@ -27,7 +27,7 @@ export function useNewWorkspaceSetupScript(args: { const { client, selectedRepo } = args const [details, setDetails] = useState(null) const [setupDecisionChoice, setSetupDecisionChoice] = useState | null>(null) const [runSetup, setRunSetup] = useState(true) diff --git a/mobile/src/dictation/mobile-dictation-setup.test.ts b/mobile/src/dictation/mobile-dictation-setup.test.ts index f84e545af49..7b36df5890b 100644 --- a/mobile/src/dictation/mobile-dictation-setup.test.ts +++ b/mobile/src/dictation/mobile-dictation-setup.test.ts @@ -1,3 +1,4 @@ +import type { RuntimeSpeechSetupState } from '../../../src/shared/runtime-types' import { describe, expect, it, vi } from 'vitest' import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' import type { RpcClient } from '../transport/rpc-client' @@ -10,8 +11,7 @@ import { isDictationSetupRequiredError, isModelInFlight, setDictationConfig, - type MobileSpeechModel, - type MobileSpeechSetup + type MobileSpeechModel } from './mobile-dictation-setup' function ok(result: unknown): RpcSuccess { @@ -62,14 +62,14 @@ describe('isDictationSetupRequiredError', () => { describe('rpc wrappers', () => { it('fetches setup', async () => { - const setup: MobileSpeechSetup = { enabled: false, selectedModelId: '', models: [] } + const setup: RuntimeSpeechSetupState = { enabled: false, selectedModelId: '', models: [] } const client = clientWith([ok(setup)]) await expect(fetchDictationSetup(client)).resolves.toEqual(setup) expect(client.calls[0]).toEqual({ method: 'speech.models.list', params: null }) }) it('retries the idempotent setup read once after logical-client cutover', async () => { - const setup: MobileSpeechSetup = { enabled: false, selectedModelId: '', models: [] } + const setup: RuntimeSpeechSetupState = { enabled: false, selectedModelId: '', models: [] } const sendRequest = vi .fn() .mockRejectedValueOnce(new LogicalClientCutoverError()) @@ -95,14 +95,14 @@ describe('rpc wrappers', () => { }) it('deletes a model and returns refreshed setup', async () => { - const setup: MobileSpeechSetup = { enabled: true, selectedModelId: '', models: [] } + const setup: RuntimeSpeechSetupState = { enabled: true, selectedModelId: '', models: [] } const client = clientWith([ok(setup)]) await expect(deleteDictationModel(client, 'm1')).resolves.toEqual(setup) expect(client.calls[0]).toEqual({ method: 'speech.models.delete', params: { modelId: 'm1' } }) }) it('sets config', async () => { - const setup: MobileSpeechSetup = { enabled: true, selectedModelId: 'm1', models: [] } + const setup: RuntimeSpeechSetupState = { enabled: true, selectedModelId: 'm1', models: [] } const client = clientWith([ok(setup)]) await expect(setDictationConfig(client, { enabled: true, modelId: 'm1' })).resolves.toEqual( setup diff --git a/mobile/src/dictation/mobile-dictation-setup.ts b/mobile/src/dictation/mobile-dictation-setup.ts index ca510ee4a31..dc82190b4a8 100644 --- a/mobile/src/dictation/mobile-dictation-setup.ts +++ b/mobile/src/dictation/mobile-dictation-setup.ts @@ -3,7 +3,6 @@ import type { RpcClient } from '../transport/rpc-client' import { LogicalClientCutoverError } from '../transport/stable-logical-rpc-client' import type { RpcSuccess } from '../transport/types' -export type MobileSpeechSetup = RuntimeSpeechSetupState export type MobileSpeechModel = RuntimeSpeechSetupState['models'][number] // Dictation-setup errors startMobileDictation throws when the desktop isn't @@ -31,7 +30,7 @@ export function isDictationSetupRequiredError(message: string): boolean { export async function fetchDictationSetup( client: Pick -): Promise { +): Promise { const response = await fetchDictationSetupResponse(client) if (!response.ok) { if (isLegacyDesktopSpeechSetupError(response.error)) { @@ -39,7 +38,7 @@ export async function fetchDictationSetup( } throw new Error(response.error?.message || 'Failed to load dictation models') } - return (response as RpcSuccess).result as MobileSpeechSetup + return (response as RpcSuccess).result as RuntimeSpeechSetupState } async function fetchDictationSetupResponse(client: Pick) { @@ -68,23 +67,23 @@ export async function downloadDictationModel( export async function deleteDictationModel( client: Pick, modelId: string -): Promise { +): Promise { const response = await client.sendRequest('speech.models.delete', { modelId }) if (!response.ok) { throw new Error(response.error?.message || 'Failed to delete model') } - return (response as RpcSuccess).result as MobileSpeechSetup + return (response as RpcSuccess).result as RuntimeSpeechSetupState } export async function setDictationConfig( client: Pick, params: { enabled?: boolean; modelId?: string; dictationMode?: 'toggle' | 'hold' } -): Promise { +): Promise { const response = await client.sendRequest('speech.dictation.setup', params) if (!response.ok) { throw new Error(response.error?.message || 'Failed to update dictation settings') } - return (response as RpcSuccess).result as MobileSpeechSetup + return (response as RpcSuccess).result as RuntimeSpeechSetupState } // A model is mid-download (or extracting) and the sheet should keep polling. @@ -93,7 +92,7 @@ export function isModelInFlight(model: MobileSpeechModel): boolean { } // Whether dictation can be used right now: enabled + a selected model that's ready. -export function isDictationReady(setup: MobileSpeechSetup): boolean { +export function isDictationReady(setup: RuntimeSpeechSetupState): boolean { if (!setup.enabled || !setup.selectedModelId) { return false } diff --git a/mobile/src/files/mobile-file-preview-request.ts b/mobile/src/files/mobile-file-preview-request.ts index 63b61326654..edb98b8da1b 100644 --- a/mobile/src/files/mobile-file-preview-request.ts +++ b/mobile/src/files/mobile-file-preview-request.ts @@ -46,7 +46,6 @@ export type MobileFilePreviewRequest = { } type MobileFilePreviewClient = Pick -type TerminalArtifactSource = MobileTerminalArtifactPreviewSource type TerminalArtifactSaveOptions = TerminalArtifactRetryOptions & { baseContent?: string } @@ -112,7 +111,7 @@ export async function loadMobileFilePreview( export async function saveMobileTerminalArtifactPreview( client: MobileFilePreviewClient, - source: TerminalArtifactSource, + source: MobileTerminalArtifactPreviewSource, content: string, options: TerminalArtifactSaveOptions = {} ): Promise { @@ -175,11 +174,11 @@ export async function saveMobileTerminalArtifactPreview( async function verifyTerminalArtifactBaseContent( client: MobileFilePreviewClient, - source: TerminalArtifactSource, + source: MobileTerminalArtifactPreviewSource, baseContent: string, options: TerminalArtifactRetryOptions ): Promise< - | { status: 'ok'; source: TerminalArtifactSource; refreshed: boolean } + | { status: 'ok'; source: MobileTerminalArtifactPreviewSource; refreshed: boolean } | { status: 'error'; error: MobileFilePreviewResult } > { let readSource = source @@ -233,7 +232,7 @@ async function verifyTerminalArtifactBaseContent( function writeTerminalArtifactPreview( client: MobileFilePreviewClient, - source: TerminalArtifactSource, + source: MobileTerminalArtifactPreviewSource, content: string ): Promise { return client.sendRequest('files.writeTerminalArtifact', { diff --git a/mobile/src/layout/responsive-layout.ts b/mobile/src/layout/responsive-layout.ts index 9a93afbaa42..e92b4d1db35 100644 --- a/mobile/src/layout/responsive-layout.ts +++ b/mobile/src/layout/responsive-layout.ts @@ -4,9 +4,7 @@ import { type ResponsiveLayoutMetrics } from './responsive-layout-metrics' -export type ResponsiveLayout = ResponsiveLayoutMetrics - -export function useResponsiveLayout(): ResponsiveLayout { +export function useResponsiveLayout(): ResponsiveLayoutMetrics { const { width, height } = useWindowDimensions() return getResponsiveLayoutMetrics(width, height) } diff --git a/mobile/src/session/TerminalPaneView.tsx b/mobile/src/session/TerminalPaneView.tsx index 06174cd0378..89800a74f08 100644 --- a/mobile/src/session/TerminalPaneView.tsx +++ b/mobile/src/session/TerminalPaneView.tsx @@ -1,8 +1,8 @@ import { useCallback } from 'react' import { StyleSheet, View } from 'react-native' import { TerminalWebView } from '../terminal/TerminalWebView' +import type { RuntimeMobileTerminalTheme } from '../../../src/shared/runtime-types' import type { - MobileTerminalTheme, TerminalKeyboardAvoidanceMetrics, TerminalModes, TerminalWebViewHandle @@ -12,7 +12,7 @@ type TerminalPaneViewProps = { handle: string active: boolean keyboardLift: number - terminalTheme?: MobileTerminalTheme + terminalTheme?: RuntimeMobileTerminalTheme textScale: number onRef: (handle: string, ref: TerminalWebViewHandle | null) => void onWebReady: (handle: string) => void diff --git a/mobile/src/session/mobile-diff-review-loaders.ts b/mobile/src/session/mobile-diff-review-loaders.ts index 0b1a14d156b..917c360006a 100644 --- a/mobile/src/session/mobile-diff-review-loaders.ts +++ b/mobile/src/session/mobile-diff-review-loaders.ts @@ -13,10 +13,8 @@ import { readMobileReviewGitDiffResult, readMobileReviewWorktreeMetadata } from './mobile-diff-review-rpc' -import { - canOpenMobileBranchCompareDiff, - type MobileGitBranchCompareResult -} from '../source-control/mobile-branch-compare' +import { canOpenMobileBranchCompareDiff } from '../source-control/mobile-branch-compare' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' import { resolveMobileBranchCompareBaseRef } from '../source-control/mobile-branch-base-ref' import { isMobileGitUnavailable } from '../source-control/mobile-git-status' import type { RpcClient } from '../transport/rpc-client' @@ -25,7 +23,7 @@ import type { ReviewDiffState, ReviewScreenState } from './mobile-diff-review-sc import { reviewDescriptorFromItem } from './mobile-diff-review-screen-model' type BranchCompareLoadResult = { - result: MobileGitBranchCompareResult | null + result: GitBranchCompareResult | null error?: string } @@ -33,7 +31,7 @@ type DiffLoadInput = { client: RpcClient worktreeId: string item: MobileDiffReviewQueueItem - branchCompare: MobileGitBranchCompareResult | null + branchCompare: GitBranchCompareResult | null } export async function loadMobileDiffReviewBranchCompare( @@ -163,7 +161,7 @@ async function loadBranchFileDiff( client: RpcClient, worktreeId: string, item: MobileDiffReviewQueueItem, - branchCompare: MobileGitBranchCompareResult | null + branchCompare: GitBranchCompareResult | null ) { const summary = branchCompare?.summary if (!summary || !summary.headOid || !summary.mergeBase) { diff --git a/mobile/src/session/mobile-diff-review-positioning.ts b/mobile/src/session/mobile-diff-review-positioning.ts index 63932310767..04354a27f31 100644 --- a/mobile/src/session/mobile-diff-review-positioning.ts +++ b/mobile/src/session/mobile-diff-review-positioning.ts @@ -1,11 +1,11 @@ +import type { GitStagingArea } from '../../../src/shared/git-status-types' import type { DiffReviewScope } from '../../../src/shared/diff-comment-types' -import type { MobileGitStagingArea } from '../source-control/mobile-git-status' import { createMobileDiffReviewFileKey, type MobileDiffReviewQueueItem } from './mobile-diff-review-queue' -export type MobileDiffReviewTargetArea = MobileGitStagingArea | 'branch' +export type MobileDiffReviewTargetArea = GitStagingArea | 'branch' export type MobileDiffReviewInitialTarget = { filePath: string diff --git a/mobile/src/session/mobile-diff-review-queue.test.ts b/mobile/src/session/mobile-diff-review-queue.test.ts index e22d4cd2642..d45c4fc139d 100644 --- a/mobile/src/session/mobile-diff-review-queue.test.ts +++ b/mobile/src/session/mobile-diff-review-queue.test.ts @@ -1,7 +1,7 @@ +import type { GitStatusEntry } from '../../../src/shared/git-status-types' import { describe, expect, it } from 'vitest' import type { DiffComment, MobileDiffReviewState } from '../../../src/shared/diff-comment-types' -import type { MobileGitBranchChangeEntry } from '../source-control/mobile-branch-compare' -import type { MobileGitStatusEntry } from '../source-control/mobile-git-status' +import type { GitBranchChangeEntry } from '../../../src/shared/git-diff-compare-types' import { buildMobileDiffReviewQueue, createMobileDiffReviewFileKey, @@ -12,7 +12,7 @@ import { const emptyReviewState: MobileDiffReviewState = { version: 1, files: {} } -function statusEntry(overrides: Partial): MobileGitStatusEntry { +function statusEntry(overrides: Partial): GitStatusEntry { return { path: 'src/app.ts', status: 'modified', @@ -21,7 +21,7 @@ function statusEntry(overrides: Partial): MobileGitStatusE } } -function branchEntry(overrides: Partial): MobileGitBranchChangeEntry { +function branchEntry(overrides: Partial): GitBranchChangeEntry { return { path: 'src/branch.ts', status: 'modified', diff --git a/mobile/src/session/mobile-diff-review-queue.ts b/mobile/src/session/mobile-diff-review-queue.ts index 68c8e6a17e4..b06829cf54c 100644 --- a/mobile/src/session/mobile-diff-review-queue.ts +++ b/mobile/src/session/mobile-diff-review-queue.ts @@ -1,15 +1,17 @@ +import type { + GitFileStatus, + GitStagingArea, + GitStatusEntry +} from '../../../src/shared/git-status-types' import type { DiffComment, DiffReviewScope, MobileDiffReviewState } from '../../../src/shared/diff-comment-types' -import type { MobileGitBranchChangeEntry } from '../source-control/mobile-branch-compare' +import type { GitBranchChangeEntry } from '../../../src/shared/git-diff-compare-types' import { isMobileGitDiscardableEntry, - isMobileGitStageableEntry, - type MobileGitFileStatus, - type MobileGitStagingArea, - type MobileGitStatusEntry + isMobileGitStageableEntry } from '../source-control/mobile-git-status' import { buildMobileDiffIdentity, @@ -28,10 +30,10 @@ export type MobileDiffReviewQueueFilter = export type MobileDiffReviewQueueItem = { key: string scope: DiffReviewScope - area: MobileGitStagingArea | 'branch' + area: GitStagingArea | 'branch' filePath: string oldPath?: string - status: MobileGitFileStatus + status: GitFileStatus title: string subtitle: string added?: number @@ -51,8 +53,8 @@ export type MobileDiffReviewQueueItem = { export type BuildMobileDiffReviewQueueInput = { worktreeId: string - statusEntries: readonly MobileGitStatusEntry[] - branchEntries: readonly MobileGitBranchChangeEntry[] + statusEntries: readonly GitStatusEntry[] + branchEntries: readonly GitBranchChangeEntry[] branchHeadOid?: string | null branchMergeBase?: string | null comments: readonly DiffComment[] @@ -65,20 +67,20 @@ const SCOPE_SORT_ORDER: Record = { branch: 2 } -function scopeForStatusArea(area: MobileGitStagingArea): DiffReviewScope { +function scopeForStatusArea(area: GitStagingArea): DiffReviewScope { return area === 'staged' ? 'staged' : 'unstaged' } export function createMobileDiffReviewFileKey( scope: DiffReviewScope, - area: MobileGitStagingArea | 'branch', + area: GitStagingArea | 'branch', filePath: string, oldPath?: string ): string { return [scope, area, oldPath ?? '', filePath].join('\0') } -function statusEntryIdentity(entry: MobileGitStatusEntry, scope: DiffReviewScope): string { +function statusEntryIdentity(entry: GitStatusEntry, scope: DiffReviewScope): string { return buildMobileDiffIdentity([ scope, entry.area, @@ -92,7 +94,7 @@ function statusEntryIdentity(entry: MobileGitStatusEntry, scope: DiffReviewScope } function branchEntryIdentity( - entry: MobileGitBranchChangeEntry, + entry: GitBranchChangeEntry, branchHeadOid: string | null | undefined, branchMergeBase: string | null | undefined ): string { @@ -163,7 +165,7 @@ function queueNoteCounts( } function statusEntryToQueueItem( - entry: MobileGitStatusEntry, + entry: GitStatusEntry, comments: readonly DiffComment[], reviewState: MobileDiffReviewState ): MobileDiffReviewQueueItem { @@ -199,7 +201,7 @@ function statusEntryToQueueItem( } function branchEntryToQueueItem( - entry: MobileGitBranchChangeEntry, + entry: GitBranchChangeEntry, input: BuildMobileDiffReviewQueueInput ): MobileDiffReviewQueueItem { const scope: DiffReviewScope = 'branch' diff --git a/mobile/src/session/mobile-diff-review-rpc.ts b/mobile/src/session/mobile-diff-review-rpc.ts index b3d1f25ed89..1752045c20e 100644 --- a/mobile/src/session/mobile-diff-review-rpc.ts +++ b/mobile/src/session/mobile-diff-review-rpc.ts @@ -1,15 +1,13 @@ import type { - MobileGitBranchChangeEntry, - MobileGitBranchCompareResult, - MobileGitBranchCompareSummary -} from '../source-control/mobile-branch-compare' -import type { - MobileGitFileStatus, - MobileGitStagingArea, - MobileGitStatusEntry, - MobileGitStatusResult, - MobileGitUpstreamStatus -} from '../source-control/mobile-git-status' + GitFileStatus, + GitStagingArea, + GitStatusEntry, + GitStatusResult, + GitUpstreamStatus +} from '../../../src/shared/git-status-types' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' +import type { GitBranchChangeEntry } from '../../../src/shared/git-diff-compare-types' +import type { GitBranchCompareSummary } from '../../../src/shared/git-diff-compare-types' export type MobileReviewGitDiffResult = | { @@ -47,7 +45,7 @@ function readBoolean(value: unknown): boolean | undefined { return typeof value === 'boolean' ? value : undefined } -function readFileStatus(value: unknown): MobileGitFileStatus | null { +function readFileStatus(value: unknown): GitFileStatus | null { return value === 'modified' || value === 'added' || value === 'deleted' || @@ -58,17 +56,17 @@ function readFileStatus(value: unknown): MobileGitFileStatus | null { : null } -function readStagingArea(value: unknown): MobileGitStagingArea | null { +function readStagingArea(value: unknown): GitStagingArea | null { return value === 'staged' || value === 'unstaged' || value === 'untracked' ? value : null } -function readConflictOperation(value: unknown): MobileGitStatusResult['conflictOperation'] { +function readConflictOperation(value: unknown): GitStatusResult['conflictOperation'] { return value === 'merge' || value === 'rebase' || value === 'cherry-pick' || value === 'unknown' ? value : 'unknown' } -function readUpstreamStatus(value: unknown): MobileGitUpstreamStatus | undefined { +function readUpstreamStatus(value: unknown): GitUpstreamStatus | undefined { if (!isRecord(value)) { return undefined } @@ -88,7 +86,7 @@ function readUpstreamStatus(value: unknown): MobileGitUpstreamStatus | undefined } } -function readStatusEntry(value: unknown): MobileGitStatusEntry | null { +function readStatusEntry(value: unknown): GitStatusEntry | null { if (!isRecord(value)) { return null } @@ -117,12 +115,12 @@ function readStatusEntry(value: unknown): MobileGitStatusEntry | null { } } -export function readMobileGitStatusResult(value: unknown): MobileGitStatusResult | null { +export function readMobileGitStatusResult(value: unknown): GitStatusResult | null { if (!isRecord(value) || !Array.isArray(value.entries)) { return null } return { - entries: value.entries.flatMap((entry): MobileGitStatusEntry[] => { + entries: value.entries.flatMap((entry): GitStatusEntry[] => { const parsed = readStatusEntry(entry) return parsed ? [parsed] : [] }), @@ -133,7 +131,7 @@ export function readMobileGitStatusResult(value: unknown): MobileGitStatusResult } } -function readBranchStatus(value: unknown): MobileGitBranchCompareSummary['status'] { +function readBranchStatus(value: unknown): GitBranchCompareSummary['status'] { return value === 'ready' || value === 'invalid-base' || value === 'unborn-head' || @@ -144,7 +142,7 @@ function readBranchStatus(value: unknown): MobileGitBranchCompareSummary['status : 'error' } -function readBranchEntry(value: unknown): MobileGitBranchChangeEntry | null { +function readBranchEntry(value: unknown): GitBranchChangeEntry | null { if (!isRecord(value)) { return null } @@ -162,7 +160,7 @@ function readBranchEntry(value: unknown): MobileGitBranchChangeEntry | null { } } -export function readMobileBranchCompareResult(value: unknown): MobileGitBranchCompareResult | null { +export function readMobileBranchCompareResult(value: unknown): GitBranchCompareResult | null { if (!isRecord(value) || !isRecord(value.summary) || !Array.isArray(value.entries)) { return null } @@ -184,7 +182,7 @@ export function readMobileBranchCompareResult(value: unknown): MobileGitBranchCo status: readBranchStatus(value.summary.status), errorMessage: readString(value.summary.errorMessage) }, - entries: value.entries.flatMap((entry): MobileGitBranchChangeEntry[] => { + entries: value.entries.flatMap((entry): GitBranchChangeEntry[] => { const parsed = readBranchEntry(entry) return parsed ? [parsed] : [] }) diff --git a/mobile/src/session/mobile-diff-review-screen-model.ts b/mobile/src/session/mobile-diff-review-screen-model.ts index ecf09bf3efb..364b75a5648 100644 --- a/mobile/src/session/mobile-diff-review-screen-model.ts +++ b/mobile/src/session/mobile-diff-review-screen-model.ts @@ -1,6 +1,6 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import type { DiffComment, MobileDiffReviewState } from '../../../src/shared/diff-comment-types' -import type { MobileGitBranchCompareResult } from '../source-control/mobile-branch-compare' -import type { MobileGitStatusResult } from '../source-control/mobile-git-status' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' import type { MobileDiffLine } from './mobile-diff-lines' import type { MobileDiffHunk } from './mobile-diff-hunks' import type { @@ -15,8 +15,8 @@ export type ReviewScreenState = | { kind: 'loading' } | { kind: 'ready' - status: MobileGitStatusResult - branchCompare: MobileGitBranchCompareResult | null + status: GitStatusResult + branchCompare: GitBranchCompareResult | null branchError?: string comments: DiffComment[] reviewState: MobileDiffReviewState diff --git a/mobile/src/session/mobile-session-route-types.ts b/mobile/src/session/mobile-session-route-types.ts index 03ddcb1a124..07bbedb31ed 100644 --- a/mobile/src/session/mobile-session-route-types.ts +++ b/mobile/src/session/mobile-session-route-types.ts @@ -3,12 +3,9 @@ import type { DiffComment } from '../../../src/shared/diff-comment-types' import type { TuiAgent } from '../../../src/shared/tui-agent' import type { AgentStatusEntry } from '../../../src/shared/agent-status-types' import type { MobileBrowserTab } from '../browser/MobileBrowserPane' -import type { MobileTerminalTheme } from '../terminal/terminal-webview-contract' +import type { RuntimeMobileTerminalTheme } from '../../../src/shared/runtime-types' import type { MobileDiffLine } from './mobile-diff-lines' import type { MobileHighlightedDiffLine, MobileSyntaxSegment } from './mobile-file-syntax' -import type { TerminalRecord } from './mobile-terminal-records' - -export type Terminal = TerminalRecord export type MobileSessionTabType = 'terminal' | 'markdown' | 'file' | 'browser' | 'agent-session' @@ -28,7 +25,7 @@ export type MobileSessionTab = /** Host-provided launch context still parked as an unsent TUI-input draft. */ launchDraft?: string launchDraftCreatedAt?: number - terminalTheme?: MobileTerminalTheme + terminalTheme?: RuntimeMobileTerminalTheme isActive: boolean } | { diff --git a/mobile/src/session/mobile-terminal-records.ts b/mobile/src/session/mobile-terminal-records.ts index f03a31acf41..c80dd47993b 100644 --- a/mobile/src/session/mobile-terminal-records.ts +++ b/mobile/src/session/mobile-terminal-records.ts @@ -1,10 +1,10 @@ -import type { MobileTerminalTheme } from '../terminal/terminal-webview-contract' +import type { RuntimeMobileTerminalTheme } from '../../../src/shared/runtime-types' import type { AgentStatusEntry } from '../../../src/shared/agent-status-types' export type TerminalRecord = { handle: string title: string - terminalTheme?: MobileTerminalTheme + terminalTheme?: RuntimeMobileTerminalTheme isActive: boolean /** From `terminal.list`; parked and proven-absent leaves report false. */ connected?: boolean @@ -24,7 +24,7 @@ export type MobileTerminalSessionTab = { /** Host-provided launch context still parked as an unsent TUI-input draft. */ launchDraft?: string launchDraftCreatedAt?: number - terminalTheme?: MobileTerminalTheme + terminalTheme?: RuntimeMobileTerminalTheme isActive: boolean } @@ -72,8 +72,8 @@ type MobileSessionTabLike = } export function mobileTerminalThemesEqual( - left: MobileTerminalTheme | null | undefined, - right: MobileTerminalTheme | null | undefined + left: RuntimeMobileTerminalTheme | null | undefined, + right: RuntimeMobileTerminalTheme | null | undefined ): boolean { if (left === right) { return true diff --git a/mobile/src/session/use-mobile-pr-branch-context.test.ts b/mobile/src/session/use-mobile-pr-branch-context.test.ts index 7ac21cf3006..07016535795 100644 --- a/mobile/src/session/use-mobile-pr-branch-context.test.ts +++ b/mobile/src/session/use-mobile-pr-branch-context.test.ts @@ -1,13 +1,13 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { describe, expect, it, vi } from 'vitest' -import type { MobileGitBranchCompareResult } from '../source-control/mobile-branch-compare' -import type { MobileGitStatusResult } from '../source-control/mobile-git-status' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' import { deriveMobilePrBranchContext, loadMobilePrBranchContext, loadMobilePrRepoContext } from './use-mobile-pr-branch-context' -function status(overrides: Partial): MobileGitStatusResult { +function status(overrides: Partial): GitStatusResult { return { entries: [], conflictOperation: 'unknown', @@ -17,7 +17,7 @@ function status(overrides: Partial): MobileGitStatusResul } } -function branchCompare(headOid: string | null): MobileGitBranchCompareResult { +function branchCompare(headOid: string | null): GitBranchCompareResult { return { summary: { baseRef: 'main', diff --git a/mobile/src/session/use-mobile-pr-branch-context.ts b/mobile/src/session/use-mobile-pr-branch-context.ts index d0816b02f71..cb96b8d072b 100644 --- a/mobile/src/session/use-mobile-pr-branch-context.ts +++ b/mobile/src/session/use-mobile-pr-branch-context.ts @@ -1,8 +1,8 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { useEffect, useState } from 'react' import type { ConnectionState } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' -import type { MobileGitBranchCompareResult } from '../source-control/mobile-branch-compare' -import type { MobileGitStatusResult } from '../source-control/mobile-git-status' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' import { resolveMobileBranchCompareBaseRef } from '../source-control/mobile-branch-base-ref' import { fetchGithubRepoSlug } from './github-pr-rpc' import { readMobileBranchCompareResult, readMobileGitStatusResult } from './mobile-diff-review-rpc' @@ -10,7 +10,7 @@ import { readMobileBranchCompareResult, readMobileGitStatusResult } from './mobi export type MobilePrBranchContext = { branch: string | null headSha: string | null - status: MobileGitStatusResult | null + status: GitStatusResult | null isGithubRepo: boolean repoLoaded: boolean loaded: boolean @@ -21,9 +21,9 @@ export type MobilePrBranchContext = { // `status.head ?? branchCompare.summary.headOid ?? null` — a status-only read would lose // the SHA when `status.head` is absent and diverge from the review surface's check status. export function deriveMobilePrBranchContext( - status: MobileGitStatusResult | null, - branchCompare: MobileGitBranchCompareResult | null -): { branch: string | null; headSha: string | null; status: MobileGitStatusResult | null } { + status: GitStatusResult | null, + branchCompare: GitBranchCompareResult | null +): { branch: string | null; headSha: string | null; status: GitStatusResult | null } { return { branch: status?.branch ?? null, headSha: status?.head ?? branchCompare?.summary.headOid ?? null, @@ -172,7 +172,7 @@ export async function loadMobilePrBranchIdentity( async function readGitStatus( client: RpcClient, worktreeId: string -): Promise { +): Promise { const response = await client.sendRequest('git.status', { worktree: `id:${worktreeId}` }) return response.ok ? readMobileGitStatusResult(response.result) : null } @@ -180,7 +180,7 @@ async function readGitStatus( async function readBranchCompare( client: RpcClient, worktreeId: string -): Promise { +): Promise { // branchCompare requires a baseRef; without one (or on error) the headOid fallback is // simply unavailable and headSha relies on status.head. const baseRef = await resolveMobileBranchCompareBaseRef(client, worktreeId) diff --git a/mobile/src/session/use-mobile-session-close-actions.ts b/mobile/src/session/use-mobile-session-close-actions.ts index c2ff6825b0a..521e62841bc 100644 --- a/mobile/src/session/use-mobile-session-close-actions.ts +++ b/mobile/src/session/use-mobile-session-close-actions.ts @@ -1,4 +1,5 @@ -import type { MobileSessionTab, Terminal } from './mobile-session-route-types' +import type { MobileSessionTab } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' import type { MobileSessionContentCreateActionsModel } from './use-mobile-session-content-create-actions' export function useMobileSessionCloseActions(scope: MobileSessionContentCreateActionsModel) { @@ -60,7 +61,7 @@ export function useMobileSessionCloseActions(scope: MobileSessionContentCreateAc } } - async function handleCloseTerminal(target: Terminal) { + async function handleCloseTerminal(target: TerminalRecord) { if (!client) { return } diff --git a/mobile/src/session/use-mobile-session-screen-state.ts b/mobile/src/session/use-mobile-session-screen-state.ts index 6e2f82124b3..40b68a8efdb 100644 --- a/mobile/src/session/use-mobile-session-screen-state.ts +++ b/mobile/src/session/use-mobile-session-screen-state.ts @@ -20,16 +20,16 @@ import type { MarkdownDocState, MobileDisplayMode, MobileNewTabAgentLoadState, - MobileSessionTab, - Terminal + MobileSessionTab } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' import { useMobileSessionTabActionTargets } from './use-mobile-session-tab-action-targets' import type { MobileSessionFoundationModel } from './use-mobile-session-foundation' export function useMobileSessionScreenState(scope: MobileSessionFoundationModel) { const { worktreeId, hostId, initialCreateWarning } = scope - const [terminals, setTerminals] = useState([]) - const terminalsRef = useRef([]) + const [terminals, setTerminals] = useState([]) + const terminalsRef = useRef([]) const [sessionTabs, setSessionTabs] = useState([]) const sessionTabsRef = useRef([]) // Why: track the last applied (epoch, version) so a late older snapshot can't overwrite a newer one and resurrect closed tabs (session-tab-snapshot-gate). @@ -97,7 +97,7 @@ export function useMobileSessionScreenState(scope: MobileSessionFoundationModel) { type: 'markdown' } > | null>(null) const [leaveDrafts, setLeaveDrafts] = useState(null) - const [renameTarget, setRenameTarget] = useState(null) + const [renameTarget, setRenameTarget] = useState(null) const [customKeys, setCustomKeys] = useState([]) const [visibleBuiltInIds, setVisibleBuiltInIds] = useState( getDefaultTerminalAccessoryBuiltInIds diff --git a/mobile/src/session/use-mobile-session-tab-action-targets.ts b/mobile/src/session/use-mobile-session-tab-action-targets.ts index caf41d3bd19..8a736ae5d07 100644 --- a/mobile/src/session/use-mobile-session-tab-action-targets.ts +++ b/mobile/src/session/use-mobile-session-tab-action-targets.ts @@ -5,7 +5,8 @@ import { type MutableRefObject, type SetStateAction } from 'react' -import type { MobileSessionTab, Terminal } from './mobile-session-route-types' +import type { MobileSessionTab } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' type MarkdownTab = Extract type FileTab = Extract @@ -14,7 +15,7 @@ type AgentSessionTab = Extract type SetActionTarget = Dispatch> export function useMobileSessionTabActionTargets() { - const [actionTarget, setActionTarget] = useState(null) + const [actionTarget, setActionTarget] = useState(null) const [markdownActionTarget, setMarkdownActionTarget] = useState(null) const [fileActionTarget, setFileActionTarget] = useState(null) const [browserActionTarget, setBrowserActionTarget] = useState(null) @@ -38,7 +39,7 @@ export function useMobileSessionTabActionTargets() { export function useMobileSessionTabActionSheetOpener(args: { activeHandleRef: MutableRefObject - setActionTarget: SetActionTarget + setActionTarget: SetActionTarget setMarkdownActionTarget: SetActionTarget setFileActionTarget: SetActionTarget setBrowserActionTarget: SetActionTarget diff --git a/mobile/src/session/use-mobile-session-terminal-create-actions.ts b/mobile/src/session/use-mobile-session-terminal-create-actions.ts index 1daa3eb1fa5..4dd30be8900 100644 --- a/mobile/src/session/use-mobile-session-terminal-create-actions.ts +++ b/mobile/src/session/use-mobile-session-terminal-create-actions.ts @@ -8,7 +8,8 @@ import { buildTerminalSendParams } from '../terminal/terminal-send-request' import { terminalRecordsEqual } from './mobile-terminal-records' import type { MobileNewTabAgentOption } from './mobile-new-tab-agent-options' import type { TerminalQuickCommand } from '../../../src/shared/terminal-quick-command-types' -import type { Terminal, TerminalCreateResult } from './mobile-session-route-types' +import type { TerminalCreateResult } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' import type { MobileSessionAttachmentsModel } from './use-mobile-session-attachments' import { isAgentSessionHandleProvider } from '../../../src/shared/agent-session-provider-handle' import { createMobileStructuredAgentSession } from './mobile-structured-agent-session-launch' @@ -145,7 +146,7 @@ export function useMobileSessionTerminalCreateActions(scope: MobileSessionAttach setActiveHandle(createdHandle) setTerminals((prev) => { const existing = prev.find((terminal) => terminal.handle === createdHandle) - const createdTerminal: Terminal = { + const createdTerminal: TerminalRecord = { handle: createdHandle, title: created.title || existing?.title || 'Terminal', terminalTheme: created.terminalTheme ?? existing?.terminalTheme, diff --git a/mobile/src/session/use-mobile-session-terminal-input.ts b/mobile/src/session/use-mobile-session-terminal-input.ts index 3f6e417e23a..b3f5af641c8 100644 --- a/mobile/src/session/use-mobile-session-terminal-input.ts +++ b/mobile/src/session/use-mobile-session-terminal-input.ts @@ -19,7 +19,8 @@ import { TERMINAL_GESTURE_INPUT_MAX_QUEUE_AGE_MS, TERMINAL_GESTURE_INPUT_REFILL_PER_SECOND } from './mobile-session-route-helpers' -import type { Terminal, TerminalGestureInputQueue } from './mobile-session-route-types' +import type { TerminalGestureInputQueue } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' import type { MobileSessionFileActionsModel } from './use-mobile-session-file-actions' export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsModel) { @@ -228,7 +229,7 @@ export function useMobileSessionTerminalInput(scope: MobileSessionFileActionsMod }) }, []) - async function handleClearTerminal(target: Terminal) { + async function handleClearTerminal(target: TerminalRecord) { if (!client) { return } diff --git a/mobile/src/session/use-mobile-session-terminal-list.ts b/mobile/src/session/use-mobile-session-terminal-list.ts index 105677533de..f218ee2df56 100644 --- a/mobile/src/session/use-mobile-session-terminal-list.ts +++ b/mobile/src/session/use-mobile-session-terminal-list.ts @@ -6,7 +6,7 @@ import { pruneTerminalKeyboardMetrics, resolveRetainedTerminalHandles } from './mobile-terminal-prune-decision' -import type { Terminal } from './mobile-session-route-types' +import type { TerminalRecord } from './mobile-terminal-records' import type { MobileSessionTerminalStreamDisplayModel } from './use-mobile-session-terminal-stream-display' import { MobileTerminalInventoryRequest } from './mobile-terminal-inventory-request' import type { MobileTerminalInventoryRefreshOptions } from './use-mobile-terminal-inventory-recovery' @@ -61,7 +61,7 @@ export function useMobileSessionTerminalList(scope: MobileSessionTerminalStreamD if (!isCurrent() || !response.ok) { return false } - const result = (response as RpcSuccess).result as { terminals: Terminal[] } + const result = (response as RpcSuccess).result as { terminals: TerminalRecord[] } if (result.terminals.length === 0 && !allowsEmpty()) { return true } diff --git a/mobile/src/settings/voice-settings-operations.ts b/mobile/src/settings/voice-settings-operations.ts index fc5fced208d..12e785b6139 100644 --- a/mobile/src/settings/voice-settings-operations.ts +++ b/mobile/src/settings/voice-settings-operations.ts @@ -1,12 +1,12 @@ -import type { MobileSpeechSetup } from '../dictation/mobile-dictation-setup' +import type { RuntimeSpeechSetupState } from '../../../src/shared/runtime-types' export interface VoiceSettingsOperations { - load(): Promise + load(): Promise configure(params: { enabled?: boolean modelId?: string dictationMode?: 'toggle' | 'hold' - }): Promise + }): Promise download(modelId: string): Promise - delete(modelId: string): Promise + delete(modelId: string): Promise } diff --git a/mobile/src/settings/voice-settings-screen.tsx b/mobile/src/settings/voice-settings-screen.tsx index 1e2c33c7a4c..b4e3a7338b9 100644 --- a/mobile/src/settings/voice-settings-screen.tsx +++ b/mobile/src/settings/voice-settings-screen.tsx @@ -1,3 +1,4 @@ +import type { RuntimeSpeechSetupState } from '../../../src/shared/runtime-types' import { useCallback, useRef, useState } from 'react' import { ActivityIndicator, Pressable, ScrollView, Switch, Text, View } from 'react-native' import { useSafeAreaInsets } from 'react-native-safe-area-context' @@ -8,11 +9,7 @@ import { colors, spacing } from '../theme/mobile-theme' import { BottomDrawer } from '../components/BottomDrawer' import { VoiceModelList } from '../components/VoiceModelList' import { useDictationSetupPoller } from '../dictation/use-dictation-setup-poller' -import { - isModelInFlight, - type MobileSpeechModel, - type MobileSpeechSetup -} from '../dictation/mobile-dictation-setup' +import { isModelInFlight, type MobileSpeechModel } from '../dictation/mobile-dictation-setup' const POLL_INTERVAL_MS = 1500 @@ -33,7 +30,7 @@ export default function VoiceSettingsScreen({ onBack: () => void }): React.JSX.Element { const insets = useSafeAreaInsets() - const [setup, setSetup] = useState(null) + const [setup, setSetup] = useState(null) const [loading, setLoading] = useState(true) const [error, setError] = useState(null) const [busyAction, setBusyAction] = useState(null) diff --git a/mobile/src/source-control/mobile-branch-compare.test.ts b/mobile/src/source-control/mobile-branch-compare.test.ts index cb7a5d7e86a..12e463baef8 100644 --- a/mobile/src/source-control/mobile-branch-compare.test.ts +++ b/mobile/src/source-control/mobile-branch-compare.test.ts @@ -1,17 +1,11 @@ -import { describe, expect, expectTypeOf, it } from 'vitest' -import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' +import { describe, expect, it } from 'vitest' import { buildMobileBranchCompareSection, canOpenMobileBranchCompareDiff, - formatMobileBranchCompareSummary, - type MobileGitBranchCompareResult + formatMobileBranchCompareSummary } from './mobile-branch-compare' describe('mobile branch compare helpers', () => { - it('keeps the mobile branch compare type in lockstep with the runtime contract', () => { - expectTypeOf().toEqualTypeOf() - }) - it('sorts committed branch entries by path', () => { const section = buildMobileBranchCompareSection([ { path: 'zeta.ts', status: 'modified' }, diff --git a/mobile/src/source-control/mobile-branch-compare.ts b/mobile/src/source-control/mobile-branch-compare.ts index 0cbd7bdab79..f0f221b8fae 100644 --- a/mobile/src/source-control/mobile-branch-compare.ts +++ b/mobile/src/source-control/mobile-branch-compare.ts @@ -1,21 +1,15 @@ import type { GitBranchChangeEntry, - GitBranchCompareResult, GitBranchCompareSummary } from '../../../src/shared/git-diff-compare-types' -export type MobileGitBranchChangeEntry = GitBranchChangeEntry -export type MobileGitBranchCompareSummary = GitBranchCompareSummary -export type MobileGitBranchCompareResult = GitBranchCompareResult +export type MobileBranchCompareSection = + { + title: 'Committed on Branch' + data: TEntry[] + } -export type MobileBranchCompareSection< - TEntry extends MobileGitBranchChangeEntry = MobileGitBranchChangeEntry -> = { - title: 'Committed on Branch' - data: TEntry[] -} - -export function buildMobileBranchCompareSection( +export function buildMobileBranchCompareSection( entries: readonly TEntry[] ): MobileBranchCompareSection | null { if (entries.length === 0) { @@ -32,9 +26,7 @@ export function buildMobileBranchCompareSection[] + stagedEntries: Pick[] } export type RecordMobileCommitFailure = (failure: MobileCommitFailureRecovery | null) => void export function getMobileCommitFailureStagedEntries( - entries: readonly MobileGitStatusEntry[] | undefined -): Pick[] { + entries: readonly GitStatusEntry[] | undefined +): Pick[] { return (entries ?? []) .filter((entry) => entry.area === 'staged') .map((entry) => ({ path: entry.path, status: entry.status, area: entry.area })) diff --git a/mobile/src/source-control/mobile-git-read-operations.ts b/mobile/src/source-control/mobile-git-read-operations.ts index 3b815f3eb5b..1400ab67b22 100644 --- a/mobile/src/source-control/mobile-git-read-operations.ts +++ b/mobile/src/source-control/mobile-git-read-operations.ts @@ -1,3 +1,4 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { bindDeferredRpcOperation, defineRpcOperation } from '../transport/rpc-operation' import type { RpcCompatibleReader } from '../transport/rpc-operation-contract' import { @@ -5,7 +6,6 @@ import { rpcUncheckedPayloadReader } from '../transport/rpc-reader-payload' import { readMobileGitStatusResult } from '../session/mobile-diff-review-rpc' -import type { MobileGitStatusResult } from './mobile-git-status' // Source-control reads. Every one of these replies used to be re-typed with a cast at the call // site; the reader below is now the only place that says what the payload is. @@ -32,7 +32,7 @@ export const gitStatusHostPayloadRead = bindDeferredRpcOperation( const gitStatusProjectionReader: RpcCompatibleReader< unknown, 'normalized-status', - MobileGitStatusResult | null + GitStatusResult | null > = (raw) => ({ compatible: true, variant: 'normalized-status', diff --git a/mobile/src/source-control/mobile-git-status.test.ts b/mobile/src/source-control/mobile-git-status.test.ts index a9e2b554f91..6ca4aa3fdd0 100644 --- a/mobile/src/source-control/mobile-git-status.test.ts +++ b/mobile/src/source-control/mobile-git-status.test.ts @@ -1,5 +1,5 @@ -import { describe, expect, expectTypeOf, it } from 'vitest' -import type { GitStatusResult } from '../../../src/shared/git-status-types' +import { describe, expect, it } from 'vitest' +import type { GitStatusEntry } from '../../../src/shared/git-status-types' import { buildMobileSourceControlSections, canOpenMobileGitStatusEntry, @@ -10,22 +10,16 @@ import { isMobileGitDiscardableEntry, isMobileGitStageableEntry, isMobileGitTransientRefreshError, - isMobileGitUnavailable, - type MobileGitStatusEntry, - type MobileGitStatusResult + isMobileGitUnavailable } from './mobile-git-status' -const entries: MobileGitStatusEntry[] = [ +const entries: GitStatusEntry[] = [ { path: 'b.ts', status: 'modified', area: 'staged' }, { path: 'a.ts', status: 'modified', area: 'unstaged' }, { path: 'new.ts', status: 'untracked', area: 'untracked' } ] describe('mobile source control status helpers', () => { - it('keeps the mobile RPC status type in lockstep with the shared git contract', () => { - expectTypeOf().toEqualTypeOf() - }) - it('builds sections in the mobile source control order', () => { const sections = buildMobileSourceControlSections(entries) @@ -44,7 +38,7 @@ describe('mobile source control status helpers', () => { }) it('keeps unresolved conflicts out of stage actions', () => { - const conflictedEntries: MobileGitStatusEntry[] = [ + const conflictedEntries: GitStatusEntry[] = [ { path: 'ready.ts', status: 'modified', area: 'unstaged' }, { path: 'conflicted.ts', diff --git a/mobile/src/source-control/mobile-git-status.ts b/mobile/src/source-control/mobile-git-status.ts index 522d4873fc9..807eecb0832 100644 --- a/mobile/src/source-control/mobile-git-status.ts +++ b/mobile/src/source-control/mobile-git-status.ts @@ -1,34 +1,25 @@ import type { GitFileStatus, GitStagingArea, - GitStatusEntry, - GitStatusResult, - GitUpstreamStatus + GitStatusEntry } from '../../../src/shared/git-status-types' import type { RpcResponse } from '../transport/types' -export type MobileGitFileStatus = GitFileStatus -export type MobileGitStagingArea = GitStagingArea -export type MobileGitStatusEntry = GitStatusEntry -export type MobileGitUpstreamStatus = GitUpstreamStatus -export type MobileGitStatusResult = GitStatusResult +export type MobileSourceControlSection = { + area: GitStagingArea + title: string + data: TEntry[] +} -export type MobileSourceControlSection = - { - area: MobileGitStagingArea - title: string - data: TEntry[] - } +const AREA_ORDER: GitStagingArea[] = ['unstaged', 'untracked', 'staged'] -const AREA_ORDER: MobileGitStagingArea[] = ['unstaged', 'untracked', 'staged'] - -const AREA_TITLES: Record = { +const AREA_TITLES: Record = { unstaged: 'Changes', untracked: 'Untracked Files', staged: 'Staged Changes' } -export const MOBILE_GIT_STATUS_LABELS: Record = { +export const MOBILE_GIT_STATUS_LABELS: Record = { modified: 'M', added: 'A', deleted: 'D', @@ -37,7 +28,7 @@ export const MOBILE_GIT_STATUS_LABELS: Record = { copied: 'C' } -function getConflictSortRank(entry: MobileGitStatusEntry): number { +function getConflictSortRank(entry: GitStatusEntry): number { if (entry.conflictStatus === 'unresolved') { return 0 } @@ -47,7 +38,7 @@ function getConflictSortRank(entry: MobileGitStatusEntry): number { return 2 } -export function buildMobileSourceControlSections( +export function buildMobileSourceControlSections( entries: readonly TEntry[] ): MobileSourceControlSection[] { const sections = AREA_ORDER.map((area) => ({ @@ -67,36 +58,36 @@ export function buildMobileSourceControlSections entry.area === 'staged').length } -export function countUnstagedEntries(entries: readonly MobileGitStatusEntry[]): number { +export function countUnstagedEntries(entries: readonly GitStatusEntry[]): number { return entries.filter((entry) => entry.area === 'unstaged' || entry.area === 'untracked').length } -export function getStageablePaths(entries: readonly MobileGitStatusEntry[]): string[] { +export function getStageablePaths(entries: readonly GitStatusEntry[]): string[] { return entries.filter(isMobileGitStageableEntry).map((entry) => entry.path) } -export function getUnstageablePaths(entries: readonly MobileGitStatusEntry[]): string[] { +export function getUnstageablePaths(entries: readonly GitStatusEntry[]): string[] { return entries.filter((entry) => entry.area === 'staged').map((entry) => entry.path) } -export function isMobileGitStageableEntry(entry: MobileGitStatusEntry): boolean { +export function isMobileGitStageableEntry(entry: GitStatusEntry): boolean { return ( (entry.area === 'unstaged' || entry.area === 'untracked') && entry.conflictStatus !== 'unresolved' ) } -export function isMobileGitDiscardableEntry(entry: MobileGitStatusEntry): boolean { +export function isMobileGitDiscardableEntry(entry: GitStatusEntry): boolean { return entry.conflictStatus !== 'unresolved' && entry.conflictStatus !== 'resolved_locally' } // Why: unresolved conflicts are not a stable file to open. Deletions are — // git.diff still returns the pre-delete side (text or image via modifiedDeleted). -export function canOpenMobileGitStatusEntry(entry: MobileGitStatusEntry): boolean { +export function canOpenMobileGitStatusEntry(entry: GitStatusEntry): boolean { return entry.conflictStatus !== 'unresolved' } diff --git a/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts b/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts index 26087d34541..221c9df74df 100644 --- a/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts +++ b/mobile/src/source-control/mobile-hosted-review-create-intent-runner.ts @@ -1,11 +1,11 @@ -import type { MobileGitStatusResult } from './mobile-git-status' +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { createMobilePr, getMobilePrCreateBlockMessage, getMobilePrCreateSuccessWarning, - shouldPushBeforeMobilePrCreate, - type MobilePrPrefill + shouldPushBeforeMobilePrCreate } from './mobile-pr-create' +import type { MobileHostedReviewPrefill } from './mobile-hosted-review-service' import { prepareMobileHostedReviewCreateIntent, type MobileHostedReviewCreateIntentProgress @@ -15,7 +15,7 @@ import type { MobileSourceControlRpcSender } from './mobile-source-control-rpc-s type RunInput = { branch: string title: string - status: MobileGitStatusResult | null + status: GitStatusResult | null commitMessage?: string onProgress?: (progress: MobileHostedReviewCreateIntentProgress) => void } @@ -25,15 +25,15 @@ export type MobileHostedReviewCreateIntentRunOutcome = ok: true url: string warning?: string - prefill: MobilePrPrefill - status: MobileGitStatusResult | null + prefill: MobileHostedReviewPrefill + status: GitStatusResult | null committed: boolean } | { ok: false error: string committed?: boolean - status?: MobileGitStatusResult | null + status?: GitStatusResult | null commitMessage?: string } diff --git a/mobile/src/source-control/mobile-hosted-review-create-intent.ts b/mobile/src/source-control/mobile-hosted-review-create-intent.ts index f056e1eeaab..ce9a2003225 100644 --- a/mobile/src/source-control/mobile-hosted-review-create-intent.ts +++ b/mobile/src/source-control/mobile-hosted-review-create-intent.ts @@ -1,7 +1,9 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { requestMobileCommitMessage } from './mobile-commit-message-ai' -import { getStageablePaths, type MobileGitStatusResult } from './mobile-git-status' +import { getStageablePaths } from './mobile-git-status' import { getMobilePrEligibilityReadiness } from './mobile-open-pr-prefill' -import { resolveMobilePrPrefill, type MobilePrPrefill } from './mobile-pr-create' +import { resolveMobilePrPrefill } from './mobile-pr-create' +import type { MobileHostedReviewPrefill } from './mobile-hosted-review-service' import { commitMobileHostedReviewStagedChanges, mobileHostedReviewBranchStillMatches, @@ -24,15 +26,15 @@ type MobileHostedReviewCreateIntentFailure = { ok: false error: string committed?: boolean - status?: MobileGitStatusResult | null + status?: GitStatusResult | null commitMessage?: string } export type MobileHostedReviewCreateIntentOutcome = | { ok: true - prefill: MobilePrPrefill - status: MobileGitStatusResult | null + prefill: MobileHostedReviewPrefill + status: GitStatusResult | null committed: boolean } | MobileHostedReviewCreateIntentFailure @@ -40,7 +42,7 @@ export type MobileHostedReviewCreateIntentOutcome = type PrepareInput = { branch: string title: string - status: MobileGitStatusResult | null + status: GitStatusResult | null commitMessage?: string onProgress?: (progress: MobileHostedReviewCreateIntentProgress) => void } @@ -66,7 +68,7 @@ export function mobileHostedReviewCreateIntentProgressMessage( } } -function hasUnresolvedConflicts(status: MobileGitStatusResult | null): boolean { +function hasUnresolvedConflicts(status: GitStatusResult | null): boolean { return status?.entries.some((entry) => entry.conflictStatus === 'unresolved') === true } @@ -75,8 +77,8 @@ async function resolvePrefillFromStatus( worktreeId: string, branch: string, title: string, - status: MobileGitStatusResult | null -): Promise { + status: GitStatusResult | null +): Promise { return resolveMobilePrPrefill(client, worktreeId, { branch, title, @@ -88,9 +90,9 @@ async function ensureLocalChangesCommitted( client: MobileSourceControlRpcSender, worktreeId: string, input: PrepareInput, - currentStatus: MobileGitStatusResult | null + currentStatus: GitStatusResult | null ): Promise< - | { ok: true; status: MobileGitStatusResult | null; committed: boolean } + | { ok: true; status: GitStatusResult | null; committed: boolean } | MobileHostedReviewCreateIntentFailure > { if ((currentStatus?.entries.length ?? 0) === 0) { diff --git a/mobile/src/source-control/mobile-hosted-review-git-preparation.ts b/mobile/src/source-control/mobile-hosted-review-git-preparation.ts index 518a8724254..22c19cb2dce 100644 --- a/mobile/src/source-control/mobile-hosted-review-git-preparation.ts +++ b/mobile/src/source-control/mobile-hosted-review-git-preparation.ts @@ -1,3 +1,4 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import type { RpcSendParams } from '../transport/rpc-params-contract' import { hostReplyErrorTextOrFallback, @@ -6,11 +7,10 @@ import { import type { RpcResponse } from '../transport/types' import { gitBulkStageRun, gitCommitRun, gitPushRun } from './mobile-git-mutation-operations' import { gitStatusProjectionRead } from './mobile-git-read-operations' -import type { MobileGitStatusResult } from './mobile-git-status' import type { MobileSourceControlRpcSender } from './mobile-source-control-rpc-sender' export type MobileHostedReviewStatusReadResult = - | { ok: true; status: MobileGitStatusResult | null } + | { ok: true; status: GitStatusResult | null } | { ok: false; error: string } export type MobileHostedReviewMutationResult = { ok: true } | { ok: false; error: string } @@ -32,7 +32,7 @@ export async function readMobileHostedReviewGitStatus( export function mobileHostedReviewBranchStillMatches( inputBranch: string, - status: MobileGitStatusResult | null + status: GitStatusResult | null ): boolean { const branch = status?.branch return Boolean(branch && (branch === inputBranch || branch === `refs/heads/${inputBranch}`)) diff --git a/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts b/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts index 8ddbd344069..3dbb7c69c09 100644 --- a/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts +++ b/mobile/src/source-control/mobile-hosted-review-remote-prerequisite.ts @@ -1,18 +1,18 @@ -import type { MobileGitStatusResult } from './mobile-git-status' +import type { GitStatusResult } from '../../../src/shared/git-status-types' import type { MobileHostedReviewCreateIntentProgress } from './mobile-hosted-review-create-intent' -import type { MobilePrPrefill } from './mobile-pr-create' +import type { MobileHostedReviewPrefill } from './mobile-hosted-review-service' import { pushMobileHostedReviewBranch } from './mobile-hosted-review-git-preparation' import type { MobileSourceControlRpcSender } from './mobile-source-control-rpc-sender' type RemotePrerequisiteInput = { - status: MobileGitStatusResult | null + status: GitStatusResult | null onProgress?: (progress: MobileHostedReviewCreateIntentProgress) => void } export async function applyMobileHostedReviewRemotePrerequisite( client: MobileSourceControlRpcSender, worktreeId: string, - prefill: MobilePrPrefill, + prefill: MobileHostedReviewPrefill, input: RemotePrerequisiteInput ): Promise<{ ok: true; ran: boolean } | { ok: false; error: string }> { const worktree = `id:${worktreeId}` diff --git a/mobile/src/source-control/mobile-open-pr-prefill.test.ts b/mobile/src/source-control/mobile-open-pr-prefill.test.ts index cf977b0b2b2..7a7b3ca9cff 100644 --- a/mobile/src/source-control/mobile-open-pr-prefill.test.ts +++ b/mobile/src/source-control/mobile-open-pr-prefill.test.ts @@ -1,8 +1,8 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { describe, expect, it, vi } from 'vitest' import { getMobilePrEligibilityReadiness, readFreshGitStatus } from './mobile-open-pr-prefill' -import type { MobileGitStatusResult } from './mobile-git-status' -const fallback = { branch: 'old', entries: [] } as unknown as MobileGitStatusResult +const fallback = { branch: 'old', entries: [] } as unknown as GitStatusResult describe('readFreshGitStatus', () => { it('returns the freshly-read status when parseable', async () => { @@ -43,7 +43,7 @@ describe('getMobilePrEligibilityReadiness', () => { const status = { entries: [{ path: 'a.ts' }], upstreamStatus: { hasUpstream: true, ahead: 2, behind: 1 } - } as unknown as MobileGitStatusResult + } as unknown as GitStatusResult expect(getMobilePrEligibilityReadiness(status)).toEqual({ hasUncommittedChanges: true, diff --git a/mobile/src/source-control/mobile-open-pr-prefill.ts b/mobile/src/source-control/mobile-open-pr-prefill.ts index 2369095f328..dc1632d35d0 100644 --- a/mobile/src/source-control/mobile-open-pr-prefill.ts +++ b/mobile/src/source-control/mobile-open-pr-prefill.ts @@ -1,12 +1,12 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { readMobileGitStatusResult } from '../session/mobile-diff-review-rpc' -import type { MobileGitStatusResult } from './mobile-git-status' // Refresh after a push when possible so readiness reflects the new upstream state. export async function readFreshGitStatus( worktreeId: string, - fallback: MobileGitStatusResult | null, + fallback: GitStatusResult | null, sendGitRequest: (method: string, params?: Record) => Promise -): Promise { +): Promise { try { const fresh = await sendGitRequest('git.status', { worktree: `id:${worktreeId}` }) return readMobileGitStatusResult(fresh) ?? fallback @@ -15,7 +15,7 @@ export async function readFreshGitStatus( } } -export function getMobilePrEligibilityReadiness(status: MobileGitStatusResult | null): { +export function getMobilePrEligibilityReadiness(status: GitStatusResult | null): { hasUncommittedChanges?: boolean hasUpstream?: boolean ahead?: number diff --git a/mobile/src/source-control/mobile-path-sort.test.ts b/mobile/src/source-control/mobile-path-sort.test.ts index ffc18d5aa0d..a4f88731e8b 100644 --- a/mobile/src/source-control/mobile-path-sort.test.ts +++ b/mobile/src/source-control/mobile-path-sort.test.ts @@ -1,7 +1,8 @@ +import type { GitStatusEntry } from '../../../src/shared/git-status-types' import { describe, expect, it, vi } from 'vitest' import { buildMobileDiffReviewQueue } from '../session/mobile-diff-review-queue' import { buildMobileBranchCompareSection } from './mobile-branch-compare' -import { buildMobileSourceControlSections, type MobileGitStatusEntry } from './mobile-git-status' +import { buildMobileSourceControlSections } from './mobile-git-status' const paths = [ 'file10.ts', @@ -37,7 +38,7 @@ const reviewInput = { reviewState: { version: 1 as const, files: {} } } const comparePath = (a: string, b: string) => a.localeCompare(b, undefined, { numeric: true }) -const conflictRank = (entry: MobileGitStatusEntry) => +const conflictRank = (entry: GitStatusEntry) => entry.conflictStatus === 'unresolved' ? 0 : entry.conflictStatus === 'resolved_locally' ? 1 : 2 describe('mobile path sort collation', () => { diff --git a/mobile/src/source-control/mobile-pr-create.test.ts b/mobile/src/source-control/mobile-pr-create.test.ts index deb36b9e9f0..27e4f9bc634 100644 --- a/mobile/src/source-control/mobile-pr-create.test.ts +++ b/mobile/src/source-control/mobile-pr-create.test.ts @@ -8,9 +8,9 @@ import { getMobilePrCreateBlockMessage, mobileRepoSelectorFromWorktreeId, resolveMobilePrPrefill, - shouldPushBeforeMobilePrCreate, - type MobilePrPrefill + shouldPushBeforeMobilePrCreate } from './mobile-pr-create' +import type { MobileHostedReviewPrefill } from './mobile-hosted-review-service' function ok(result: unknown): RpcSuccess { return { id: 'r', ok: true, result, _meta: { runtimeId: 'rt' } } @@ -236,7 +236,8 @@ describe('mobile create form gating parity', () => { title: 'Add feature', body: '', canCreate: false, - blockedReason: 'future_desktop_reason' as unknown as MobilePrPrefill['blockedReason'] + blockedReason: + 'future_desktop_reason' as unknown as MobileHostedReviewPrefill['blockedReason'] }) ).toBe('This branch is not ready for a pull request yet.') }) diff --git a/mobile/src/source-control/mobile-pr-create.ts b/mobile/src/source-control/mobile-pr-create.ts index 65a70a63ed9..b10922ef1fd 100644 --- a/mobile/src/source-control/mobile-pr-create.ts +++ b/mobile/src/source-control/mobile-pr-create.ts @@ -5,17 +5,10 @@ import { mobileRepoSelectorFromWorktreeId, resolveMobileHostedReviewPrefill, shouldPushBeforeMobileHostedReviewCreate, - type MobileHostedReviewCreateInput, type MobileHostedReviewCreateOutcome, - type MobileHostedReviewEligibilityInput, type MobileHostedReviewPrefill } from './mobile-hosted-review-service' -export type MobilePrEligibilityInput = MobileHostedReviewEligibilityInput -export type MobilePrPrefill = MobileHostedReviewPrefill -export type MobilePrCreateInput = MobileHostedReviewCreateInput -export type MobilePrCreateOutcome = MobileHostedReviewCreateOutcome - export { buildMobileHostedReviewCreateParams as buildMobilePrCreateParams, createMobileHostedReview as createMobilePr, @@ -25,8 +18,8 @@ export { } export function getMobilePrCreateSuccessWarning( - outcome: Extract, - provider: MobilePrPrefill['provider'] + outcome: Extract, + provider: MobileHostedReviewPrefill['provider'] ): string | undefined { const copy = hostedReviewCopy(provider) if (outcome.existing) { @@ -40,7 +33,7 @@ export function getMobilePrCreateSuccessWarning( return undefined } -export function getMobilePrCreateBlockMessage(prefill: MobilePrPrefill): string | null { +export function getMobilePrCreateBlockMessage(prefill: MobileHostedReviewPrefill): string | null { const copy = hostedReviewCopy(prefill.provider) if (prefill.canCreate !== false || shouldPushBeforeMobileHostedReviewCreate(prefill)) { // Fail closed: only an accepted no-review lookup (`not_found`) may open diff --git a/mobile/src/source-control/mobile-review-route.ts b/mobile/src/source-control/mobile-review-route.ts index ee0939a7c74..ccc833d104d 100644 --- a/mobile/src/source-control/mobile-review-route.ts +++ b/mobile/src/source-control/mobile-review-route.ts @@ -1,6 +1,6 @@ -import type { MobileGitStagingArea } from './mobile-git-status' +import type { GitStagingArea } from '../../../src/shared/git-status-types' -export type MobileReviewRouteArea = MobileGitStagingArea | 'branch' +export type MobileReviewRouteArea = GitStagingArea | 'branch' export type MobileReviewRouteTarget = { hostId: string diff --git a/mobile/src/source-control/mobile-source-control-actions.test.ts b/mobile/src/source-control/mobile-source-control-actions.test.ts index 8dc281a4019..1904c072c28 100644 --- a/mobile/src/source-control/mobile-source-control-actions.test.ts +++ b/mobile/src/source-control/mobile-source-control-actions.test.ts @@ -1,5 +1,5 @@ +import type { GitUpstreamStatus } from '../../../src/shared/git-status-types' import { describe, expect, it, vi } from 'vitest' -import type { MobileGitUpstreamStatus } from './mobile-git-status' import { buildMobileSourceControlActions, type MobileSourceControlActionArgs @@ -30,7 +30,7 @@ function args( return { commitMessage: 'msg', stagedCount: 1, - upstream: { hasUpstream: true, ahead: 0, behind: 0 } as MobileGitUpstreamStatus, + upstream: { hasUpstream: true, ahead: 0, behind: 0 } as GitUpstreamStatus, upstreamKnown: true, busyAction: null, openingPath: null, @@ -67,14 +67,14 @@ describe('buildMobileSourceControlActions', () => { it('disables fast-forward when ahead of upstream (would lose local commits)', () => { const actions = buildMobileSourceControlActions( - args({ upstream: { hasUpstream: true, ahead: 2, behind: 3 } as MobileGitUpstreamStatus }) + args({ upstream: { hasUpstream: true, ahead: 2, behind: 3 } as GitUpstreamStatus }) ) expect(action(actions, 'Fast-forward')?.disabled).toBe(true) }) it('enables fast-forward when behind and not ahead', () => { const actions = buildMobileSourceControlActions( - args({ upstream: { hasUpstream: true, ahead: 0, behind: 3 } as MobileGitUpstreamStatus }) + args({ upstream: { hasUpstream: true, ahead: 0, behind: 3 } as GitUpstreamStatus }) ) expect(action(actions, 'Fast-forward')?.disabled).toBe(false) }) diff --git a/mobile/src/source-control/mobile-source-control-actions.ts b/mobile/src/source-control/mobile-source-control-actions.ts index b06af8a6aab..05780ee8eff 100644 --- a/mobile/src/source-control/mobile-source-control-actions.ts +++ b/mobile/src/source-control/mobile-source-control-actions.ts @@ -1,4 +1,4 @@ -import type { MobileGitUpstreamStatus } from './mobile-git-status' +import type { GitUpstreamStatus } from '../../../src/shared/git-status-types' // Icon identifier resolved to a lucide component by the screen. Kept as a string // here so this module stays free of the native lucide import and unit-testable. @@ -27,7 +27,7 @@ export type MobileSourceControlAction = { export type MobileSourceControlActionArgs = { commitMessage: string stagedCount: number - upstream: MobileGitUpstreamStatus | null + upstream: GitUpstreamStatus | null upstreamKnown: boolean busyAction: string | null openingPath: string | null diff --git a/mobile/src/source-control/mobile-source-control-primary-action.test.ts b/mobile/src/source-control/mobile-source-control-primary-action.test.ts index 8314ecb2d80..59342cceeac 100644 --- a/mobile/src/source-control/mobile-source-control-primary-action.test.ts +++ b/mobile/src/source-control/mobile-source-control-primary-action.test.ts @@ -1,10 +1,10 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { describe, expect, it, vi } from 'vitest' import { buildMobileSourceControlPrimaryAction, type MobileSourceControlPrimaryActionArgs, type MobileSourceControlPrimaryActionHandlers } from './mobile-source-control-primary-action' -import type { MobileGitStatusResult } from './mobile-git-status' function handlers(): MobileSourceControlPrimaryActionHandlers { return { @@ -15,7 +15,7 @@ function handlers(): MobileSourceControlPrimaryActionHandlers { } } -function status(overrides: Partial = {}): MobileGitStatusResult { +function status(overrides: Partial = {}): GitStatusResult { return { entries: [], conflictOperation: 'unknown', diff --git a/mobile/src/source-control/mobile-source-control-primary-action.ts b/mobile/src/source-control/mobile-source-control-primary-action.ts index 2a993967b52..b78fc941003 100644 --- a/mobile/src/source-control/mobile-source-control-primary-action.ts +++ b/mobile/src/source-control/mobile-source-control-primary-action.ts @@ -1,16 +1,14 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { resolveSourceControlCommitAreaPrimaryActionDecision, type SourceControlCommitAreaPrimaryActionDecision, type SourceControlRemoteOpKind } from '../../../src/shared/source-control-primary-action-decision' -import type { MobileGitBranchCompareResult } from './mobile-branch-compare' -import type { MobileGitStatusResult } from './mobile-git-status' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' type GitStep = { method: string; params?: Record } type MobileSourceControlPrimaryActionKind = SourceControlCommitAreaPrimaryActionDecision['kind'] -type MobileSourceControlPrimaryActionDecision = SourceControlCommitAreaPrimaryActionDecision -type MobileSourceControlRemoteOpKind = SourceControlRemoteOpKind export type MobileSourceControlPrimaryAction = { kind: MobileSourceControlPrimaryActionKind @@ -31,7 +29,7 @@ export type MobileSourceControlPrimaryActionHandlers = { } export type MobileSourceControlPrimaryActionArgs = { - status: MobileGitStatusResult | null + status: GitStatusResult | null hasUnresolvedConflicts: boolean stageablePaths: readonly string[] stagedCount: number @@ -40,7 +38,7 @@ export type MobileSourceControlPrimaryActionArgs = { busyAction: string | null openingPath: string | null openingBranchPath: string | null - branchCompareResult: MobileGitBranchCompareResult | null + branchCompareResult: GitBranchCompareResult | null handlers: MobileSourceControlPrimaryActionHandlers } @@ -88,9 +86,7 @@ function isMobileRemoteOperationActive(busyAction: string | null): boolean { return getInFlightRemoteOpKind(busyAction) !== null } -function getInFlightRemoteOpKind( - busyAction: string | null -): MobileSourceControlRemoteOpKind | null { +function getInFlightRemoteOpKind(busyAction: string | null): SourceControlRemoteOpKind | null { switch (busyAction) { case 'push': case 'commit-push': @@ -127,7 +123,9 @@ function getMobileBranchCommitsAhead( return upstream?.hasUpstream ? upstream.ahead : undefined } -function getMobilePrimaryActionLabel(decision: MobileSourceControlPrimaryActionDecision): string { +function getMobilePrimaryActionLabel( + decision: SourceControlCommitAreaPrimaryActionDecision +): string { if (decision.requiresForceWithLease) { return 'Force Push' } @@ -147,7 +145,9 @@ function getMobilePrimaryActionLabel(decision: MobileSourceControlPrimaryActionD } } -function getMobilePrimaryActionHint(decision: MobileSourceControlPrimaryActionDecision): string { +function getMobilePrimaryActionHint( + decision: SourceControlCommitAreaPrimaryActionDecision +): string { switch (decision.titleIntent) { case 'commit_in_progress': return 'Commit in progress.' @@ -194,7 +194,7 @@ function getMobilePrimaryActionHint(decision: MobileSourceControlPrimaryActionDe } function isLoadingDecision( - decision: MobileSourceControlPrimaryActionDecision, + decision: SourceControlCommitAreaPrimaryActionDecision, busyAction: string | null ): boolean { switch (decision.kind) { @@ -219,7 +219,7 @@ function isLoadingDecision( } async function runMobilePrimaryAction( - decision: MobileSourceControlPrimaryActionDecision, + decision: SourceControlCommitAreaPrimaryActionDecision, handlers: MobileSourceControlPrimaryActionHandlers ): Promise { switch (decision.kind) { diff --git a/mobile/src/source-control/mobile-source-control-screen-state.ts b/mobile/src/source-control/mobile-source-control-screen-state.ts index 78a588d19ff..061e6bad55c 100644 --- a/mobile/src/source-control/mobile-source-control-screen-state.ts +++ b/mobile/src/source-control/mobile-source-control-screen-state.ts @@ -1,3 +1,8 @@ +import type { + GitFileStatus, + GitStatusEntry, + GitStatusResult +} from '../../../src/shared/git-status-types' import { ArrowDown, ArrowDownUp, @@ -14,23 +19,18 @@ import { colors } from '../theme/mobile-theme' import type { MobileSourceControlActionIcon } from './mobile-source-control-actions' import type { MobileDiffLine } from '../session/mobile-diff-lines' import type { MobileHighlightedDiffLine } from '../session/mobile-file-syntax' -import type { - MobileGitBranchChangeEntry, - MobileGitBranchCompareResult, - MobileGitBranchCompareSummary -} from './mobile-branch-compare' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' +import type { GitBranchChangeEntry } from '../../../src/shared/git-diff-compare-types' +import type { GitBranchCompareSummary } from '../../../src/shared/git-diff-compare-types' import { canOpenMobileGitStatusEntry, isMobileGitDiscardableEntry, - isMobileGitStageableEntry, - type MobileGitFileStatus, - type MobileGitStatusEntry, - type MobileGitStatusResult + isMobileGitStageableEntry } from './mobile-git-status' export type ScreenState = | { kind: 'loading' } - | { kind: 'ready'; status: MobileGitStatusResult } + | { kind: 'ready'; status: GitStatusResult } | { kind: 'unavailable'; message: string } | { kind: 'error'; message: string } @@ -49,7 +49,7 @@ export type StatusLoadInFlight = { export type GitRequestError = Error & { code?: string } export type GitCommitResult = { success: boolean; error?: string } -export type MobileGitStatusEntryView = MobileGitStatusEntry & { +export type MobileGitStatusEntryView = GitStatusEntry & { canDiscard: boolean canOpen: boolean canStage: boolean @@ -61,7 +61,7 @@ export type MobileGitStatusEntryView = MobileGitStatusEntry & { // Decorate raw status entries with the row-level capability/action-id fields the // file list needs. Opener guards must use the same canOpen rule. export function buildMobileGitStatusEntryViews( - entries: readonly MobileGitStatusEntry[] + entries: readonly GitStatusEntry[] ): MobileGitStatusEntryView[] { return entries.map((entry) => ({ ...entry, @@ -77,23 +77,23 @@ export function buildMobileGitStatusEntryViews( export type MobileBranchCompareState = | { kind: 'idle' } | { kind: 'loading' } - | { kind: 'ready'; result: MobileGitBranchCompareResult } + | { kind: 'ready'; result: GitBranchCompareResult } | { kind: 'error'; message: string } -export type MobileBranchEntryView = MobileGitBranchChangeEntry & { +export type MobileBranchEntryView = GitBranchChangeEntry & { canOpen: boolean } export type MobileBranchDiffPreviewState = - | { kind: 'loading'; entry: MobileGitBranchChangeEntry } + | { kind: 'loading'; entry: GitBranchChangeEntry } | { kind: 'ready' - entry: MobileGitBranchChangeEntry - summary: MobileGitBranchCompareSummary + entry: GitBranchChangeEntry + summary: GitBranchCompareSummary lines: MobileHighlightedDiffLine[] truncated: boolean } - | { kind: 'error'; entry: MobileGitBranchChangeEntry; message: string } + | { kind: 'error'; entry: GitBranchChangeEntry; message: string } export type GitDiffTextResult = { kind: 'text' @@ -134,7 +134,7 @@ export function formatBranchLabel(branch: string | undefined, head: string | und return branch || head?.slice(0, 7) || 'No branch' } -export function statusColor(status: MobileGitFileStatus): string { +export function statusColor(status: GitFileStatus): string { switch (status) { case 'added': case 'copied': diff --git a/mobile/src/source-control/use-mobile-create-pr-runner.ts b/mobile/src/source-control/use-mobile-create-pr-runner.ts index b7fb9ce4145..890c4383761 100644 --- a/mobile/src/source-control/use-mobile-create-pr-runner.ts +++ b/mobile/src/source-control/use-mobile-create-pr-runner.ts @@ -1,7 +1,7 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { useCallback, type MutableRefObject } from 'react' import type { RpcClient } from '../transport/rpc-client' import { triggerError } from '../platform/haptics' -import type { MobileGitStatusResult } from './mobile-git-status' import type { LoadStatusOptions } from './mobile-source-control-screen-state' import { getMobileCommitFailureStagedEntries, @@ -24,7 +24,7 @@ type LoadStatus = (options?: LoadStatusOptions) => Promise type Params = { client: RpcClient | null worktreeId: string - status: MobileGitStatusResult | null + status: GitStatusResult | null branchLabel: string commitMessage: string stagedEntries: MobileCommitFailureRecovery['stagedEntries'] diff --git a/mobile/src/source-control/use-mobile-git-requests.ts b/mobile/src/source-control/use-mobile-git-requests.ts index 9783d83ac39..6d6eb6caa7d 100644 --- a/mobile/src/source-control/use-mobile-git-requests.ts +++ b/mobile/src/source-control/use-mobile-git-requests.ts @@ -1,11 +1,8 @@ +import type { GitStatusResult, GitUpstreamStatus } from '../../../src/shared/git-status-types' import { useCallback } from 'react' import type { ConnectionState, RpcSuccess } from '../transport/types' import type { RpcClient } from '../transport/rpc-client' -import { - isMobileGitUnavailable, - type MobileGitStatusResult, - type MobileGitUpstreamStatus -} from './mobile-git-status' +import { isMobileGitUnavailable } from './mobile-git-status' import type { GitCommitResult, GitRequestError } from './mobile-source-control-screen-state' type Params = { @@ -49,16 +46,16 @@ export function useMobileGitRequests({ client, connState, worktreeId }: Params) [sendGitRequest] ) - const readUpstreamStatusForSync = useCallback(async (): Promise => { + const readUpstreamStatusForSync = useCallback(async (): Promise => { try { - return await sendGitRequest('git.upstreamStatus') + return await sendGitRequest('git.upstreamStatus') } catch (err) { const code = err instanceof Error ? (err as GitRequestError).code : undefined const message = err instanceof Error ? err.message : String(err) if (!isMobileGitUnavailable(code, message)) { throw err } - const status = await sendGitRequest('git.status') + const status = await sendGitRequest('git.status') if (!status.upstreamStatus) { throw new Error('Branch status unavailable') } diff --git a/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts b/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts index 49ec9bda83a..1740a57114e 100644 --- a/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts +++ b/mobile/src/source-control/use-mobile-source-control-create-pr-action.ts @@ -1,14 +1,14 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { useMemo } from 'react' import { buildMobileCreatePrAction } from './mobile-create-pr-action' import { useMobileHostedReviewEligibility } from './use-mobile-hosted-review-eligibility' -import type { MobileGitStatusResult } from './mobile-git-status' type Params = { client: Parameters[0]['client'] connState: Parameters[0]['connState'] hostId: string worktreeId: string - status: MobileGitStatusResult | null + status: GitStatusResult | null hasUncommittedChanges: boolean busyAction: string | null createPr: (pushFirst: boolean) => void diff --git a/mobile/src/source-control/use-mobile-source-control-loaders.ts b/mobile/src/source-control/use-mobile-source-control-loaders.ts index da03e8639b5..323e7d755ab 100644 --- a/mobile/src/source-control/use-mobile-source-control-loaders.ts +++ b/mobile/src/source-control/use-mobile-source-control-loaders.ts @@ -1,3 +1,4 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { useCallback, useEffect, useRef, useState, type MutableRefObject } from 'react' import { View } from 'react-native' import type { RpcClient } from '../transport/rpc-client' @@ -8,10 +9,9 @@ import { gitBranchCompareRead, gitStatusHostPayloadRead } from './mobile-git-rea import { isMobileGitTransientRefreshError, isMobileGitUnavailableReply, - readMobileGitRefusal, - type MobileGitStatusResult + readMobileGitRefusal } from './mobile-git-status' -import type { MobileGitBranchCompareResult } from './mobile-branch-compare' +import type { GitBranchCompareResult } from '../../../src/shared/git-diff-compare-types' import { SELECTOR_RETRY_COUNT, SELECTOR_RETRY_DELAY_MS, @@ -147,7 +147,7 @@ export function useMobileSourceControlLoaders(params: Params): MobileSourceContr setBranchCompareState({ kind: 'ready', // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Preserve the established response shape at this boundary. - result: compared as MobileGitBranchCompareResult + result: compared as GitBranchCompareResult }) return true } catch (err) { @@ -215,7 +215,7 @@ export function useMobileSourceControlLoaders(params: Params): MobileSourceContr const refusal = readMobileGitRefusal(reply) if (!refusal) { // oxlint-disable-next-line typescript/consistent-type-assertions -- SAFETY: Preserve the established response shape at this boundary. - const result = gitStatusHostPayloadRead.interpret(reply) as MobileGitStatusResult + const result = gitStatusHostPayloadRead.interpret(reply) as GitStatusResult setScreenState({ kind: 'ready', status: result }) void loadBranchCompare({ preserveReadyOnFailure: true }) if (options?.clearActionErrorOnSuccess !== false) { diff --git a/mobile/src/source-control/use-mobile-source-control-openers.ts b/mobile/src/source-control/use-mobile-source-control-openers.ts index 4d92964c803..3c5dead3145 100644 --- a/mobile/src/source-control/use-mobile-source-control-openers.ts +++ b/mobile/src/source-control/use-mobile-source-control-openers.ts @@ -1,3 +1,4 @@ +import type { GitStatusEntry } from '../../../src/shared/git-status-types' import { useCallback, useRef, useState, type MutableRefObject } from 'react' import { useRouter } from 'expo-router' import type { RpcClient } from '../transport/rpc-client' @@ -9,16 +10,10 @@ import { highlightMobileDiffLines, resolveMobileSyntaxLanguage } from '../session/mobile-file-syntax' -import { - canOpenMobileBranchCompareDiff, - type MobileGitBranchChangeEntry -} from './mobile-branch-compare' +import { canOpenMobileBranchCompareDiff } from './mobile-branch-compare' +import type { GitBranchChangeEntry } from '../../../src/shared/git-diff-compare-types' import { gitBranchDiffRead } from './mobile-git-read-operations' -import { - canOpenMobileGitStatusEntry, - isMobileGitUnavailableReply, - type MobileGitStatusEntry -} from './mobile-git-status' +import { canOpenMobileGitStatusEntry, isMobileGitUnavailableReply } from './mobile-git-status' import { sourceFileDiffOpenRun, sourceFileOpenRun } from './mobile-source-file-open-operations' import { buildMobileReviewFileRoute } from './mobile-review-route' import { revealMobileSourceControlSessionDiff } from './reveal-mobile-source-control-session-diff' @@ -77,7 +72,7 @@ export function useMobileSourceControlOpeners(params: Params) { const openingBranchPathRef = useRef(null) const openFile = useCallback( - async (entry: MobileGitStatusEntry) => { + async (entry: GitStatusEntry) => { // Deletions are openable (pre-delete text/image via git.diff); only block // unresolved conflicts, matching canOpenMobileGitStatusEntry / row UI. if (!canOpenMobileGitStatusEntry(entry)) { @@ -202,7 +197,7 @@ export function useMobileSourceControlOpeners(params: Params) { ) const openBranchDiff = useCallback( - async (entry: MobileGitBranchChangeEntry) => { + async (entry: GitBranchChangeEntry) => { if (openingBranchPathRef.current || openingPathRef.current || busyActionRef.current) { return } diff --git a/mobile/src/source-control/use-mobile-source-control-runners.ts b/mobile/src/source-control/use-mobile-source-control-runners.ts index 026a706a5f9..0715ecf0287 100644 --- a/mobile/src/source-control/use-mobile-source-control-runners.ts +++ b/mobile/src/source-control/use-mobile-source-control-runners.ts @@ -1,3 +1,4 @@ +import type { GitStatusResult } from '../../../src/shared/git-status-types' import { useCallback, type MutableRefObject } from 'react' import { useRouter } from 'expo-router' import type { RpcClient } from '../transport/rpc-client' @@ -7,7 +8,6 @@ import { useMobileSourceControlCommitRunners } from './use-mobile-source-control import { useMobileSourceControlActionSheetRunners } from './use-mobile-source-control-action-sheet-runners' import { useMobileCreatePrRunner } from './use-mobile-create-pr-runner' import type { RuntimeGitLocalBranches } from '../../../src/shared/runtime-types' -import type { MobileGitStatusResult } from './mobile-git-status' import type { LoadStatusOptions } from './mobile-source-control-screen-state' import type { MobileCommitFailureRecovery, @@ -21,7 +21,7 @@ type Params = { client: RpcClient | null hostId: string worktreeId: string - status: MobileGitStatusResult | null + status: GitStatusResult | null branchLabel: string commitMessage: string stagedEntries: MobileCommitFailureRecovery['stagedEntries'] diff --git a/mobile/src/source-control/use-mobile-source-control-state.ts b/mobile/src/source-control/use-mobile-source-control-state.ts index 1f541739d9a..5f20f4eccf1 100644 --- a/mobile/src/source-control/use-mobile-source-control-state.ts +++ b/mobile/src/source-control/use-mobile-source-control-state.ts @@ -1,3 +1,4 @@ +import type { GitStatusEntry } from '../../../src/shared/git-status-types' import { useCallback, useMemo, useRef, useState } from 'react' import { useSafeAreaInsets } from 'react-native-safe-area-context' import { useHostClient, useForceReconnect } from '../transport/client-context' @@ -21,8 +22,7 @@ import { countStagedEntries, countUnstagedEntries, getStageablePaths, - getUnstageablePaths, - type MobileGitStatusEntry + getUnstageablePaths } from './mobile-git-status' import { getMobileCommitFailureStagedEntries } from './mobile-commit-failure-recovery' import { useMobileSourceControlCommitFailure } from './use-mobile-source-control-commit-failure' @@ -32,8 +32,6 @@ import { type MobileBranchEntryView } from './mobile-source-control-screen-state' -type MobileGitLocalBranches = RuntimeGitLocalBranches - export type MobileSourceControlStateParams = { hostId: string worktreeId: string @@ -67,10 +65,10 @@ export function useMobileSourceControlState(params: MobileSourceControlStatePara const [commitMessage, setCommitMessage] = useState('') const [generatingMessage, setGeneratingMessage] = useState(false) const [showBranchPicker, setShowBranchPicker] = useState(false) - const [localBranches, setLocalBranches] = useState(null) + const [localBranches, setLocalBranches] = useState(null) const [createdPrUrl, setCreatedPrUrl] = useState(null) const [createdPrWarning, setCreatedPrWarning] = useState(null) - const [discardTarget, setDiscardTarget] = useState(null) + const [discardTarget, setDiscardTarget] = useState(null) const [showActionSheet, setShowActionSheet] = useState(false) const [actionError, setActionError] = useState(null) const keyboardLift = useMobileSourceControlKeyboardLift() diff --git a/mobile/src/tasks/blank-workspace-create.ts b/mobile/src/tasks/blank-workspace-create.ts index ea3c827c3b9..62d6ee3133d 100644 --- a/mobile/src/tasks/blank-workspace-create.ts +++ b/mobile/src/tasks/blank-workspace-create.ts @@ -2,11 +2,8 @@ import type { TuiAgent } from '../../../src/shared/tui-agent' import type { RpcClient } from '../transport/rpc-client' import { createWorktreeWithNameRetry, type WorktreeCreateResult } from './worktree-create-retry' import type { WorktreeCreateIdempotencyProbe } from './worktree-create-idempotency-policy' -import { - agentLaunchCreateFields, - type WorkspaceCreateParams, - type WorkspaceCreateSetupDecision -} from './workspace-create-params' +import { agentLaunchCreateFields, type WorkspaceCreateParams } from './workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' // The blank/named create path, extracted from NewWorktreeModal so the modal keeps // only the UI-coupled setup-trust flow. Assembles worktree.create params and @@ -17,7 +14,7 @@ export async function createBlankWorkspace(args: { baseName: string createdWithAgentId: TuiAgent | undefined comment: string | undefined - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision /** True when `baseName` is a generated creature name rather than one the user typed; only then * may the host retire it. */ nameWasGenerated: boolean diff --git a/mobile/src/tasks/composer-linked-work-item.ts b/mobile/src/tasks/composer-linked-work-item.ts index 13d5c845b37..ee0794b88ef 100644 --- a/mobile/src/tasks/composer-linked-work-item.ts +++ b/mobile/src/tasks/composer-linked-work-item.ts @@ -16,7 +16,7 @@ import type { MobileLinkedWorkItem, SmartNameSelection } from './mobile-composer-source-types' -import type { WorkspaceCreateGitPushTarget } from './workspace-create-params' +import type { GitPushTarget } from '../../../src/shared/worktree/types' export function buildGitHubLinkedWorkItem(item: { type: 'issue' | 'pr' @@ -85,7 +85,7 @@ export function resolveComposerCreateSelection(args: { base: { baseBranch?: string compareBaseRef?: string - pushTarget?: WorkspaceCreateGitPushTarget + pushTarget?: GitPushTarget branchNameOverride?: string } branch: { refName: string; localBranchName: string } | null diff --git a/mobile/src/tasks/github-check-summary.ts b/mobile/src/tasks/github-check-summary.ts index 413c41c7c31..bb55e18969b 100644 --- a/mobile/src/tasks/github-check-summary.ts +++ b/mobile/src/tasks/github-check-summary.ts @@ -6,10 +6,8 @@ export type GitHubCheckLike = { conclusion?: string | null } -export type GitHubCheckSummary = ProviderCheckSummary - // Why: reuse the desktop classifier verbatim — a second copy is what let mobile call `skipped` // unresolved while desktop called the same PR green. -export function buildGitHubCheckSummary(checks: GitHubCheckLike[]): GitHubCheckSummary { +export function buildGitHubCheckSummary(checks: GitHubCheckLike[]): ProviderCheckSummary { return summarizeProviderChecks(checks) } diff --git a/mobile/src/tasks/github-project-reference.ts b/mobile/src/tasks/github-project-reference.ts index 5ed7ef22ead..a5303d6e2e2 100644 --- a/mobile/src/tasks/github-project-reference.ts +++ b/mobile/src/tasks/github-project-reference.ts @@ -1,14 +1,13 @@ import type { GitHubProjectIdentity } from '../../../src/shared/github/project-identity' export type GitHubProjectOwnerType = GitHubProjectIdentity['ownerType'] -export type GitHubProjectRef = GitHubProjectIdentity export type GitHubProjectSettings = { - pinned: GitHubProjectRef[] - recent: Array + pinned: GitHubProjectIdentity[] + recent: Array lastViewByProject: Record - activeProject: GitHubProjectRef | null + activeProject: GitHubProjectIdentity | null } -export type GitHubProjectSummary = GitHubProjectRef & { +export type GitHubProjectSummary = GitHubProjectIdentity & { id: string title: string url: string diff --git a/mobile/src/tasks/mobile-composer-source-types.ts b/mobile/src/tasks/mobile-composer-source-types.ts index d36215189af..31c9e2f3c9d 100644 --- a/mobile/src/tasks/mobile-composer-source-types.ts +++ b/mobile/src/tasks/mobile-composer-source-types.ts @@ -3,14 +3,14 @@ import type { WorkspaceSourceLinkedItem, WorkspaceSourceSelection } from '../../../src/shared/new-workspace/workspace-source' -import type { WorkspaceCreateGitPushTarget } from './workspace-create-params' +import type { GitPushTarget } from '../../../src/shared/worktree/types' export type { SmartNameMode } export type ComposerBaseState = { baseBranch?: string compareBaseRef?: string - pushTarget?: WorkspaceCreateGitPushTarget + pushTarget?: GitPushTarget branchNameOverride?: string } @@ -48,7 +48,7 @@ export type MobileComposerCreateSelection = item: MobileLinkedWorkItem baseBranch?: string compareBaseRef?: string - pushTarget?: WorkspaceCreateGitPushTarget + pushTarget?: GitPushTarget branchNameOverride?: string } | { diff --git a/mobile/src/tasks/mobile-linear-group-sorted.test.ts b/mobile/src/tasks/mobile-linear-group-sorted.test.ts index fbd87f9c68e..27e365b1132 100644 --- a/mobile/src/tasks/mobile-linear-group-sorted.test.ts +++ b/mobile/src/tasks/mobile-linear-group-sorted.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import type { LinearIssue } from './mobile-tasks-provider-detail-types' +import type { LinearMobileIssue } from './mobile-tasks-provider-detail-types' import { sortLinearIssues, groupLinearIssues, @@ -9,7 +9,7 @@ import { vi.mock('./mobile-tasks-dependencies', () => import('../theme/mobile-theme')) afterEach(() => vi.restoreAllMocks()) -const issues: LinearIssue[] = Array.from({ length: 60 }, (_, i) => ({ +const issues: LinearMobileIssue[] = Array.from({ length: 60 }, (_, i) => ({ id: `${i}`, identifier: ['ENG-10', 'ENG-2', 'Ä-1', 'Å-1', 'é-2', 'e\u0301-2', 'İ-3'][i % 7], title: 'Task', diff --git a/mobile/src/tasks/mobile-linear-sort.test.ts b/mobile/src/tasks/mobile-linear-sort.test.ts index a64809b6f62..5b290d4cc82 100644 --- a/mobile/src/tasks/mobile-linear-sort.test.ts +++ b/mobile/src/tasks/mobile-linear-sort.test.ts @@ -1,5 +1,5 @@ import { afterEach, describe, expect, it, vi } from 'vitest' -import type { LinearIssue } from './mobile-tasks-provider-detail-types' +import type { LinearMobileIssue } from './mobile-tasks-provider-detail-types' import type { LinearOrderBy } from './mobile-tasks-view-state-types' import { groupLinearIssues, sortLinearIssues } from './mobile-tasks-reviewer-linear' import { taskTime } from './mobile-tasks-item-mapping' @@ -8,7 +8,7 @@ import { getLinearPriorityRank } from './mobile-tasks-hosted-review' vi.mock('./mobile-tasks-dependencies', () => import('../theme/mobile-theme')) afterEach(() => vi.restoreAllMocks()) -const issues: LinearIssue[] = Array.from({ length: 60 }, (_, i) => ({ +const issues: LinearMobileIssue[] = Array.from({ length: 60 }, (_, i) => ({ id: `${i}`, identifier: ['ENG-10', 'ENG-2', 'Ä-1', 'Å-1', 'é-2', 'e\u0301-2', 'İ-3'][i % 7], title: 'Task', @@ -21,7 +21,10 @@ const issues: LinearIssue[] = Array.from({ length: 60 }, (_, i) => ({ state: { name: i % 2 ? 'Todo' : 'Done', type: 'started', color: '' }, team: { id: `${i % 3}`, name: `Team ${i % 3}`, key: 'ENG' } })) -function originalSort(input: readonly LinearIssue[], mode: LinearOrderBy): LinearIssue[] { +function originalSort( + input: readonly LinearMobileIssue[], + mode: LinearOrderBy +): LinearMobileIssue[] { return [...input].sort((a, b) => { if (mode === 'updated') { return taskTime(b.updatedAt) - taskTime(a.updatedAt) diff --git a/mobile/src/tasks/mobile-task-navigation.test.ts b/mobile/src/tasks/mobile-task-navigation.test.ts index bacc24be5b8..2f5a9b4e114 100644 --- a/mobile/src/tasks/mobile-task-navigation.test.ts +++ b/mobile/src/tasks/mobile-task-navigation.test.ts @@ -1,12 +1,12 @@ import { describe, expect, it, vi } from 'vitest' +import type { HostStackNavigationState } from '../navigation/host-stack-navigation' import { coordinateMobileTasksNavigation, mobileTasksHostRoute, - navigateToMobileTasks, - type MobileTasksNavigationState + navigateToMobileTasks } from './mobile-task-navigation' -function navigationHarness(initialState: MobileTasksNavigationState) { +function navigationHarness(initialState: HostStackNavigationState) { let stateListener = () => {} let state = initialState const unsubscribeState = vi.fn() @@ -20,7 +20,7 @@ function navigationHarness(initialState: MobileTasksNavigationState) { } return { navigation, - setState(nextState: MobileTasksNavigationState) { + setState(nextState: HostStackNavigationState) { state = nextState stateListener() }, diff --git a/mobile/src/tasks/mobile-task-navigation.ts b/mobile/src/tasks/mobile-task-navigation.ts index f1dc733878e..85c8a556483 100644 --- a/mobile/src/tasks/mobile-task-navigation.ts +++ b/mobile/src/tasks/mobile-task-navigation.ts @@ -4,7 +4,6 @@ import { navigateToHostStackRoute, type HostStackHostRoute, type HostStackNavigationController, - type HostStackNavigationState, type HostStackRootNavigation, type HostStackRouteTarget, type HostStackRouter, @@ -12,14 +11,7 @@ import { } from '../navigation/host-stack-navigation' import type { TaskProvider } from './mobile-task-providers' -export type MobileTasksHostRoute = HostStackHostRoute -export type MobileTasksNavigationState = HostStackNavigationState -export type MobileTasksRootNavigation = HostStackRootNavigation -export type MobileTasksRouter = HostStackRouter -export type MobileTasksNavigationController = HostStackNavigationController -export type PendingMobileTasksNavigation = PendingHostStackNavigation - -export function mobileTasksHostRoute(hostId: string): MobileTasksHostRoute { +export function mobileTasksHostRoute(hostId: string): HostStackHostRoute { return hostStackHostRoute(hostId) } @@ -34,11 +26,11 @@ export function mobileTasksRouteTarget( } export function navigateToMobileTasks( - navigation: MobileTasksRootNavigation, - router: MobileTasksRouter, + navigation: HostStackRootNavigation, + router: HostStackRouter, hostId: string, provider?: TaskProvider -): MobileTasksNavigationController { +): HostStackNavigationController { return navigateToHostStackRoute( navigation, router, @@ -48,12 +40,12 @@ export function navigateToMobileTasks( } export function coordinateMobileTasksNavigation( - current: PendingMobileTasksNavigation | null, - navigation: MobileTasksRootNavigation, - router: MobileTasksRouter, + current: PendingHostStackNavigation | null, + navigation: HostStackRootNavigation, + router: HostStackRouter, hostId: string, provider?: TaskProvider -): PendingMobileTasksNavigation { +): PendingHostStackNavigation { return coordinateHostStackNavigation( current, navigation, diff --git a/mobile/src/tasks/mobile-tasks-dependencies.ts b/mobile/src/tasks/mobile-tasks-dependencies.ts index 0834ed8bfe3..5e78cd6c20c 100644 --- a/mobile/src/tasks/mobile-tasks-dependencies.ts +++ b/mobile/src/tasks/mobile-tasks-dependencies.ts @@ -89,11 +89,11 @@ export { parseGitHubProjectInput as parseProjectInput } from './github-project-r export type { GitHubProjectOwnerType, GitHubProjectPartialFailure, - GitHubProjectRef, GitHubProjectSettings, GitHubProjectSummary, GitHubProjectViewSummary } from './github-project-reference' +export type { GitHubProjectIdentity } from '../../../src/shared/github/project-identity' export { extractGitHubIssueSourceFallback, extractGitHubIssueSourceError diff --git a/mobile/src/tasks/mobile-tasks-item-mapping.ts b/mobile/src/tasks/mobile-tasks-item-mapping.ts index d98a5b4c4de..bedd4efc60f 100644 --- a/mobile/src/tasks/mobile-tasks-item-mapping.ts +++ b/mobile/src/tasks/mobile-tasks-item-mapping.ts @@ -15,7 +15,7 @@ import type { GitHubWorkItem, GitLabTodo, GitLabWorkItem, - LinearIssue, + LinearMobileIssue, RepoSummary } from './mobile-tasks-provider-detail-types' @@ -288,7 +288,7 @@ export async function mapWithConcurrency( return results } -export function createLinearTask(issue: LinearIssue): TaskItem { +export function createLinearTask(issue: LinearMobileIssue): TaskItem { return { key: `linear:${issue.workspaceId ?? 'workspace'}:${issue.id}`, provider: 'linear', diff --git a/mobile/src/tasks/mobile-tasks-options.tsx b/mobile/src/tasks/mobile-tasks-options.tsx index cb4cdbe94f0..793c4be1928 100644 --- a/mobile/src/tasks/mobile-tasks-options.tsx +++ b/mobile/src/tasks/mobile-tasks-options.tsx @@ -22,7 +22,7 @@ import type { TaskSort } from './mobile-tasks-view-state-types' import type { ActionableTaskItem } from './mobile-tasks-project-workspace-types' -import type { DetailComment, LinearIssue } from './mobile-tasks-provider-detail-types' +import type { DetailComment, LinearMobileIssue } from './mobile-tasks-provider-detail-types' export const PROVIDER_OPTIONS: PickerOption[] = [ { @@ -180,12 +180,12 @@ export type LinearIssueSection = { key: string label: string color: string - issues: LinearIssue[] + issues: LinearMobileIssue[] } export type LinearListEntry = | { type: 'section'; section: LinearIssueSection } - | { type: 'issue'; issue: LinearIssue } + | { type: 'issue'; issue: LinearMobileIssue } export const PROJECT_VIEW_DEFAULT_SORT = '__view_default__' diff --git a/mobile/src/tasks/mobile-tasks-project-workspace-types.ts b/mobile/src/tasks/mobile-tasks-project-workspace-types.ts index 7a334a83e67..031b9610b5d 100644 --- a/mobile/src/tasks/mobile-tasks-project-workspace-types.ts +++ b/mobile/src/tasks/mobile-tasks-project-workspace-types.ts @@ -2,7 +2,7 @@ import type { GitHubWorkItem, GitLabWorkItem, GitLabTodo, - LinearIssue, + LinearMobileIssue, SetupDecision } from './mobile-tasks-provider-detail-types' import type { WorkspaceAgentChoice, SparsePreset } from './mobile-tasks-dependencies' @@ -43,7 +43,7 @@ export type TaskItem = subtitle: string status: string updatedAt: string - source: LinearIssue + source: LinearMobileIssue } export type ActionableTaskItem = Exclude diff --git a/mobile/src/tasks/mobile-tasks-provider-detail-types.ts b/mobile/src/tasks/mobile-tasks-provider-detail-types.ts index bd07371c07d..e5698fc669f 100644 --- a/mobile/src/tasks/mobile-tasks-provider-detail-types.ts +++ b/mobile/src/tasks/mobile-tasks-provider-detail-types.ts @@ -151,7 +151,7 @@ export type LinearIssueChild = { url: string } -export type LinearIssue = LinearMobileIssue +export type { LinearMobileIssue } export type LinearState = { id: string diff --git a/mobile/src/tasks/mobile-tasks-reviewer-linear.ts b/mobile/src/tasks/mobile-tasks-reviewer-linear.ts index ed5e51f8bf7..26071e4791e 100644 --- a/mobile/src/tasks/mobile-tasks-reviewer-linear.ts +++ b/mobile/src/tasks/mobile-tasks-reviewer-linear.ts @@ -11,7 +11,7 @@ import type { GitHubAssignableUser, GitHubPRReviewSummary, GitHubRepoSources, - LinearIssue, + LinearMobileIssue, LinearTeam } from './mobile-tasks-provider-detail-types' @@ -84,9 +84,9 @@ export function issueSourceSlug(source: GitHubOwnerRepo | null | undefined): str } export function sortLinearIssues( - issues: readonly LinearIssue[], + issues: readonly LinearMobileIssue[], orderBy: LinearOrderBy -): LinearIssue[] { +): LinearMobileIssue[] { if (issues.length < 2) { return [...issues] } @@ -104,7 +104,7 @@ export function sortLinearIssues( } export function getLinearIssueGroup( - issue: LinearIssue, + issue: LinearMobileIssue, groupBy: LinearGroupBy ): { key: string @@ -135,7 +135,7 @@ export function getLinearIssueGroup( } export function groupLinearIssues( - issues: LinearIssue[], + issues: LinearMobileIssue[], groupBy: LinearGroupBy, orderBy: LinearOrderBy ): LinearIssueSection[] { @@ -144,14 +144,14 @@ export function groupLinearIssues( /** The caller must sort issues by its selected order before grouping. */ export function groupSortedLinearIssues( - issues: readonly LinearIssue[], + issues: readonly LinearMobileIssue[], groupBy: LinearGroupBy ): LinearIssueSection[] { return groupOrderedLinearIssues([...issues], groupBy) } function groupOrderedLinearIssues( - sorted: LinearIssue[], + sorted: LinearMobileIssue[], groupBy: LinearGroupBy ): LinearIssueSection[] { if (groupBy === 'none') { @@ -159,7 +159,7 @@ function groupOrderedLinearIssues( } const sections = new Map< string, - { key: string; label: string; color: string; issues: LinearIssue[] } + { key: string; label: string; color: string; issues: LinearMobileIssue[] } >() for (const issue of sorted) { const group = getLinearIssueGroup(issue, groupBy) @@ -174,7 +174,7 @@ function groupOrderedLinearIssues( } export function linearIssueSecondaryParts( - issue: LinearIssue, + issue: LinearMobileIssue, displayProperties: ReadonlySet ): string[] { const parts = [issue.identifier] diff --git a/mobile/src/tasks/mobile-tasks-view-state-types.ts b/mobile/src/tasks/mobile-tasks-view-state-types.ts index 5e071f229ae..7be094bd72b 100644 --- a/mobile/src/tasks/mobile-tasks-view-state-types.ts +++ b/mobile/src/tasks/mobile-tasks-view-state-types.ts @@ -3,7 +3,7 @@ import type { TuiAgent, TaskProvider, GitHubProjectSettings, - GitHubProjectRef + GitHubProjectIdentity } from './mobile-tasks-dependencies' import type { GitHubProjectSortDirection } from '../../../src/shared/github/project-types' @@ -159,7 +159,7 @@ export type GitHubProjectRow = { } export type GitHubProjectTable = { - project: GitHubProjectRef & { + project: GitHubProjectIdentity & { id: string title: string url: string diff --git a/mobile/src/tasks/source-workspace-create.ts b/mobile/src/tasks/source-workspace-create.ts index 30dd16f89fb..acc30ba7786 100644 --- a/mobile/src/tasks/source-workspace-create.ts +++ b/mobile/src/tasks/source-workspace-create.ts @@ -10,9 +10,9 @@ import { agentLaunchCreateFields, buildTaskWorkspaceCreateParams, type WorkspaceCreateParams, - type WorkspaceCreateSetupDecision, type WorkspaceCreateTaskItem } from './workspace-create-params' +import type { SetupDecision } from '../../../src/shared/worktree/create-types' import { createWorktreeWithNameRetry, type WorktreeCreateResult } from './worktree-create-retry' import type { WorktreeCreateIdempotencyProbe } from './worktree-create-idempotency-policy' @@ -26,7 +26,7 @@ export type CreateWorkspaceFromComposerArgs = { client: RpcClient selection: MobileComposerCreateSelection targetRepoId: string - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision agent: WorkspaceCreateAgentBundle workspaceName: string | undefined nameIsAutoManaged?: boolean @@ -89,7 +89,7 @@ async function createWorkItemWorkspace(args: { client: RpcClient selection: Extract targetRepoId: string - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision agent: WorkspaceCreateAgentBundle workspaceName: string | undefined nameIsAutoManaged?: boolean @@ -148,7 +148,7 @@ async function createBranchWorkspace(args: { client: RpcClient selection: Extract targetRepoId: string - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision agent: WorkspaceCreateAgentBundle workspaceName: string | undefined nameIsAutoManaged?: boolean @@ -235,7 +235,7 @@ async function createNewBranchWorkspace(args: { client: RpcClient selection: Extract targetRepoId: string - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision agent: WorkspaceCreateAgentBundle workspaceName: string | undefined nameIsAutoManaged?: boolean diff --git a/mobile/src/tasks/use-mobile-tasks-item-detail-loading.tsx b/mobile/src/tasks/use-mobile-tasks-item-detail-loading.tsx index 4b401927596..c88eb7ccae5 100644 --- a/mobile/src/tasks/use-mobile-tasks-item-detail-loading.tsx +++ b/mobile/src/tasks/use-mobile-tasks-item-detail-loading.tsx @@ -10,7 +10,7 @@ import { type GitHubDetailCheck, type GitHubDetailFile, type GitHubPRReviewSummary, - type LinearIssue, + type LinearMobileIssue, type TaskItem, createLinearTask, isSuccess @@ -207,7 +207,7 @@ export function useMobileTasksItemDetailLoading(model: ItemDetailMetadataEffects if (!isSuccess(issueResponse)) { throw new Error(issueResponse.error.message) } - const issue = issueResponse.result as LinearIssue | null + const issue = issueResponse.result as LinearMobileIssue | null const comments = isSuccess(commentsResponse) ? ((commentsResponse.result as DetailComment[]) ?? []) : [] diff --git a/mobile/src/tasks/use-mobile-tasks-linear-item-actions.tsx b/mobile/src/tasks/use-mobile-tasks-linear-item-actions.tsx index 021737f5672..f9377b0bb02 100644 --- a/mobile/src/tasks/use-mobile-tasks-linear-item-actions.tsx +++ b/mobile/src/tasks/use-mobile-tasks-linear-item-actions.tsx @@ -2,7 +2,7 @@ import type { GithubReplyMergeActionsModel } from './use-mobile-tasks-github-rep import { useCallback } from './mobile-tasks-dependencies' import { type DetailComment, - type LinearIssue, + type LinearMobileIssue, type LinearIssueChild, type TaskItem, createLinearTask, @@ -87,7 +87,7 @@ export function useMobileTasksLinearItemActions(model: GithubReplyMergeActionsMo if (!isSuccess(response)) { throw new Error(response.error.message) } - const issue = response.result as LinearIssue | null + const issue = response.result as LinearMobileIssue | null if (!issue) { throw new Error('Sub-issue not found') } diff --git a/mobile/src/tasks/use-mobile-tasks-project-loading-actions.tsx b/mobile/src/tasks/use-mobile-tasks-project-loading-actions.tsx index 221134e46ee..dc212cb666f 100644 --- a/mobile/src/tasks/use-mobile-tasks-project-loading-actions.tsx +++ b/mobile/src/tasks/use-mobile-tasks-project-loading-actions.tsx @@ -2,7 +2,7 @@ import type { TaskPaginationActionsModel } from './use-mobile-tasks-task-paginat import { type GitHubProjectOwnerType, type GitHubProjectPartialFailure, - type GitHubProjectRef, + type GitHubProjectIdentity, type GitHubProjectSettings, type GitHubProjectSummary, type GitHubProjectViewSummary, @@ -69,7 +69,7 @@ export function useMobileTasksProjectLoadingActions(model: TaskPaginationActions }, [client, connState, tasksSupported]) const loadGitHubProjectViews = useCallback( - async (project: GitHubProjectRef): Promise => { + async (project: GitHubProjectIdentity): Promise => { if (!client || connState !== 'connected' || !tasksSupported || !taskStateHydrated) { return [] } @@ -163,7 +163,7 @@ export function useMobileTasksProjectLoadingActions(model: TaskPaginationActions ) const commitGitHubProjectView = useCallback( - (project: GitHubProjectRef, viewId: string): void => { + (project: GitHubProjectIdentity, viewId: string): void => { const projectKey = githubProjectKey(project) const nextSettings: GitHubProjectSettings = { ...githubProjectSettings, @@ -186,7 +186,10 @@ export function useMobileTasksProjectLoadingActions(model: TaskPaginationActions ) const selectGitHubProject = useCallback( - async (project: GitHubProjectRef, options: { viewNumber?: number } = {}): Promise => { + async ( + project: GitHubProjectIdentity, + options: { viewNumber?: number } = {} + ): Promise => { if (!tasksSupported || !taskStateHydrated) { return } diff --git a/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx b/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx index 0eb8d3d32f7..0206b2c809c 100644 --- a/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx +++ b/mobile/src/tasks/use-mobile-tasks-provider-view-projection.test.tsx @@ -3,7 +3,7 @@ import { act, create, type ReactTestRenderer } from 'react-test-renderer' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import type { LinearGroupBy, - LinearIssue, + LinearMobileIssue, LinearIssueSection, LinearOrderBy, LinearViewMode, @@ -35,11 +35,15 @@ vi.mock('./mobile-tasks-legacy-foundation', async () => { return { ...options, ...linear, - groupLinearIssues: (issues: LinearIssue[], groupBy: LinearGroupBy, orderBy: LinearOrderBy) => { + groupLinearIssues: ( + issues: LinearMobileIssue[], + groupBy: LinearGroupBy, + orderBy: LinearOrderBy + ) => { groupingInputSizes.push(issues.length) return linear.groupLinearIssues(issues, groupBy, orderBy) }, - groupSortedLinearIssues: (issues: readonly LinearIssue[], groupBy: LinearGroupBy) => { + groupSortedLinearIssues: (issues: readonly LinearMobileIssue[], groupBy: LinearGroupBy) => { groupingInputSizes.push(issues.length) return linear.groupSortedLinearIssues(issues, groupBy) } @@ -70,7 +74,7 @@ const ORDERINGS: LinearOrderBy[] = ['priority', 'updated', 'identifier'] /** Deterministic issues: every field is a pure function of the index, so grouping, * ordering and comparison counts repeat exactly across runs. */ -function makeIssue(index: number): LinearIssue { +function makeIssue(index: number): LinearMobileIssue { const state = STATES[(index * 3) % STATES.length]! const team = TEAMS[(index * 5) % TEAMS.length]! return { @@ -190,7 +194,7 @@ function current(): Projection { /** The pre-change board memo, kept verbatim as the parity and count oracle. */ function legacyProjection(input: ProbeInput): { - issuesForView: LinearIssue[] + issuesForView: LinearMobileIssue[] listSections: LinearIssueSection[] boardSections: LinearIssueSection[] } { diff --git a/mobile/src/tasks/use-mobile-tasks-workspace-and-project-state.tsx b/mobile/src/tasks/use-mobile-tasks-workspace-and-project-state.tsx index 98243128a5f..be5261b4dc0 100644 --- a/mobile/src/tasks/use-mobile-tasks-workspace-and-project-state.tsx +++ b/mobile/src/tasks/use-mobile-tasks-workspace-and-project-state.tsx @@ -2,7 +2,7 @@ import type { RouteAndItemStateModel } from './use-mobile-tasks-route-and-item-s import { type BaseRefSearchResult, type GitHubProjectPartialFailure, - type GitHubProjectRef, + type GitHubProjectIdentity, type GitHubProjectSettings, type GitHubProjectSummary, type GitHubProjectViewSummary, @@ -120,7 +120,7 @@ export function useMobileTasksWorkspaceAndProjectState(model: RouteAndItemStateM const [showGitHubProjectSortPicker, setShowGitHubProjectSortPicker] = useState(false) const [showGitHubProjectFieldsPicker, setShowGitHubProjectFieldsPicker] = useState(false) const [pendingGitHubProjectViewSelection, setPendingGitHubProjectViewSelection] = - useState(null) + useState(null) const [projectRowItem, setProjectRowItem] = useState(null) const [projectRowDetail, setProjectRowDetail] = useState(null) const [projectRowDetailLoading, setProjectRowDetailLoading] = useState(false) diff --git a/mobile/src/tasks/workspace-create-params.ts b/mobile/src/tasks/workspace-create-params.ts index 5f5adbecf5f..ce685d4de16 100644 --- a/mobile/src/tasks/workspace-create-params.ts +++ b/mobile/src/tasks/workspace-create-params.ts @@ -9,13 +9,9 @@ import { getWorkspaceSourceName } from '../../../src/shared/new-workspace/worksp import { resolveMobileWorkspaceCreateName } from './mobile-workspace-name' import type { WorkspaceAgentChoice } from './workspace-agent-selection' -export type WorkspaceCreateSetupDecision = SetupDecision -export type WorkspaceCreateSparseCheckout = CreateSparseCheckoutRequest -export type WorkspaceCreateGitPushTarget = GitPushTarget - export type WorkspaceCreateHostedStartPoint = { baseBranch: string - pushTarget?: WorkspaceCreateGitPushTarget + pushTarget?: GitPushTarget } type WorkspaceCreateGitHubItem = { @@ -78,15 +74,15 @@ export function agentLaunchCreateFields(agentId: TuiAgent | undefined): { export function buildTaskWorkspaceCreateParams(args: { item: WorkspaceCreateTaskItem targetRepoId: string - setupDecision: WorkspaceCreateSetupDecision + setupDecision: SetupDecision agent?: WorkspaceAgentChoice workspaceName?: string note?: string baseBranch?: string compareBaseRef?: string branchNameOverride?: string - pushTarget?: WorkspaceCreateGitPushTarget - sparseCheckout?: WorkspaceCreateSparseCheckout + pushTarget?: GitPushTarget + sparseCheckout?: CreateSparseCheckoutRequest hostedStartPoint?: WorkspaceCreateHostedStartPoint nameIsAutoManaged?: boolean }): WorkspaceCreateParams { diff --git a/mobile/src/terminal/TerminalWebView.tsx b/mobile/src/terminal/TerminalWebView.tsx index d09c0661240..232ee1a6924 100644 --- a/mobile/src/terminal/TerminalWebView.tsx +++ b/mobile/src/terminal/TerminalWebView.tsx @@ -16,168 +16,16 @@ import { dispatchTerminalWebViewNotification } from './terminal-webview-notifica import { routeTerminalQueryReply } from './terminal-webview-query-reply-routing' import { createTerminalWriteCoalescer } from './terminal-write-coalescer' -type Props = TerminalWebViewProps - export type { TerminalWebViewHandle } from './terminal-webview-contract' -export const TerminalWebView = forwardRef(function TerminalWebView( - { - style, - terminalTheme, - textScale = 1, - onWebReady, - onEngineError, - onSelectionMode, - onSelectionCopy, - onSelectionEvicted, - onModesChanged, - onKeyboardAvoidanceMetrics, - onHaptic, - onTerminalInput, - onTerminalQueryReply, - onTerminalTap, - onFileTap, - onOpenUrl, - onTextScaleChange - }, - ref -) { - const webViewRef = useRef(null) - const isWebReadyRef = useRef(false) - const pendingMessages = useMemo(() => createTerminalWebViewPendingMessages(), []) - const messageIdRef = useRef(0) - const pendingPingIdRef = useRef(null) - const terminalThemeKey = useMemo(() => JSON.stringify(terminalTheme ?? null), [terminalTheme]) - const measureResolveRef = useRef< - ((result: { cols: number; rows: number } | null) => void) | null - >(null) - // Why: each init() call posts 'init' to the WebView and arms a fresh - // ready promise. WebView's init() rAF chain ends with a 'ready' notify - // that resolves it. measureFitDimensions awaits this so it doesn't - // race ahead of term.open() / renderService population. - const readyPromiseRef = useRef | null>(null) - const readyResolveRef = useRef<(() => void) | null>(null) - const { clearEngineError, engineError, reportEngineError, reportNativeEngineError } = - useTerminalWebViewEngineErrorState(onEngineError) - const { armWebReadyWatchdog, clearWebReadyWatchdog } = useTerminalWebReadyWatchdog( - isWebReadyRef, - reportEngineError - ) - - const sendToWebView = useCallback((msg: TerminalWebViewCommand) => { - messageIdRef.current += 1 - const id = messageIdRef.current - webViewRef.current?.postMessage(JSON.stringify({ ...msg, id })) - return id - }, []) - - const flushPendingMessages = useCallback(() => { - pendingMessages.flush(sendToWebView) - }, [pendingMessages, sendToWebView]) - - const postMessage = useCallback( - (msg: TerminalWebViewCommand) => { - if (!isWebReadyRef.current) { - pendingMessages.queue(msg) - return - } - sendToWebView(msg) - }, - [pendingMessages, sendToWebView] - ) - - // Why: a busy PTY delivers ~200 stream frames/s; coalescing here collapses the - // per-frame bridge + WebKit IPC + paint cost that runs the phone hot (#9302). - const writeCoalescer = useMemo( - () => createTerminalWriteCoalescer((data) => postMessage({ type: 'write', data })), - [postMessage] - ) - - useEffect(() => { - return () => { - writeCoalescer.clear() - } - }, [writeCoalescer]) - - const confirmWebReady = useCallback( - (notifyParent: boolean) => { - pendingPingIdRef.current = null - isWebReadyRef.current = true - clearWebReadyWatchdog() - clearEngineError() - if (notifyParent) { - onWebReady?.() - } - // Why: reload clears queued commands, so readiness must always restore the - // native-selected theme even when its value did not change in React. - sendToWebView({ type: 'set-theme', terminalTheme }) - flushPendingMessages() - }, - [ - clearEngineError, - clearWebReadyWatchdog, - flushPendingMessages, +export const TerminalWebView = forwardRef( + function TerminalWebView( + { + style, + terminalTheme, + textScale = 1, onWebReady, - sendToWebView, - terminalTheme - ] - ) - - const handleMessage = useCallback( - (event: WebViewMessageEvent) => { - let msg: Record - try { - msg = JSON.parse(event.nativeEvent.data) as Record - } catch { - return - } - routeTerminalQueryReply(msg, onTerminalQueryReply) - - if (msg.type === 'web-ready') { - confirmWebReady(true) - } else if ( - msg.type === 'pong' && - typeof msg.pingId === 'number' && - msg.pingId === pendingPingIdRef.current - ) { - confirmWebReady(false) - } else if (msg.type === 'ready') { - // Why: the WebView's init() rAF chain has run — term is open, - // renderService is populated, first paint has happened. Resolve - // any pending awaitReady() so a queued measure can now safely - // read cell dims. - const resolve = readyResolveRef.current - readyResolveRef.current = null - readyPromiseRef.current = null - resolve?.() - } else if (msg.type === 'measure-result') { - const resolve = measureResolveRef.current - measureResolveRef.current = null - if (resolve) { - const cols = typeof msg.cols === 'number' ? msg.cols : null - const rows = typeof msg.rows === 'number' ? msg.rows : null - resolve(cols && rows && cols >= 20 && rows >= 8 ? { cols, rows } : null) - } - } else { - dispatchTerminalWebViewNotification(msg, { - reportEngineError, - onSelectionMode, - onSelectionCopy, - onSelectionEvicted, - onModesChanged, - onKeyboardAvoidanceMetrics, - onHaptic, - onTerminalInput, - onTerminalTap, - onFileTap, - onOpenUrl, - onTextScaleChange - }) - } - }, - [ - confirmWebReady, - reportEngineError, + onEngineError, onSelectionMode, onSelectionCopy, onSelectionEvicted, @@ -190,207 +38,359 @@ export const TerminalWebView = forwardRef(function onFileTap, onOpenUrl, onTextScaleChange - ] - ) + }, + ref + ) { + const webViewRef = useRef(null) + const isWebReadyRef = useRef(false) + const pendingMessages = useMemo(() => createTerminalWebViewPendingMessages(), []) + const messageIdRef = useRef(0) + const pendingPingIdRef = useRef(null) + const terminalThemeKey = useMemo(() => JSON.stringify(terminalTheme ?? null), [terminalTheme]) + const measureResolveRef = useRef< + ((result: { cols: number; rows: number } | null) => void) | null + >(null) + // Why: each init() call posts 'init' to the WebView and arms a fresh + // ready promise. WebView's init() rAF chain ends with a 'ready' notify + // that resolves it. measureFitDimensions awaits this so it doesn't + // race ahead of term.open() / renderService population. + const readyPromiseRef = useRef | null>(null) + const readyResolveRef = useRef<(() => void) | null>(null) + const { clearEngineError, engineError, reportEngineError, reportNativeEngineError } = + useTerminalWebViewEngineErrorState(onEngineError) + const { armWebReadyWatchdog, clearWebReadyWatchdog } = useTerminalWebReadyWatchdog( + isWebReadyRef, + reportEngineError + ) - const handleLoadStart = useCallback(() => { - isWebReadyRef.current = false - pendingPingIdRef.current = null - armWebReadyWatchdog() - // Why: messages queued for a previous WebView generation are stale after a reload; - // dropping them avoids replaying terminal chunks before the next init snapshot. - pendingMessages.clear() - writeCoalescer.clear() - }, [armWebReadyWatchdog, pendingMessages, writeCoalescer]) + const sendToWebView = useCallback((msg: TerminalWebViewCommand) => { + messageIdRef.current += 1 + const id = messageIdRef.current + webViewRef.current?.postMessage(JSON.stringify({ ...msg, id })) + return id + }, []) - const handleReload = useCallback(() => { - clearEngineError() - webViewRef.current?.reload() - }, [clearEngineError]) + const flushPendingMessages = useCallback(() => { + pendingMessages.flush(sendToWebView) + }, [pendingMessages, sendToWebView]) - const handleContentProcessDidTerminate = useCallback(() => { - // Why: WKWebView content-process loss is recoverable; stale commands belong - // to the dead document and the replacement must prove readiness before replay. - isWebReadyRef.current = false - pendingPingIdRef.current = null - pendingMessages.clear() - writeCoalescer.clear() - clearEngineError() - armWebReadyWatchdog() - webViewRef.current?.reload() - }, [armWebReadyWatchdog, clearEngineError, pendingMessages, writeCoalescer]) - - useEffect(() => { - postMessage({ type: 'set-theme', terminalTheme }) - }, [postMessage, terminalThemeKey, terminalTheme]) - - // Why: live-apply text-size changes to an already-mounted terminal (the pane - // stays alive while the user visits Settings), so no terminal reload is needed. - useEffect(() => { - postMessage({ type: 'set-font-scale', fontScale: textScale }) - }, [postMessage, textScale]) - - useImperativeHandle( - ref, - () => ({ - prepareForForegroundRecovery() { - if (Platform.OS !== 'ios') { + const postMessage = useCallback( + (msg: TerminalWebViewCommand) => { + if (!isWebReadyRef.current) { + pendingMessages.queue(msg) return } - // Why: direct ping is the only command allowed through while readiness is - // invalid; init/write commands queue until this exact document answers. - isWebReadyRef.current = false - armWebReadyWatchdog() - pendingPingIdRef.current = sendToWebView({ type: 'ping' }) + sendToWebView(msg) }, - write(data: string) { - writeCoalescer.write(data) + [pendingMessages, sendToWebView] + ) + + // Why: a busy PTY delivers ~200 stream frames/s; coalescing here collapses the + // per-frame bridge + WebKit IPC + paint cost that runs the phone hot (#9302). + const writeCoalescer = useMemo( + () => createTerminalWriteCoalescer((data) => postMessage({ type: 'write', data })), + [postMessage] + ) + + useEffect(() => { + return () => { + writeCoalescer.clear() + } + }, [writeCoalescer]) + + const confirmWebReady = useCallback( + (notifyParent: boolean) => { + pendingPingIdRef.current = null + isWebReadyRef.current = true + clearWebReadyWatchdog() + clearEngineError() + if (notifyParent) { + onWebReady?.() + } + // Why: reload clears queued commands, so readiness must always restore the + // native-selected theme even when its value did not change in React. + sendToWebView({ type: 'set-theme', terminalTheme }) + flushPendingMessages() }, - init( - cols: number, - rows: number, - initialData?: string, - preserveScroll?: boolean, - oscLinks?: TerminalOscLinkRange[] - ) { - // Why: arm a fresh ready promise BEFORE posting init. The WebView - // resolves it via the 'ready' notify at the end of its rAF chain. - // Resolve any prior in-flight ready first so awaiters from the - // previous generation don't sit on the 3s setTimeout fallback — - // each leaked timer + closure pinned an awaiting measure caller - // for the full 3s under rapid re-init (orientation change, - // multiple resubscribes), delaying cold-start fit chains. - const priorResolve = readyResolveRef.current - if (priorResolve) { + [ + clearEngineError, + clearWebReadyWatchdog, + flushPendingMessages, + onWebReady, + sendToWebView, + terminalTheme + ] + ) + + const handleMessage = useCallback( + (event: WebViewMessageEvent) => { + let msg: Record + try { + msg = JSON.parse(event.nativeEvent.data) as Record + } catch { + return + } + routeTerminalQueryReply(msg, onTerminalQueryReply) + + if (msg.type === 'web-ready') { + confirmWebReady(true) + } else if ( + msg.type === 'pong' && + typeof msg.pingId === 'number' && + msg.pingId === pendingPingIdRef.current + ) { + confirmWebReady(false) + } else if (msg.type === 'ready') { + // Why: the WebView's init() rAF chain has run — term is open, + // renderService is populated, first paint has happened. Resolve + // any pending awaitReady() so a queued measure can now safely + // read cell dims. + const resolve = readyResolveRef.current readyResolveRef.current = null readyPromiseRef.current = null - priorResolve() - } - readyPromiseRef.current = new Promise((resolve) => { - readyResolveRef.current = resolve - }) - // Why: pending chunks are pre-snapshot data; the init snapshot supersedes - // them, and writing them after init would corrupt the fresh buffer. - writeCoalescer.clear() - postMessage({ - type: 'init', - cols, - rows, - initialData, - oscLinks, - terminalTheme, - fontScale: textScale, - preserveScroll - }) - }, - resize(cols: number, rows: number) { - // Why: resize/reflow must observe all prior writes or bytes reorder. - writeCoalescer.flushNow() - postMessage({ type: 'resize', cols, rows }) - }, - reflow(cols: number, rows: number) { - writeCoalescer.flushNow() - postMessage({ type: 'reflow', cols, rows }) - }, - clear() { - writeCoalescer.clear() - postMessage({ type: 'clear' }) - }, - measureFitDimensions( - containerHeight?: number - ): Promise<{ cols: number; rows: number } | null> { - if (!isWebReadyRef.current) { - return Promise.resolve(null) - } - return new Promise((resolve) => { - measureResolveRef.current?.(null) - let timeout: ReturnType | null = null - const finish = (result: { cols: number; rows: number } | null) => { - if (timeout) { - clearTimeout(timeout) - timeout = null - } - if (measureResolveRef.current === finish) { - measureResolveRef.current = null - } - resolve(result) + resolve?.() + } else if (msg.type === 'measure-result') { + const resolve = measureResolveRef.current + measureResolveRef.current = null + if (resolve) { + const cols = typeof msg.cols === 'number' ? msg.cols : null + const rows = typeof msg.rows === 'number' ? msg.rows : null + resolve(cols && rows && cols >= 20 && rows >= 8 ? { cols, rows } : null) } - measureResolveRef.current = finish - sendToWebView({ type: 'measure', containerHeight }) - // Why: if the WebView doesn't respond within 2s (e.g., xterm - // failed to load), resolve null so the caller can disable - // Fit to Phone rather than hanging indefinitely. - timeout = setTimeout(() => { - if (measureResolveRef.current === finish) { - finish(null) - } - }, 2000) - }) - }, - resetZoom() { - postMessage({ type: 'reset-zoom' }) - }, - cancelSelect() { - postMessage({ type: 'cancel-select' }) - }, - doSelectAll() { - postMessage({ type: 'do-select-all' }) - }, - async awaitReady(): Promise { - // Why: returns the in-flight ready promise (set by init); resolves - // immediately if no init is pending. Capped at 3s so a stuck - // WebView doesn't hang the caller. - const p = readyPromiseRef.current - if (!p) { - return + } else { + dispatchTerminalWebViewNotification(msg, { + reportEngineError, + onSelectionMode, + onSelectionCopy, + onSelectionEvicted, + onModesChanged, + onKeyboardAvoidanceMetrics, + onHaptic, + onTerminalInput, + onTerminalTap, + onFileTap, + onOpenUrl, + onTextScaleChange + }) } - await new Promise((resolve) => { - let settled = false - const timeout = setTimeout(() => { - settled = true - resolve() - }, 3000) - void p.finally(() => { - if (!settled) { - clearTimeout(timeout) + }, + [ + confirmWebReady, + reportEngineError, + onSelectionMode, + onSelectionCopy, + onSelectionEvicted, + onModesChanged, + onKeyboardAvoidanceMetrics, + onHaptic, + onTerminalInput, + onTerminalQueryReply, + onTerminalTap, + onFileTap, + onOpenUrl, + onTextScaleChange + ] + ) + + const handleLoadStart = useCallback(() => { + isWebReadyRef.current = false + pendingPingIdRef.current = null + armWebReadyWatchdog() + // Why: messages queued for a previous WebView generation are stale after a reload; + // dropping them avoids replaying terminal chunks before the next init snapshot. + pendingMessages.clear() + writeCoalescer.clear() + }, [armWebReadyWatchdog, pendingMessages, writeCoalescer]) + + const handleReload = useCallback(() => { + clearEngineError() + webViewRef.current?.reload() + }, [clearEngineError]) + + const handleContentProcessDidTerminate = useCallback(() => { + // Why: WKWebView content-process loss is recoverable; stale commands belong + // to the dead document and the replacement must prove readiness before replay. + isWebReadyRef.current = false + pendingPingIdRef.current = null + pendingMessages.clear() + writeCoalescer.clear() + clearEngineError() + armWebReadyWatchdog() + webViewRef.current?.reload() + }, [armWebReadyWatchdog, clearEngineError, pendingMessages, writeCoalescer]) + + useEffect(() => { + postMessage({ type: 'set-theme', terminalTheme }) + }, [postMessage, terminalThemeKey, terminalTheme]) + + // Why: live-apply text-size changes to an already-mounted terminal (the pane + // stays alive while the user visits Settings), so no terminal reload is needed. + useEffect(() => { + postMessage({ type: 'set-font-scale', fontScale: textScale }) + }, [postMessage, textScale]) + + useImperativeHandle( + ref, + () => ({ + prepareForForegroundRecovery() { + if (Platform.OS !== 'ios') { + return + } + // Why: direct ping is the only command allowed through while readiness is + // invalid; init/write commands queue until this exact document answers. + isWebReadyRef.current = false + armWebReadyWatchdog() + pendingPingIdRef.current = sendToWebView({ type: 'ping' }) + }, + write(data: string) { + writeCoalescer.write(data) + }, + init( + cols: number, + rows: number, + initialData?: string, + preserveScroll?: boolean, + oscLinks?: TerminalOscLinkRange[] + ) { + // Why: arm a fresh ready promise BEFORE posting init. The WebView + // resolves it via the 'ready' notify at the end of its rAF chain. + // Resolve any prior in-flight ready first so awaiters from the + // previous generation don't sit on the 3s setTimeout fallback — + // each leaked timer + closure pinned an awaiting measure caller + // for the full 3s under rapid re-init (orientation change, + // multiple resubscribes), delaying cold-start fit chains. + const priorResolve = readyResolveRef.current + if (priorResolve) { + readyResolveRef.current = null + readyPromiseRef.current = null + priorResolve() + } + readyPromiseRef.current = new Promise((resolve) => { + readyResolveRef.current = resolve + }) + // Why: pending chunks are pre-snapshot data; the init snapshot supersedes + // them, and writing them after init would corrupt the fresh buffer. + writeCoalescer.clear() + postMessage({ + type: 'init', + cols, + rows, + initialData, + oscLinks, + terminalTheme, + fontScale: textScale, + preserveScroll + }) + }, + resize(cols: number, rows: number) { + // Why: resize/reflow must observe all prior writes or bytes reorder. + writeCoalescer.flushNow() + postMessage({ type: 'resize', cols, rows }) + }, + reflow(cols: number, rows: number) { + writeCoalescer.flushNow() + postMessage({ type: 'reflow', cols, rows }) + }, + clear() { + writeCoalescer.clear() + postMessage({ type: 'clear' }) + }, + measureFitDimensions( + containerHeight?: number + ): Promise<{ cols: number; rows: number } | null> { + if (!isWebReadyRef.current) { + return Promise.resolve(null) + } + return new Promise((resolve) => { + measureResolveRef.current?.(null) + let timeout: ReturnType | null = null + const finish = (result: { cols: number; rows: number } | null) => { + if (timeout) { + clearTimeout(timeout) + timeout = null + } + if (measureResolveRef.current === finish) { + measureResolveRef.current = null + } + resolve(result) + } + measureResolveRef.current = finish + sendToWebView({ type: 'measure', containerHeight }) + // Why: if the WebView doesn't respond within 2s (e.g., xterm + // failed to load), resolve null so the caller can disable + // Fit to Phone rather than hanging indefinitely. + timeout = setTimeout(() => { + if (measureResolveRef.current === finish) { + finish(null) + } + }, 2000) + }) + }, + resetZoom() { + postMessage({ type: 'reset-zoom' }) + }, + cancelSelect() { + postMessage({ type: 'cancel-select' }) + }, + doSelectAll() { + postMessage({ type: 'do-select-all' }) + }, + async awaitReady(): Promise { + // Why: returns the in-flight ready promise (set by init); resolves + // immediately if no init is pending. Capped at 3s so a stuck + // WebView doesn't hang the caller. + const p = readyPromiseRef.current + if (!p) { + return + } + await new Promise((resolve) => { + let settled = false + const timeout = setTimeout(() => { settled = true resolve() - } + }, 3000) + void p.finally(() => { + if (!settled) { + clearTimeout(timeout) + settled = true + resolve() + } + }) }) - }) - } - }), - [armWebReadyWatchdog, postMessage, sendToWebView, terminalTheme, textScale, writeCoalescer] - ) - - return ( - - reportNativeEngineError('Terminal WebView load failed', event)} - onHttpError={(event) => reportNativeEngineError('Terminal WebView HTTP error', event)} - onRenderProcessGone={(event) => - reportNativeEngineError('Terminal WebView render process ended', event) } - onContentProcessDidTerminate={handleContentProcessDidTerminate} - /> - {engineError ? ( - - ) : null} - - ) -}) + }), + [armWebReadyWatchdog, postMessage, sendToWebView, terminalTheme, textScale, writeCoalescer] + ) + + return ( + + reportNativeEngineError('Terminal WebView load failed', event)} + onHttpError={(event) => reportNativeEngineError('Terminal WebView HTTP error', event)} + onRenderProcessGone={(event) => + reportNativeEngineError('Terminal WebView render process ended', event) + } + onContentProcessDidTerminate={handleContentProcessDidTerminate} + /> + {engineError ? ( + + ) : null} + + ) + } +) diff --git a/mobile/src/terminal/terminal-file-url-tap.ts b/mobile/src/terminal/terminal-file-url-tap.ts index b27260b61cb..87348228d54 100644 --- a/mobile/src/terminal/terminal-file-url-tap.ts +++ b/mobile/src/terminal/terminal-file-url-tap.ts @@ -1,7 +1,7 @@ -import type { TappedFilePath } from './terminal-path-tap' +import type { ParsedFileLinkLocation } from '../../../src/shared/file-link-location' import { parsePathWithOptionalLineColumn } from './terminal-path-tap' -export function resolveTerminalFileUrlTap(uri: string): TappedFilePath | null { +export function resolveTerminalFileUrlTap(uri: string): ParsedFileLinkLocation | null { let parsed: URL try { parsed = new URL(uri) @@ -24,7 +24,7 @@ export function resolveTerminalFileUrlTap(uri: string): TappedFilePath | null { ) } -export function resolveTerminalOscFileTap(uri: string): TappedFilePath | null { +export function resolveTerminalOscFileTap(uri: string): ParsedFileLinkLocation | null { return resolveTerminalFileUrlTap(uri) ?? parseOscPathLikeTarget(uri) } @@ -57,7 +57,7 @@ function isLocalFileUriHostname(hostname: string): boolean { ) } -function parseOscPathLikeTarget(value: string): TappedFilePath | null { +function parseOscPathLikeTarget(value: string): ParsedFileLinkLocation | null { if ( !/^(?:~[\\/]|[\\/]|\.{1,2}[\\/]|[A-Za-z]:[\\/]|[A-Za-z0-9._-]+[\\/]|(?=[A-Za-z0-9._-]*\.[A-Za-z0-9]))/.test( value @@ -81,7 +81,7 @@ function parseFileUrlLineHash(hash: string): { line: number; column: number | nu return { line, column } } -function parseFilePathTrailingLineTarget(filePath: string): TappedFilePath | null { +function parseFilePathTrailingLineTarget(filePath: string): ParsedFileLinkLocation | null { const match = /^(.*?)(?::(\d+))(?::(\d+))?$/.exec(filePath) if (!match || !match[1] || match[1].endsWith('/') || match[1].endsWith('\\')) { return null diff --git a/mobile/src/terminal/terminal-live-input.ts b/mobile/src/terminal/terminal-live-input.ts index 42b4d5cb2cf..4bf06cdbe0e 100644 --- a/mobile/src/terminal/terminal-live-input.ts +++ b/mobile/src/terminal/terminal-live-input.ts @@ -84,8 +84,6 @@ export type TerminalLiveInputDefaultResult = { changed: boolean } -export type TerminalLiveInputPruneResult = TerminalLiveInputDefaultResult - export function getTerminalLiveSpecialKeyBytes(key: string): string | null { const shortcutKey = TERMINAL_LIVE_SPECIAL_KEY_IDS.get(key) if (!shortcutKey) { @@ -176,7 +174,7 @@ export function pruneTerminalLiveInputHandles( enabledHandles: ReadonlySet, defaultedHandles: ReadonlySet, liveTerminalHandles: ReadonlySet -): TerminalLiveInputPruneResult { +): TerminalLiveInputDefaultResult { let nextEnabledHandles: Set | null = null let nextDefaultedHandles: Set | null = null diff --git a/mobile/src/terminal/terminal-path-tap.ts b/mobile/src/terminal/terminal-path-tap.ts index 8e7545a4cfb..8b8c8573843 100644 --- a/mobile/src/terminal/terminal-path-tap.ts +++ b/mobile/src/terminal/terminal-path-tap.ts @@ -8,8 +8,6 @@ import { type ParsedFileLinkLocation } from '../../../src/shared/file-link-location' -export type TappedFilePath = ParsedFileLinkLocation - // Separator-anchored path tokens (absolute, relative, ~/, drive-letter, UNC) OR // a bare filename with an extension (README.md, index.ts), optionally suffixed // with :line or :line:col. Like desktop, we propose candidates and let the host @@ -47,7 +45,7 @@ function trimBoundaryPunctuation( } } -export function parsePathWithOptionalLineColumn(value: string): TappedFilePath | null { +export function parsePathWithOptionalLineColumn(value: string): ParsedFileLinkLocation | null { const parsed = parseFileLinkLocation(value) if (!parsed) { return null @@ -137,7 +135,7 @@ function hasSpacedPathExtension(text: string): boolean { return /\s/.test(trimmed) && /\.[A-Za-z0-9_+-]+(?::\d+)?(?::\d+)?$/.test(trimmed) } -function matchSpacedFilePathAtColumn(lineText: string, col: number): TappedFilePath | null { +function matchSpacedFilePathAtColumn(lineText: string, col: number): ParsedFileLinkLocation | null { SPACED_PATH_REGEX.lastIndex = 0 let match: RegExpExecArray | null while ((match = SPACED_PATH_REGEX.exec(lineText)) !== null) { @@ -165,7 +163,10 @@ function matchSpacedFilePathAtColumn(lineText: string, col: number): TappedFileP // Returns the file-path span (after punctuation trim) that contains `col`, or // null when the tap isn't on a path. -export function matchFilePathAtColumn(lineText: string, col: number): TappedFilePath | null { +export function matchFilePathAtColumn( + lineText: string, + col: number +): ParsedFileLinkLocation | null { const spaced = matchSpacedFilePathAtColumn(lineText, col) if (spaced) { return spaced diff --git a/mobile/src/terminal/terminal-webview-contract.ts b/mobile/src/terminal/terminal-webview-contract.ts index 45b9a38c05a..a6386ac7261 100644 --- a/mobile/src/terminal/terminal-webview-contract.ts +++ b/mobile/src/terminal/terminal-webview-contract.ts @@ -42,8 +42,6 @@ function toNonNegativeInteger(value: unknown): number { return typeof value === 'number' && Number.isFinite(value) && value > 0 ? Math.floor(value) : 0 } -export type MobileTerminalTheme = RuntimeMobileTerminalTheme - export type TerminalSelectionEvents = { onSelectionMode?: (active: boolean) => void onSelectionCopy?: (text: string) => void @@ -65,7 +63,7 @@ export type TerminalSelectionEvents = { export type TerminalWebViewProps = { style?: StyleProp - terminalTheme?: MobileTerminalTheme + terminalTheme?: RuntimeMobileTerminalTheme // Why: baseline zoom multiplier applied on top of fit-to-width scale; raw // xterm fontSize alone cannot drive apparent size because fitting cancels it. textScale?: number diff --git a/mobile/src/terminal/terminal-webview-url-tap.test.ts b/mobile/src/terminal/terminal-webview-url-tap.test.ts index bd7ff4bf06b..f58da677012 100644 --- a/mobile/src/terminal/terminal-webview-url-tap.test.ts +++ b/mobile/src/terminal/terminal-webview-url-tap.test.ts @@ -1,6 +1,6 @@ import { createContext, Script } from 'node:vm' import { describe, expect, it } from 'vitest' -import type { TappedFilePath } from './terminal-path-tap' +import type { ParsedFileLinkLocation } from '../../../src/shared/file-link-location' import { TERMINAL_PATH_TAP_JS } from './terminal-path-tap-injected' import { TERMINAL_HTTP_URL_MAX_LENGTH, @@ -16,7 +16,7 @@ import { XTERM_HTML } from './terminal-webview-html' type FileTapResolverCase = { name: string uri: string - expected: TappedFilePath | null + expected: ParsedFileLinkLocation | null } const FILE_URL_TAP_CASES: FileTapResolverCase[] = [ @@ -90,7 +90,7 @@ const OSC_FILE_TAP_CASES: FileTapResolverCase[] = [ } ] -type InjectedFileTapResolver = (uri: string) => TappedFilePath | null +type InjectedFileTapResolver = (uri: string) => ParsedFileLinkLocation | null // Why: the WebView blob hand-translates terminal-file-url-tap.ts into plain JS // with re-escaped regexes; executing it against the same cases as the TS module diff --git a/mobile/src/transport/client-context.tsx b/mobile/src/transport/client-context.tsx index 76d26c1bab5..c83371af953 100644 --- a/mobile/src/transport/client-context.tsx +++ b/mobile/src/transport/client-context.tsx @@ -43,13 +43,11 @@ export { useRefreshHostClient } from './host-client-hooks' -type StoreEntry = HostClientStoreEntry - const Ctx = createContext(null) export function RpcClientProvider({ children }: { children: ReactNode }) { // Why: entries in a ref so state changes don't re-render the whole tree; propagation goes through per-host listener Sets. - const storeRef = useRef>(new Map()) + const storeRef = useRef>(new Map()) const stateListenersRef = useRef void>>>(new Map()) const allHostsListenersRef = useRef void>>(new Set()) @@ -93,7 +91,7 @@ export function RpcClientProvider({ children }: { children: ReactNode }) { }, []) const openEntry = useCallback( - (hostId: string, allowUnowned = false): Promise => { + (hostId: string, allowUnowned = false): Promise => { const retryScheduler = retrySchedulerRef.current if (!retryScheduler) { throw new Error('host retry scheduler not initialized') diff --git a/mobile/src/transport/host-edit-navigation.test.ts b/mobile/src/transport/host-edit-navigation.test.ts index db463413a46..8fd2e4c6cb3 100644 --- a/mobile/src/transport/host-edit-navigation.test.ts +++ b/mobile/src/transport/host-edit-navigation.test.ts @@ -2,11 +2,11 @@ import { describe, expect, it, vi } from 'vitest' import { mobileHostEditHostRoute, mobileHostEditRouteTarget, - navigateToMobileHostEdit, - type MobileHostEditNavigationState + navigateToMobileHostEdit } from './host-edit-navigation' +import type { HostStackNavigationState } from '../navigation/host-stack-navigation' -function navigationHarness(initialState: MobileHostEditNavigationState) { +function navigationHarness(initialState: HostStackNavigationState) { let stateListener = () => {} let state = initialState const unsubscribeState = vi.fn() @@ -20,7 +20,7 @@ function navigationHarness(initialState: MobileHostEditNavigationState) { } return { navigation, - setState(nextState: MobileHostEditNavigationState) { + setState(nextState: HostStackNavigationState) { state = nextState stateListener() }, @@ -30,7 +30,7 @@ function navigationHarness(initialState: MobileHostEditNavigationState) { // Edit now waits for the nested host stack, not just the root `h` route, so every committed // state below carries the stack the replacement targets. -function committedHostState(hostIdParam: string): MobileHostEditNavigationState { +function committedHostState(hostIdParam: string): HostStackNavigationState { return { index: 1, routes: [ diff --git a/mobile/src/transport/host-edit-navigation.ts b/mobile/src/transport/host-edit-navigation.ts index c1e667c5ca2..37bf633d036 100644 --- a/mobile/src/transport/host-edit-navigation.ts +++ b/mobile/src/transport/host-edit-navigation.ts @@ -3,19 +3,12 @@ import { navigateToHostStackRoute, type HostStackHostRoute, type HostStackNavigationController, - type HostStackNavigationState, type HostStackRootNavigation, type HostStackRouteTarget, type HostStackRouter } from '../navigation/host-stack-navigation' -export type MobileHostEditHostRoute = HostStackHostRoute -export type MobileHostEditNavigationState = HostStackNavigationState -export type MobileHostEditRootNavigation = HostStackRootNavigation -export type MobileHostEditRouter = HostStackRouter -export type MobileHostEditNavigationController = HostStackNavigationController - -export function mobileHostEditHostRoute(hostId: string): MobileHostEditHostRoute { +export function mobileHostEditHostRoute(hostId: string): HostStackHostRoute { return hostStackHostRoute(hostId) } @@ -27,9 +20,9 @@ export function mobileHostEditRouteTarget(hostId: string): HostStackRouteTarget } export function navigateToMobileHostEdit( - navigation: MobileHostEditRootNavigation, - router: MobileHostEditRouter, + navigation: HostStackRootNavigation, + router: HostStackRouter, hostId: string -): MobileHostEditNavigationController { +): HostStackNavigationController { return navigateToHostStackRoute(navigation, router, hostId, mobileHostEditRouteTarget(hostId)) } diff --git a/src/cli/handlers/emulator.ts b/src/cli/handlers/emulator.ts index e41700ff4b5..920ec6ece17 100644 --- a/src/cli/handlers/emulator.ts +++ b/src/cli/handlers/emulator.ts @@ -25,8 +25,6 @@ type EmulatorKillResult = { deviceUdid?: string } -type EmulatorShutdownResult = EmulatorKillResult - type EmulatorGesturePoint = { edge?: number type: 'begin' | 'move' | 'end' @@ -233,7 +231,7 @@ export const EMULATOR_HANDLERS: Record = { worktree: target.worktree }) printResult(res, json, (r: unknown) => { - const result = r as EmulatorShutdownResult + const result = r as EmulatorKillResult return `Shut down ${result.deviceUdid || target.device || 'emulator'}` }) }, diff --git a/src/main/agent-hooks/first-work-branch-rename.ts b/src/main/agent-hooks/first-work-branch-rename.ts index 355a0a35dc9..b3436217065 100644 --- a/src/main/agent-hooks/first-work-branch-rename.ts +++ b/src/main/agent-hooks/first-work-branch-rename.ts @@ -18,9 +18,9 @@ import { getSshGitProvider } from '../providers/ssh-git-dispatch' import { probeBranchUpstream, renameCurrentBranch, - resolveUniqueBranchName, - type GitExec + resolveUniqueBranchName } from '../git/branch-rename' +import type { GitCommandRunner } from '../../shared/git-effective-upstream' import { generateBranchNameFromContext, resolveTextGenerationParams @@ -180,7 +180,7 @@ async function runAutoRename( if (repo.connectionId && !provider) { return retry('ssh provider unavailable') } - const exec: GitExec = provider + const exec: GitCommandRunner = provider ? (args) => provider.exec(args, worktreePath) : (args) => gitExecFileAsync(args, { cwd: worktreePath }) diff --git a/src/main/ai-vault/session-first-user-prompt-read.ts b/src/main/ai-vault/session-first-user-prompt-read.ts index ac5a35f2a53..644a3064326 100644 --- a/src/main/ai-vault/session-first-user-prompt-read.ts +++ b/src/main/ai-vault/session-first-user-prompt-read.ts @@ -19,15 +19,13 @@ export type ReadAiVaultFirstUserPromptArgs = { codexHome?: string | null } -export type ReadAiVaultFirstUserPromptResult = AiVaultFirstUserPromptResult - /** * Re-parse one session transcript under full first-prompt capture and return * the untruncated first real user ask for copy/reuse. */ export async function readAiVaultFirstUserPrompt( args: ReadAiVaultFirstUserPromptArgs -): Promise { +): Promise { const filePath = args.filePath.trim() if (!filePath || !args.agent) { return { prompt: null } diff --git a/src/main/ai-vault/session-scanner-background.ts b/src/main/ai-vault/session-scanner-background.ts index 6903e689a64..e25971ec62f 100644 --- a/src/main/ai-vault/session-scanner-background.ts +++ b/src/main/ai-vault/session-scanner-background.ts @@ -1,12 +1,15 @@ -import type { AiVaultListResult, AiVaultSubagentListResult } from '../../shared/ai-vault-types' +import type { + AiVaultFirstUserPromptResult, + AiVaultListResult, + AiVaultSubagentListResult +} from '../../shared/ai-vault-types' import type { AiVaultSessionTitleRequest, AiVaultSessionTitlesResult } from '../../shared/ai-vault-session-title' import { readAiVaultFirstUserPrompt, - type ReadAiVaultFirstUserPromptArgs, - type ReadAiVaultFirstUserPromptResult + type ReadAiVaultFirstUserPromptArgs } from './session-first-user-prompt-read' import { clearAiVaultServiceRestartCircuit, @@ -72,7 +75,7 @@ export function listAiVaultSubagentSessionsInBackground( export function readAiVaultFirstUserPromptInBackground( request: ReadAiVaultFirstUserPromptArgs -): Promise { +): Promise { return shouldUseAiVaultServiceProcess() ? readAiVaultFirstUserPromptInService(request) : readAiVaultFirstUserPrompt(request) diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts index 87cc8eb2f47..4220dc632b6 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-bounds.test.ts @@ -21,14 +21,14 @@ afterEach(() => { tempDirs = [] }) -function createTempDb(): { db: Database.Database; path: string } { +function createTempDb(): { db: Database; path: string } { const dir = mkdtempSync(join(tmpdir(), 'orca-opencode-bounds-')) tempDirs.push(dir) const path = join(dir, 'opencode.db') return { db: new Database(path), path } } -function applySchema(db: Database.Database): void { +function applySchema(db: Database): void { db.exec(` CREATE TABLE session ( id TEXT PRIMARY KEY, @@ -63,7 +63,7 @@ function applySchema(db: Database.Database): void { `) } -function insertSession(db: Database.Database, id: string, timeUpdated: number): void { +function insertSession(db: Database, id: string, timeUpdated: number): void { db.prepare( `INSERT INTO session (id, project_id, directory, title, time_created, time_updated, agent) VALUES (?, 'proj', '/tmp/w', ?, ?, ?, 'build')` @@ -71,7 +71,7 @@ function insertSession(db: Database.Database, id: string, timeUpdated: number): } function insertUserMessage( - db: Database.Database, + db: Database, args: { id: string; sessionId: string; timeCreated: number; text: string } ): void { db.prepare(`INSERT INTO message (id, session_id, time_created, data) VALUES (?, ?, ?, ?)`).run( @@ -92,7 +92,7 @@ function insertUserMessage( } function insertMessageWithPart( - db: Database.Database, + db: Database, args: { id: string sessionId: string diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts index a5981a016d3..22048cc29a4 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-coexistence.test.ts @@ -56,14 +56,14 @@ function isolatedScanRoots(root: string) { } } -function createTempOpenCodeDb(): { db: Database.Database; path: string } { +function createTempOpenCodeDb(): { db: Database; path: string } { const dir = mkdtempSync(join(tmpdir(), 'orca-ai-vault-sqlite-')) tempDbDirs.push(dir) const path = join(dir, 'opencode.db') return { db: new Database(path), path } } -function applyOpenCodeSchema(db: Database.Database): void { +function applyOpenCodeSchema(db: Database): void { db.exec(` CREATE TABLE session ( id TEXT PRIMARY KEY, diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite-open.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite-open.test.ts index 45f1edbe6fb..4a7e45aa079 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite-open.test.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite-open.test.ts @@ -142,7 +142,7 @@ describe('listOpenCodeSqliteSessions against a database OpenCode is writing to', describe('readOpenCodeDatabase', () => { it('closes the handle on the success path', () => { const path = seededDatabase('opencode.db', 'session-a') - let captured: Database.Database | null = null + let captured: Database | null = null const rows = readOpenCodeDatabase({ dbPath: path, @@ -215,7 +215,7 @@ describe('readOpenCodeDatabase', () => { it('closes the handle when the read throws', () => { const path = seededDatabase('opencode.db', 'session-a') - let captured: Database.Database | null = null + let captured: Database | null = null expect(() => readOpenCodeDatabase({ diff --git a/src/main/ai-vault/session-scanner-opencode-sqlite.test.ts b/src/main/ai-vault/session-scanner-opencode-sqlite.test.ts index f7e042d663b..681ae28331e 100644 --- a/src/main/ai-vault/session-scanner-opencode-sqlite.test.ts +++ b/src/main/ai-vault/session-scanner-opencode-sqlite.test.ts @@ -18,14 +18,14 @@ afterEach(() => { tempDirs = [] }) -function createTempDb(): { db: Database.Database; path: string } { +function createTempDb(): { db: Database; path: string } { const dir = mkdtempSync(join(tmpdir(), 'orca-opencode-sqlite-')) tempDirs.push(dir) const path = join(dir, 'opencode.db') return { db: new Database(path), path } } -function applyOpenCodeSchema(db: Database.Database): void { +function applyOpenCodeSchema(db: Database): void { db.exec(` CREATE TABLE session ( id TEXT PRIMARY KEY, @@ -90,7 +90,7 @@ function applyOpenCodeSchema(db: Database.Database): void { `) } -function applyMinimalOpenCodeSchema(db: Database.Database): void { +function applyMinimalOpenCodeSchema(db: Database): void { db.exec(`CREATE TABLE session ( id TEXT PRIMARY KEY, time_created INTEGER NOT NULL, @@ -99,7 +99,7 @@ function applyMinimalOpenCodeSchema(db: Database.Database): void { } function insertSession( - db: Database.Database, + db: Database, args: { id: string title?: string @@ -144,7 +144,7 @@ function insertSession( } function insertMessage( - db: Database.Database, + db: Database, args: { id: string sessionId: string @@ -170,7 +170,7 @@ function insertMessage( } function insertPart( - db: Database.Database, + db: Database, args: { id: string messageId: string diff --git a/src/main/ai-vault/session-scanner-service-spawn.ts b/src/main/ai-vault/session-scanner-service-spawn.ts index d841fc62593..5cd1b81196c 100644 --- a/src/main/ai-vault/session-scanner-service-spawn.ts +++ b/src/main/ai-vault/session-scanner-service-spawn.ts @@ -6,16 +6,17 @@ import type { AiVaultSearchResponse, AiVaultSearchStatus } from '../../shared/ai-vault-search-types' -import type { AiVaultListResult, AiVaultSubagentListResult } from '../../shared/ai-vault-types' +import type { + AiVaultFirstUserPromptResult, + AiVaultListResult, + AiVaultSubagentListResult +} from '../../shared/ai-vault-types' import type { AiVaultSessionTitleRequest, AiVaultSessionTitlesResult } from '../../shared/ai-vault-session-title' import { withSpan } from '../observability/tracer' -import type { - ReadAiVaultFirstUserPromptArgs, - ReadAiVaultFirstUserPromptResult -} from './session-first-user-prompt-read' +import type { ReadAiVaultFirstUserPromptArgs } from './session-first-user-prompt-read' import { sessionSearchServiceInit } from '../ai-vault-search/session-search-service-init' import { getSessionParseCachePersistenceOptions } from './session-parse-cache-persistence' import { buildAiVaultServiceEnv } from './session-scanner-service-env' @@ -91,7 +92,7 @@ export function listAiVaultSubagentSessionsInService( export function readAiVaultFirstUserPromptInService( request: ReadAiVaultFirstUserPromptArgs, signal?: AbortSignal -): Promise { +): Promise { return getSharedClient().request({ type: 'request', operation: 'firstPrompt', request }, signal) } diff --git a/src/main/claude-accounts/claude-login-process-termination.ts b/src/main/claude-accounts/claude-login-process-termination.ts index 946c6b9627c..cacfc15c235 100644 --- a/src/main/claude-accounts/claude-login-process-termination.ts +++ b/src/main/claude-accounts/claude-login-process-termination.ts @@ -1,4 +1,5 @@ -import { spawnProcess, type ChildProcessHandle } from '../../shared/child-process/run-process' +import { spawnProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import type { WindowsHostInteractiveLoginSpawn } from '../../shared/windows-interactive-login-spawn' import { recordSelfInitiatedTreeKill } from '../crash-reporting/self-initiated-tree-kill-log' import { admitSelfInitiatedTreeKill } from '../own-chromium-tree-kill-guard' @@ -7,7 +8,7 @@ const WINDOWS_TASKKILL_TIMEOUT_MS = 5_000 /** Ends a `claude` login and everything it spawned, then runs `afterKill`. */ export function terminateClaudeProcess( - child: ChildProcessHandle, + child: ChildProcess, interactiveLogin: WindowsHostInteractiveLoginSpawn | null, afterKill: () => void ): void { diff --git a/src/main/claude/claude-agent-sdk-exit-proof.test.ts b/src/main/claude/claude-agent-sdk-exit-proof.test.ts index 3f15f8e8fca..2deebe87f78 100644 --- a/src/main/claude/claude-agent-sdk-exit-proof.test.ts +++ b/src/main/claude/claude-agent-sdk-exit-proof.test.ts @@ -2,7 +2,8 @@ import { execFileSync } from 'node:child_process' import { EventEmitter } from 'node:events' import { PassThrough } from 'node:stream' import { describe, expect, it, vi } from 'vitest' -import { spawnProcess, type SpawnedProcess } from '../../shared/child-process/run-process' +import { spawnProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import type { DescendantTreeVerdict } from '../pty-descendant-exit-verification' import type { DescendantSnapshot } from '../pty-descendant-termination' import type { WindowsDescendantSnapshot } from '../windows-descendant-exit-verification' @@ -126,7 +127,7 @@ function observeExit(child: EventEmitter): { exitPromise: Promise; exited: function mockChild( pid: number | null = 424242 ): EventEmitter & - Pick & { kill: ReturnType } { + Pick & { kill: ReturnType } { const child = new EventEmitter() return Object.assign(child, { pid: pid ?? undefined, diff --git a/src/main/claude/claude-agent-sdk-exit-proof.ts b/src/main/claude/claude-agent-sdk-exit-proof.ts index 17533f87a70..05c6942df30 100644 --- a/src/main/claude/claude-agent-sdk-exit-proof.ts +++ b/src/main/claude/claude-agent-sdk-exit-proof.ts @@ -1,4 +1,4 @@ -import type { SpawnedProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import { terminateDescendantSnapshotWithVerdict, type DescendantTreeVerdict @@ -34,7 +34,7 @@ const TREE_VERDICT_TRUST: Record = { exited: 2 } -type ReapableChild = Pick +type ReapableChild = Pick /** * A walk is only admissible while the root it walked was alive. A POSIX walk diff --git a/src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts b/src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts index 9f843527e30..ef8c85adcf2 100644 --- a/src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts +++ b/src/main/claude/claude-agent-sdk-root-kill-fallback.test.ts @@ -1,7 +1,7 @@ import { EventEmitter } from 'node:events' import { PassThrough } from 'node:stream' import { describe, expect, it, vi } from 'vitest' -import type { SpawnedProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import type { DescendantSnapshot } from '../pty-descendant-termination' import type { WindowsDescendantSnapshot } from '../windows-descendant-exit-verification' import { createClaudeChildTreeReaper } from './claude-agent-sdk-exit-proof' @@ -12,7 +12,7 @@ const ROOT_STARTED_AT = 'Mon Jan 1 00:00:00 2026' const ROOT_FORK_MS = Date.parse(ROOT_STARTED_AT) function mockChild(): EventEmitter & - Pick & { kill: ReturnType } { + Pick & { kill: ReturnType } { return Object.assign(new EventEmitter(), { pid: ROOT_PID, stdin: new PassThrough(), diff --git a/src/main/claude/claude-child-exit-proof-ladder.ts b/src/main/claude/claude-child-exit-proof-ladder.ts index 85ed629f1b9..cca7d0390ec 100644 --- a/src/main/claude/claude-child-exit-proof-ladder.ts +++ b/src/main/claude/claude-child-exit-proof-ladder.ts @@ -1,4 +1,4 @@ -import type { SpawnedProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import { waitForProcessExitUntil } from '../codex/codex-process-exit-deadline' import type { ClaudeChildTreeReaper } from './claude-agent-sdk-exit-proof' @@ -6,7 +6,7 @@ const GRACEFUL_EXIT_MS = 1_500 const FORCED_EXIT_MS = 1_000 export type ClaudeChildExitProofInput = { - child: Pick + child: Pick exitPromise: Promise exited: () => boolean tree?: ClaudeChildTreeReaper diff --git a/src/main/claude/claude-child-root-termination.ts b/src/main/claude/claude-child-root-termination.ts index bed422532e1..99770c72735 100644 --- a/src/main/claude/claude-child-root-termination.ts +++ b/src/main/claude/claude-child-root-termination.ts @@ -1,4 +1,4 @@ -import type { SpawnedProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import type { PosixProcessIdentity } from '../pty-descendant-termination' import type { WindowsDescendantSnapshot, @@ -8,7 +8,7 @@ import type { export type ClaudeRootIdentity = PosixProcessIdentity | WindowsProcessIdentity type RootTerminationInput = { - child: Pick + child: Pick exited: () => boolean } diff --git a/src/main/claude/claude-stream-json-connection.test.ts b/src/main/claude/claude-stream-json-connection.test.ts index eb69a66a897..3936dd4e2b0 100644 --- a/src/main/claude/claude-stream-json-connection.test.ts +++ b/src/main/claude/claude-stream-json-connection.test.ts @@ -3,7 +3,8 @@ import { mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' import { tmpdir } from 'node:os' import { join } from 'node:path' import { afterEach, describe, expect, it, vi } from 'vitest' -import { spawnProcess, type SpawnedProcess } from '../../shared/child-process/run-process' +import { spawnProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import { hasLiveClaudePtys } from '../claude-accounts/live-pty-gate' import type { ProcessSpec } from '../../shared/child-process/process-spec' import { query, type CanUseTool, type Options } from '@anthropic-ai/claude-agent-sdk' @@ -84,7 +85,7 @@ function launchFor( /** The derived child environment, captured where Orca actually hands it to the OS. */ const spawned: ProcessSpec[] = [] /** The retained child, so a test can end it the way a crashing CLI would. */ -const spawnedChildren: SpawnedProcess[] = [] +const spawnedChildren: ChildProcess[] = [] async function open( launch: ClaudeStreamJsonLaunch, @@ -734,7 +735,7 @@ describe('the managed-auth live gate', () => { { emit: { type: 'system', subtype: 'init', session_id: SESSION_ID, uuid: 'init-1' } }, { wait: HOLD_OPEN } ]) - let started: SpawnedProcess | null = null + let started: ChildProcess | null = null try { await expect( @@ -763,7 +764,7 @@ describe('the managed-auth live gate', () => { expect(hasLiveClaudePtys()).toBe(false) } finally { - ;(started as SpawnedProcess | null)?.kill('SIGKILL') + ;(started as ChildProcess | null)?.kill('SIGKILL') } }, 30_000) }) diff --git a/src/main/codex-accounts/codex-account-identity.ts b/src/main/codex-accounts/codex-account-identity.ts index 30b42743ef7..f8572974c00 100644 --- a/src/main/codex-accounts/codex-account-identity.ts +++ b/src/main/codex-accounts/codex-account-identity.ts @@ -6,8 +6,6 @@ import type { CodexSystemDefaultIdentity } from '../../shared/managed-account-ty import { readCodexAuthIdentity, type CodexAuthIdentity } from './codex-auth-identity' import { ManagedCodexHomeTemporarilyUnavailableError } from './host-codex-managed-home-ownership' -export type ResolvedCodexIdentity = CodexAuthIdentity - /** API-key logins carry no OAuth identity even when a stale `tokens` blob is still present. */ function declaresApiKeyCredential(parsed: unknown): boolean { if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) { @@ -25,7 +23,7 @@ export class CodexAccountIdentity { ) => string ) {} - readFromHome(managedHomePath: string, expectedAccountId: string): ResolvedCodexIdentity { + readFromHome(managedHomePath: string, expectedAccountId: string): CodexAuthIdentity { const authFilePath = join( this.assertManagedHomePath(managedHomePath, expectedAccountId), 'auth.json' diff --git a/src/main/codex-accounts/codex-account-registration.ts b/src/main/codex-accounts/codex-account-registration.ts index fbdf0db1a68..b4e20878390 100644 --- a/src/main/codex-accounts/codex-account-registration.ts +++ b/src/main/codex-accounts/codex-account-registration.ts @@ -6,7 +6,7 @@ import type { import type { Store } from '../persistence' import type { RateLimitService } from '../rate-limits/service' import type { CodexRuntimeHomeService } from './runtime-home-service' -import type { ResolvedCodexIdentity } from './codex-account-identity' +import type { CodexAuthIdentity } from './codex-auth-identity' import type { CodexAccountAddTarget, CodexAccountReauthenticateOptions, @@ -28,10 +28,7 @@ type CodexAccountRegistrationDependencies = { store: Store rateLimits: RateLimitService runtimeHome: CodexRuntimeHomeService - readIdentityFromHome: ( - managedHomePath: string, - expectedAccountId: string - ) => ResolvedCodexIdentity + readIdentityFromHome: (managedHomePath: string, expectedAccountId: string) => CodexAuthIdentity selection: CodexAccountSelection configMirror: CodexConfigMirror managedHomePaths: CodexManagedHomePath diff --git a/src/main/codex-accounts/service.ts b/src/main/codex-accounts/service.ts index 6f5a4a9f0b7..2040d1cecf2 100644 --- a/src/main/codex-accounts/service.ts +++ b/src/main/codex-accounts/service.ts @@ -14,7 +14,8 @@ import type { RateLimitService } from '../rate-limits/service' import { buildEncodedWslBashCommand } from '../wsl-bash-command' import { admitSelfInitiatedTreeKill } from '../own-chromium-tree-kill-guard' import type { CodexAccountSelectionTarget } from './runtime-selection' -import { CodexAccountIdentity, type ResolvedCodexIdentity } from './codex-account-identity' +import { CodexAccountIdentity } from './codex-account-identity' +import type { CodexAuthIdentity } from './codex-auth-identity' import { CodexConfigMirror } from './codex-config-mirror' import { runCodexLoginSession, type CodexLoginChild } from './codex-login-session' import { CodexManagedHomePath } from './codex-managed-home-path' @@ -221,7 +222,7 @@ export class CodexAccountService { private readIdentityFromHome( managedHomePath: string, expectedAccountId: string - ): ResolvedCodexIdentity { + ): CodexAuthIdentity { return this.identity.readFromHome(managedHomePath, expectedAccountId) } diff --git a/src/main/codex-usage/codex-usage-event-attribution.ts b/src/main/codex-usage/codex-usage-event-attribution.ts index 798183360be..1ed7b1a2332 100644 --- a/src/main/codex-usage/codex-usage-event-attribution.ts +++ b/src/main/codex-usage/codex-usage-event-attribution.ts @@ -8,8 +8,6 @@ import { import type { UsageScanWorktreeRef } from '../usage/usage-provider-contract' import type { CodexUsageAttributedEvent, CodexUsageParsedEvent } from './types' -export type CodexUsageWorktreeRef = UsageScanWorktreeRef - function getDefaultProjectLabel(cwd: string | null): string { if (!cwd) { return 'Unknown location' @@ -59,8 +57,8 @@ function isContainingPath(candidatePath: string, targetPath: string): boolean { function findContainingWorktree( cwd: string, - worktrees: (CodexUsageWorktreeRef & { canonicalPath: string })[] -): CodexUsageWorktreeRef | null { + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[] +): UsageScanWorktreeRef | null { const normalizedCwd = normalizeFsPath(cwd) for (const worktree of worktrees) { if (areWorktreePathsEqual(worktree.canonicalPath, normalizedCwd)) { @@ -75,7 +73,7 @@ function findContainingWorktree( export async function attributeCodexUsageEvent( event: CodexUsageParsedEvent, - worktrees: (CodexUsageWorktreeRef & { canonicalPath: string })[] + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[] ): Promise { const day = localDayFromTimestamp(event.timestamp) if (!day) { diff --git a/src/main/codex-usage/scanner.ts b/src/main/codex-usage/scanner.ts index d06a85f8901..78a4d96128c 100644 --- a/src/main/codex-usage/scanner.ts +++ b/src/main/codex-usage/scanner.ts @@ -10,10 +10,8 @@ import { listCodexSessionFiles, yieldToEventLoop } from './codex-session-file-discovery' -import { - attributeCodexUsageEvent, - type CodexUsageWorktreeRef -} from './codex-usage-event-attribution' +import { attributeCodexUsageEvent } from './codex-usage-event-attribution' +import type { UsageScanWorktreeRef } from '../usage/usage-provider-contract' import { parseCodexUsageRecord, type CodexUsageParseContext } from './codex-usage-record-parser' import type { CodexUsageAttributedEvent, @@ -35,8 +33,8 @@ export async function getProcessedFileInfo(filePath: string): Promise { + worktrees: UsageScanWorktreeRef[] +): Promise<(UsageScanWorktreeRef & { canonicalPath: string })[]> { return canonicalizeUsageWorktreePaths(worktrees, canonicalizePath) } @@ -66,7 +64,7 @@ const { finalizeSessions, mergeSessions, mergeDailyAggregates, sortDailyAggregat export async function parseCodexUsageFile( filePath: string, - worktrees: (CodexUsageWorktreeRef & { canonicalPath: string })[], + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[], options: { skipInitialBytes?: number; claimEventKey?: (eventKey: string) => boolean } = {} ): Promise { const processedFile = await getProcessedFileInfo(filePath) @@ -119,7 +117,7 @@ export async function parseCodexUsageFile( } export async function scanCodexUsageFiles( - worktrees: CodexUsageWorktreeRef[], + worktrees: UsageScanWorktreeRef[], previousProcessedFiles: CodexUsagePersistedFile[] ): Promise<{ processedFiles: CodexUsagePersistedFile[] diff --git a/src/main/codex/codex-app-server-client.ts b/src/main/codex/codex-app-server-client.ts index efdee5de8bf..241be130b4a 100644 --- a/src/main/codex/codex-app-server-client.ts +++ b/src/main/codex/codex-app-server-client.ts @@ -1,4 +1,5 @@ -import type { ChildProcessHandle, ProcessSpec } from '../../shared/child-process/process-spec' +import type { ProcessSpec } from '../../shared/child-process/process-spec' +import type { ChildProcess } from 'node:child_process' import { spawnProcess } from '../../shared/child-process/run-process' import { normalizeHookTrustKeyForLookup } from './config-toml-trust' import { runCodexAppServerSession, type CodexAppServerInvocation } from './codex-app-server-session' @@ -106,12 +107,11 @@ function collectHookListings(result: unknown): CodexHookListing[] { */ export async function runCodexHookTrustGrantSession( request: CodexHookTrustGrantRequest, - spawnImpl: ( - program: string, - args: string[], - options: Record - ) => ChildProcessHandle = (program, args, options) => - spawnProcess({ program, args, ...options } as ProcessSpec) + spawnImpl: (program: string, args: string[], options: Record) => ChildProcess = ( + program, + args, + options + ) => spawnProcess({ program, args, ...options } as ProcessSpec) ): Promise { return runCodexAppServerSession( request.invocation, diff --git a/src/main/codex/codex-app-server-process-teardown.ts b/src/main/codex/codex-app-server-process-teardown.ts index 5a9c6e3574b..cf08d899d87 100644 --- a/src/main/codex/codex-app-server-process-teardown.ts +++ b/src/main/codex/codex-app-server-process-teardown.ts @@ -1,4 +1,4 @@ -import type { ChildProcessHandle } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import { captureDescendantSnapshot, type DescendantSnapshot } from '../pty-descendant-termination' import { terminateDescendantSnapshotAndWait } from '../pty-descendant-exit-verification' import { terminateWindowsProcessTree } from '../windows-process-tree-kill' @@ -9,7 +9,7 @@ const TOKEN_PROCESS_EXIT_TIMEOUT_MS = 3_500 const TOKEN_PROCESS_POLL_MS = 25 const activeTeardowns = new WeakMap>() -type TeardownChild = Pick +type TeardownChild = Pick export type CodexAppServerProcessTeardownDeps = { platform?: NodeJS.Platform diff --git a/src/main/codex/codex-app-server-process-tree-kill.ts b/src/main/codex/codex-app-server-process-tree-kill.ts index 315246aaa9f..465621659c9 100644 --- a/src/main/codex/codex-app-server-process-tree-kill.ts +++ b/src/main/codex/codex-app-server-process-tree-kill.ts @@ -1,5 +1,6 @@ import { spawnProcess } from '../../shared/child-process/run-process' -import type { ChildProcessHandle, ProcessSpec } from '../../shared/child-process/process-spec' +import type { ProcessSpec } from '../../shared/child-process/process-spec' +import type { ChildProcess } from 'node:child_process' import { admitProcessTreeKill } from '../../shared/child-process/process-tree-kill-gate' /** Spawn seam for tests; production always goes through the hardened spawnProcess wrapper. */ @@ -7,13 +8,13 @@ export type CodexAppServerSpawn = ( program: string, args: string[], options: Record -) => ChildProcessHandle +) => ChildProcess export const spawnCodexAppServerProcess: CodexAppServerSpawn = (program, args, options) => spawnProcess({ program, args, ...options } as ProcessSpec) export function killCodexAppServerProcessTree( - child: Pick, + child: Pick, options: { platform?: NodeJS.Platform; spawnImpl?: CodexAppServerSpawn } = {} ): void { const platform = options.platform ?? process.platform diff --git a/src/main/daemon/daemon-pty-process-inspection.ts b/src/main/daemon/daemon-pty-process-inspection.ts index 335c641da62..f1b9d7278b5 100644 --- a/src/main/daemon/daemon-pty-process-inspection.ts +++ b/src/main/daemon/daemon-pty-process-inspection.ts @@ -7,8 +7,10 @@ import { type ListSessionsResult, type SessionInfo } from './types' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' -import { clientOnlyUnverifiableInspection } from '../../shared/terminal-process-inspection' +import { + clientOnlyUnverifiableInspection, + type TerminalProcessInspection +} from '../../shared/terminal-process-inspection' export abstract class DaemonPtyProcessInspection extends DaemonPtyBufferSnapshots { // Why: daemon-backed PTYs can host long-lived agents while detached; cleanup prompts must not treat them as idle shells. @@ -26,7 +28,7 @@ export abstract class DaemonPtyProcessInspection extends DaemonPtyBufferSnapshot async inspectProcess( id: string, options?: { expectedIncarnationId?: string; steadyState?: boolean } - ): Promise { + ): Promise { if (this.protocolVersion < GET_FOREGROUND_PROCESS_PROTOCOL_VERSION) { return clientOnlyUnverifiableInspection('old_host') } @@ -43,7 +45,7 @@ export abstract class DaemonPtyProcessInspection extends DaemonPtyBufferSnapshot hasChildProcesses: this.hasChildProcessesFromForeground(foregroundProcess) } } - return this.client.request('inspectProcess', { + return this.client.request('inspectProcess', { sessionId: id, ...(options?.expectedIncarnationId ? { expectedIncarnationId: options.expectedIncarnationId } diff --git a/src/main/daemon/daemon-pty-router.ts b/src/main/daemon/daemon-pty-router.ts index 2fbfd17a039..c39d93b1a9c 100644 --- a/src/main/daemon/daemon-pty-router.ts +++ b/src/main/daemon/daemon-pty-router.ts @@ -8,7 +8,7 @@ import type { PtySpawnOptions, PtySpawnResult } from '../providers/types' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' import { shouldHandoffDaemonHistory } from './daemon-history-handoff' import type { DaemonPtyRouterDataEvent, DaemonPtyRouterExitEvent } from './daemon-pty-router-events' import { DaemonSessionOwnerResolver } from './daemon-session-owner-resolution' @@ -181,7 +181,7 @@ export class DaemonPtyRouter implements IPtyProvider { async inspectProcess( id: string, options?: { expectedIncarnationId?: string; steadyState?: boolean } - ): Promise { + ): Promise { return this.adapterForInspection(id).inspectProcess(id, options) } diff --git a/src/main/daemon/degraded-daemon-pty-provider.test.ts b/src/main/daemon/degraded-daemon-pty-provider.test.ts index f2e86abab28..6fc146894a7 100644 --- a/src/main/daemon/degraded-daemon-pty-provider.test.ts +++ b/src/main/daemon/degraded-daemon-pty-provider.test.ts @@ -4,12 +4,12 @@ import { DEGRADED_DAEMON_RECOVERY_RETRY_MS } from './degraded-daemon-fresh-spawn import type { DaemonPtyAdapter } from './daemon-pty-adapter' import { settledWriteStub, stubWriteSettlement } from '../providers/settled-pty-write-stub' import type { IPtyProvider, PtySpawnOptions, PtySpawnResult } from '../providers/types' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' import { SessionNotFoundError, TerminalSessionOwnerUnverifiedError } from './daemon-errors' type ProviderMock = IPtyProvider & { probePtyLiveness: (id: string) => Promise - inspectProcess: (id: string) => Promise + inspectProcess: (id: string) => Promise emitData: (id: string, data: string, sequenceChars?: number) => void emitReplay: (id: string, data: string) => void emitExit: (id: string, code: number) => void diff --git a/src/main/emulator/android/android-input-commands.ts b/src/main/emulator/android/android-input-commands.ts index 1197e248c91..457acd0973b 100644 --- a/src/main/emulator/android/android-input-commands.ts +++ b/src/main/emulator/android/android-input-commands.ts @@ -6,7 +6,7 @@ import { normalizedToDevicePixels, type DeviceScreenSize } from './android-input-mapping' -import type { EmulatorGesturePoint } from '../emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../../shared/emulator-touch-frame' // Android control via `adb shell input`, so it works without the scrcpy server. // The backend resolves the serial + screen size and delegates here. @@ -37,7 +37,7 @@ export async function androidSwipe( runner: AndroidCommandRunner, sdk: AndroidSdkPaths, serial: string, - points: EmulatorGesturePoint[], + points: ServeSimTouchFrame[], size: DeviceScreenSize ): Promise { const first = points[0] diff --git a/src/main/emulator/backends/android-emulator-backend.ts b/src/main/emulator/backends/android-emulator-backend.ts index 37ea23b137d..682fb45518a 100644 --- a/src/main/emulator/backends/android-emulator-backend.ts +++ b/src/main/emulator/backends/android-emulator-backend.ts @@ -45,7 +45,7 @@ import { } from '../android/android-stream-session-starter' import { AndroidStreamController } from '../android/android-stream-controller' import { scrcpyVideoRegistry } from '../scrcpy-video-registry' -import type { EmulatorGesturePoint } from '../emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../../shared/emulator-touch-frame' export type AndroidEmulatorBackendOptions = { runner?: AndroidCommandRunner @@ -212,7 +212,7 @@ export class AndroidEmulatorBackend implements EmulatorBackend { async gesture( deviceId: string, - points: EmulatorGesturePoint[], + points: ServeSimTouchFrame[], _wsUrl: string | null ): Promise { const serial = await this.resolveDeviceId(deviceId) diff --git a/src/main/emulator/backends/emulator-backend.ts b/src/main/emulator/backends/emulator-backend.ts index 6186a493498..6b4668f279d 100644 --- a/src/main/emulator/backends/emulator-backend.ts +++ b/src/main/emulator/backends/emulator-backend.ts @@ -1,4 +1,4 @@ -import type { EmulatorGesturePoint } from '../emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../../shared/emulator-touch-frame' import type { EmulatorBackendKind, EmulatorSessionInfo, @@ -74,7 +74,7 @@ export type EmulatorBackend = { tap(deviceId: string, x: number, y: number): Promise // wsUrl is the iOS gesture stream from the registry; Android backends ignore it // and drive their own control socket keyed by deviceId. - gesture(deviceId: string, points: EmulatorGesturePoint[], wsUrl: string | null): Promise + gesture(deviceId: string, points: ServeSimTouchFrame[], wsUrl: string | null): Promise type(deviceId: string, text: string): Promise button(deviceId: string, name: string): Promise rotate(deviceId: string, orientation: string): Promise diff --git a/src/main/emulator/backends/ios-emulator-backend.ts b/src/main/emulator/backends/ios-emulator-backend.ts index 0cfe9a39f32..5c083c94bdf 100644 --- a/src/main/emulator/backends/ios-emulator-backend.ts +++ b/src/main/emulator/backends/ios-emulator-backend.ts @@ -21,7 +21,8 @@ import { listServeSimHelperProcessesForDevice } from '../serve-sim-helper-processes' import type { EmulatorBridgeOptions } from '../emulator-bridge-types' -import { sendEmulatorGestureSequence, type EmulatorGesturePoint } from '../emulator-gesture-sender' +import { sendEmulatorGestureSequence } from '../emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../../shared/emulator-touch-frame' import { parseServeSimDetachedSession } from '../serve-sim-detached-session' import { requestServeSimAccessibilityTree } from '../serve-sim-accessibility-tree' import { hideNativeSimulatorApp } from '../simulator-app-visibility' @@ -143,7 +144,7 @@ export class IosEmulatorBackend implements EmulatorBackend { async gesture( _deviceId: string, - points: EmulatorGesturePoint[], + points: ServeSimTouchFrame[], wsUrl: string | null ): Promise { if (points.length === 0) { diff --git a/src/main/emulator/emulator-bridge.ts b/src/main/emulator/emulator-bridge.ts index b201b34963a..7d033214b8e 100644 --- a/src/main/emulator/emulator-bridge.ts +++ b/src/main/emulator/emulator-bridge.ts @@ -3,7 +3,7 @@ import { EmulatorError } from './emulator-errors' import type { EmulatorSessionInfo } from './emulator-types' import type { SimulatorDevice } from './simctl-simulator-devices' import type { EmulatorBridgeOptions } from './emulator-bridge-types' -import type { EmulatorGesturePoint } from './emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../shared/emulator-touch-frame' import { EmulatorSessionRegistry } from './emulator-session-registry' import { EmulatorStartLeaseRegistry, @@ -168,7 +168,7 @@ export class EmulatorBridge { await backend.tap(device, x, y) } - async gesture(points: EmulatorGesturePoint[], opts?: EmulatorTargetOpts): Promise { + async gesture(points: ServeSimTouchFrame[], opts?: EmulatorTargetOpts): Promise { if (points.length === 0) { return } diff --git a/src/main/emulator/emulator-gesture-sender.ts b/src/main/emulator/emulator-gesture-sender.ts index bf017b171b3..029244b5439 100644 --- a/src/main/emulator/emulator-gesture-sender.ts +++ b/src/main/emulator/emulator-gesture-sender.ts @@ -4,11 +4,9 @@ import { type ServeSimTouchFrame } from '../../shared/emulator-touch-frame' -export type EmulatorGesturePoint = ServeSimTouchFrame - export async function sendEmulatorGestureSequence( wsUrl: string, - points: EmulatorGesturePoint[] + points: ServeSimTouchFrame[] ): Promise { await new Promise((resolve, reject) => { const ws = new WebSocket(wsUrl) diff --git a/src/main/git/branch-rename.test.ts b/src/main/git/branch-rename.test.ts index 023bb542011..c29751454f9 100644 --- a/src/main/git/branch-rename.test.ts +++ b/src/main/git/branch-rename.test.ts @@ -1,10 +1,6 @@ import { describe, expect, it, vi } from 'vitest' -import { - probeBranchUpstream, - renameCurrentBranch, - resolveUniqueBranchName, - type GitExec -} from './branch-rename' +import { probeBranchUpstream, renameCurrentBranch, resolveUniqueBranchName } from './branch-rename' +import type { GitCommandRunner } from '../../shared/git-effective-upstream' const noUpstreamError = new Error( "fatal: no upstream configured for branch 'feature'\n" + @@ -14,7 +10,7 @@ const noUpstreamError = new Error( describe('probeBranchUpstream', () => { it('reports has-upstream when @{u} resolves to a tracking ref', async () => { - const exec: GitExec = vi.fn(async (args: string[]) => { + const exec: GitCommandRunner = vi.fn(async (args: string[]) => { if (args[0] === 'symbolic-ref') { return { stdout: 'feature\n', stderr: '' } } @@ -27,7 +23,7 @@ describe('probeBranchUpstream', () => { }) it('reports no-upstream when there is no upstream', async () => { - const exec: GitExec = vi.fn(async (args: string[]) => { + const exec: GitCommandRunner = vi.fn(async (args: string[]) => { if (args[0] === 'symbolic-ref') { return { stdout: 'feature\n', stderr: '' } } @@ -43,7 +39,7 @@ describe('probeBranchUpstream', () => { }) it('reports has-upstream when a same-name origin tracking ref exists without configured upstream', async () => { - const exec: GitExec = vi.fn(async (args: string[]) => { + const exec: GitCommandRunner = vi.fn(async (args: string[]) => { if (args[0] === 'symbolic-ref') { return { stdout: 'feature\n', stderr: '' } } @@ -59,7 +55,9 @@ describe('probeBranchUpstream', () => { }) it('reports probe-failed on an unexpected failure', async () => { - const exec: GitExec = vi.fn().mockRejectedValue(new Error('fatal: not a git repository')) + const exec: GitCommandRunner = vi + .fn() + .mockRejectedValue(new Error('fatal: not a git repository')) expect(await probeBranchUpstream(exec)).toEqual({ outcome: 'probe-failed', message: 'fatal: not a git repository' @@ -69,7 +67,7 @@ describe('probeBranchUpstream', () => { it('scrubs credential-bearing URLs from the probe-failed message', async () => { // The message surfaces on the worktree card, so an embedded remote URL // must not leak a token or password into the UI. - const exec: GitExec = vi + const exec: GitCommandRunner = vi .fn() .mockRejectedValue( new Error('fatal: unable to access https://user:hunter2@example.com/repo.git/: timed out') @@ -82,7 +80,7 @@ describe('probeBranchUpstream', () => { it('reports probe-failed, not has-upstream, for localized git diagnostics (issue #7808)', async () => { // A gettext-enabled git under de_DE translates even the `fatal:` prefix. - const exec: GitExec = vi.fn(async (args: string[]) => { + const exec: GitCommandRunner = vi.fn(async (args: string[]) => { if (args[0] === 'symbolic-ref') { return { stdout: 'feature\n', stderr: '' } } @@ -100,13 +98,13 @@ describe('resolveUniqueBranchName', () => { const compute = (leaf: string): string => `you/${leaf}` it('returns the first candidate when no branch collides', async () => { - const exec: GitExec = vi.fn().mockRejectedValue(new Error('not found')) // show-ref misses + const exec: GitCommandRunner = vi.fn().mockRejectedValue(new Error('not found')) // show-ref misses const result = await resolveUniqueBranchName(exec, 'fix-auth', compute, 'you/Nautilus') expect(result).toBe('you/fix-auth') }) it('suffixes when the first candidate already exists', async () => { - const exec: GitExec = vi.fn(async (args: string[]) => { + const exec: GitCommandRunner = vi.fn(async (args: string[]) => { const ref = args.at(-1) if (ref === 'refs/heads/you/fix-auth') { return { stdout: '', stderr: '' } // exists @@ -120,7 +118,7 @@ describe('resolveUniqueBranchName', () => { it('does not treat the branch being renamed away from as a collision', async () => { // exec would report every ref as existing; only the currentBranch shortcut // lets a candidate through. - const exec: GitExec = vi.fn().mockResolvedValue({ stdout: '', stderr: '' }) + const exec: GitCommandRunner = vi.fn().mockResolvedValue({ stdout: '', stderr: '' }) const result = await resolveUniqueBranchName(exec, 'octopus', compute, 'you/octopus') expect(result).toBe('you/octopus') }) @@ -128,7 +126,7 @@ describe('resolveUniqueBranchName', () => { describe('renameCurrentBranch', () => { it('runs git branch -m with the new name', async () => { - const exec: GitExec = vi.fn().mockResolvedValue({ stdout: '', stderr: '' }) + const exec: GitCommandRunner = vi.fn().mockResolvedValue({ stdout: '', stderr: '' }) await renameCurrentBranch(exec, 'you/fix-auth') expect(exec).toHaveBeenCalledWith(['branch', '-m', 'you/fix-auth']) }) diff --git a/src/main/git/branch-rename.ts b/src/main/git/branch-rename.ts index 1237b82cf5a..d4e00778644 100644 --- a/src/main/git/branch-rename.ts +++ b/src/main/git/branch-rename.ts @@ -4,13 +4,6 @@ import { type GitCommandRunner } from '../../shared/git-effective-upstream' -/** - * Git runner so branch-rename logic works identically for local worktrees - * (`gitExecFileAsync`) and SSH worktrees (`provider.exec`). Same contract the - * shared upstream-status helpers use. - */ -export type GitExec = GitCommandRunner - export type BranchUpstreamProbe = | { outcome: 'has-upstream' } | { outcome: 'no-upstream' } @@ -21,7 +14,7 @@ export type BranchUpstreamProbe = * a remote. Auto-rename refuses to touch such a branch because `git branch -m` * would orphan the remote branch and break any open PR. */ -export async function probeBranchUpstream(exec: GitExec): Promise { +export async function probeBranchUpstream(exec: GitCommandRunner): Promise { try { const upstream = await resolveEffectiveGitUpstream(exec) return { outcome: upstream !== null ? 'has-upstream' : 'no-upstream' } @@ -39,7 +32,7 @@ export async function probeBranchUpstream(exec: GitExec): Promise { +async function localBranchExists(exec: GitCommandRunner, branch: string): Promise { try { await exec(['show-ref', '--verify', '--quiet', `refs/heads/${branch}`]) return true @@ -55,7 +48,7 @@ async function localBranchExists(exec: GitExec, branch: string): Promise string, currentBranch: string, @@ -78,6 +71,9 @@ export async function resolveUniqueBranchName( } /** Rename the currently checked-out branch (`git branch -m `). */ -export async function renameCurrentBranch(exec: GitExec, newBranch: string): Promise { +export async function renameCurrentBranch( + exec: GitCommandRunner, + newBranch: string +): Promise { await exec(['branch', '-m', newBranch]) } diff --git a/src/main/git/status-test-harness.ts b/src/main/git/status-test-harness.ts index 99925f90363..95fd8ad77df 100644 --- a/src/main/git/status-test-harness.ts +++ b/src/main/git/status-test-harness.ts @@ -1,22 +1,20 @@ import type { Mock } from 'vitest' import type * as BoundedFileReader from '../../shared/node-bounded-file-reader' -type MockFn = Mock - export type GitRunnerMocks = { - gitExecFileAsyncMock: MockFn - gitExecFileAsyncBufferMock: MockFn - gitStreamOptionsMock: MockFn + gitExecFileAsyncMock: Mock + gitExecFileAsyncBufferMock: Mock + gitStreamOptionsMock: Mock } export type FsPromisesMocks = { - lstatMock: MockFn - realpathMock: MockFn - readFileMock: MockFn - statMock: MockFn - rmMock: MockFn + lstatMock: Mock + realpathMock: Mock + readFileMock: Mock + statMock: Mock + rmMock: Mock /** Optional: defaults to "nothing exists", which is what most git-read tests assume. */ - accessMock?: MockFn + accessMock?: Mock } export function createGitRunnerModuleMock(mocks: GitRunnerMocks): Record { @@ -62,7 +60,7 @@ export function createFsPromisesModuleMock(mocks: FsPromisesMocks): Record { return { ...actual, diff --git a/src/main/github/client/check/check-job-log-tails.ts b/src/main/github/client/check/check-job-log-tails.ts index d6ee40f62d3..4f7fa336b2a 100644 --- a/src/main/github/client/check/check-job-log-tails.ts +++ b/src/main/github/client/check/check-job-log-tails.ts @@ -1,10 +1,11 @@ import type { PRCheckRunDetails } from '../../../../shared/github/check-types' import { sliceCheckLogTail } from '../../../../shared/check-job-log-tail-slice' import { ghExecFileAsync } from '../../gh-utils' -import type { GitHubApiRepository } from '../../github-api-repository' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' import type { GhExecOptions } from './../github-exec-scope' import { rethrowCheckDetailsAbort } from './check-details-abort' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export const PR_CHECK_LOG_TAIL_JOB_LIMIT = 5 // Why: only the tail is kept, but the whole log buffers first — the default 10MiB cap drops long CI logs. @@ -48,7 +49,7 @@ export function getCheckJobLogTailCacheKey(job: PRCheckRunDetails['jobs'][number export async function attachFailedJobLogTails( jobs: PRCheckRunDetails['jobs'], - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, ghOptions: GhExecOptions ): Promise { const failedJobs = jobs diff --git a/src/main/github/client/check/get-pr-check-details.ts b/src/main/github/client/check/get-pr-check-details.ts index 8a5c2c2487d..648ab56343e 100644 --- a/src/main/github/client/check/get-pr-check-details.ts +++ b/src/main/github/client/check/get-pr-check-details.ts @@ -1,6 +1,6 @@ import type { PRCheckRunDetails } from '../../../../shared/github/check-types' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { GITHUB_CHECK_DETAILS_HOST_TIMEOUT_MS, GITHUB_CHECK_DETAILS_TIMEOUT_MESSAGE @@ -14,6 +14,8 @@ import { } from './check-detail-field-mapping' import { rethrowCheckDetailsAbort, waitForCheckDetailsResolution } from './check-details-abort' import { attachFailedJobLogTails } from './check-job-log-tails' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function getPRCheckDetails( repoPath: string, args: { @@ -21,7 +23,7 @@ export async function getPRCheckDetails( workflowRunId?: number checkName?: string url?: string | null - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null }, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {}, diff --git a/src/main/github/client/check/get-pr-checks.ts b/src/main/github/client/check/get-pr-checks.ts index 50db1ee9926..715e2382ff5 100644 --- a/src/main/github/client/check/get-pr-checks.ts +++ b/src/main/github/client/check/get-pr-checks.ts @@ -2,7 +2,7 @@ import type { PRCheckDetail } from '../../../../shared/github/check-types' import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/work-items' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' import { extractExecError } from '../../../git/exec-error' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { mapCheckStatus, mapCheckConclusion } from '../../mappers' import { noteRepositoryRateLimitSpend } from '../../rate-limit' import type { GhExecOptions } from './../github-exec-scope' @@ -22,8 +22,10 @@ import { getPendingApprovalCheckSuiteUrl } from './pr-checks-response-mapping' import { parseActionsRunId } from './check-detail-field-mapping' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function getPRChecksViaRestFallback( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, headSha: string | undefined, ghOptions: GhExecOptions, noCache?: boolean @@ -124,7 +126,7 @@ export async function getPRChecks( repoPath: string, prNumber: number, headSha?: string, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, options?: { noCache?: boolean }, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/client/check/pr-checks-response-mapping.ts b/src/main/github/client/check/pr-checks-response-mapping.ts index 362bbf6e137..70c9bf7e76d 100644 --- a/src/main/github/client/check/pr-checks-response-mapping.ts +++ b/src/main/github/client/check/pr-checks-response-mapping.ts @@ -1,5 +1,5 @@ import type { PRCheckDetail } from '../../../../shared/github/check-types' -import { githubRepositoryWebHost, type GitHubApiRepository } from '../../github-api-repository' +import { githubRepositoryWebHost } from '../../github-api-repository' import { mapCheckRunRESTStatus, mapCheckRunRESTConclusion, @@ -16,6 +16,8 @@ import type { RestCommitStatus } from './pr-checks-graphql-query' import { nullableString, nullableNumber, parseActionsRunId } from './check-detail-field-mapping' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export function isGraphQLCheckRunContext( context: GraphQLStatusCheckContext ): context is GraphQLCheckRunContext { @@ -91,7 +93,7 @@ export function mapRestCommitStatus(status: RestCommitStatus): PRCheckDetail | n } export function mapGraphQLPendingApprovalCheckSuite( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, suite: GraphQLCheckSuite, headSha: string | null | undefined, index: number @@ -108,7 +110,7 @@ export function mapGraphQLPendingApprovalCheckSuite( } export function mapGraphQLPRChecksResponse( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, response: GraphQLPRChecksResponse ): PRCheckDetail[] | null { const pullRequest = response.data?.repository?.pullRequest @@ -175,7 +177,7 @@ export function getPendingApprovalCheckSuiteName( } export function getPendingApprovalCheckSuiteUrl( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, headSha: string, suiteId: number | null | undefined ): string { diff --git a/src/main/github/client/check/rerun-pr-checks.ts b/src/main/github/client/check/rerun-pr-checks.ts index 82059bab664..b15db109819 100644 --- a/src/main/github/client/check/rerun-pr-checks.ts +++ b/src/main/github/client/check/rerun-pr-checks.ts @@ -1,9 +1,11 @@ import type { GitHubRerunPRChecksResult } from '../../../../shared/github/check-types' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + // Why: pure error helpers come from their own modules so tests that mock gh-utils still classify for real. import { extractExecError } from '../../../git/exec-error' import { classifyRerunChecksError } from '../../gh-error-classification' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { getPRChecks } from './get-pr-checks' import { parseActionsRunId } from './check-detail-field-mapping' export async function rerunPRChecks( @@ -12,7 +14,7 @@ export async function rerunPRChecks( options: { headSha?: string failedOnly?: boolean - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null } = {}, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/client/create/add-pr-review-comment.ts b/src/main/github/client/create/add-pr-review-comment.ts index b3c51440a6a..913c2d2ac1a 100644 --- a/src/main/github/client/create/add-pr-review-comment.ts +++ b/src/main/github/client/create/add-pr-review-comment.ts @@ -9,8 +9,10 @@ import { classifyGhError, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { mapReviewCommentResponse } from './../map/review-comment-response' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function addPRReviewComment( args: GitHubPRReviewCommentInput & { connectionId?: string | null @@ -79,7 +81,7 @@ export async function addPRReviewCommentReply( path?: string, line?: number, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/create/pull-request-template.ts b/src/main/github/client/create/pull-request-template.ts index 7f53d429d90..464884f3869 100644 --- a/src/main/github/client/create/pull-request-template.ts +++ b/src/main/github/client/create/pull-request-template.ts @@ -7,10 +7,12 @@ import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions } from '../../../source-control/hosted-review-git-options' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import { githubHostExecOptions } from '../../github-api-repository' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function findOpenPRByHeadBase(args: { repoPath: string - repo: GitHubApiRepository + repo: GitHubOwnerRepo head: string base: string connectionId?: string | null diff --git a/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts b/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts index c1505d80d55..8e3760500ca 100644 --- a/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts +++ b/src/main/github/client/detect/hydrate-work-item-merge-metadata.ts @@ -1,10 +1,10 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import type { GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './repository-merge-metadata' import type { MainWorkItem } from './../map/work-item-field-coercion' export async function hydrateWorkItemRepositoryMergeMetadata( items: MainWorkItem[], - ownerRepo: OwnerRepo | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions, executionScope?: string ): Promise { diff --git a/src/main/github/client/detect/repository-merge-metadata.ts b/src/main/github/client/detect/repository-merge-metadata.ts index a7f8f8179d4..f1849440f71 100644 --- a/src/main/github/client/detect/repository-merge-metadata.ts +++ b/src/main/github/client/detect/repository-merge-metadata.ts @@ -1,6 +1,6 @@ import { normalizeGitHubPRMergeMethodSettings } from '../../../../shared/github/pull-request-merge-methods' import { ghExecFileAsync } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import { githubHostExecOptions } from '../../github-api-repository' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' import type { GhExecOptions } from './../github-exec-scope' @@ -12,8 +12,10 @@ import { cacheRepositoryMergeMetadata, type GitHubRepositoryMergeMetadata } from './repository-merge-metadata-cache' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function detectRepositoryMergeMetadata( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, branchName: string | undefined, ghOptions: GhExecOptions, executionScope: string | undefined = 'default' diff --git a/src/main/github/client/fetch/get-pr-comments.ts b/src/main/github/client/fetch/get-pr-comments.ts index 06df95e29fb..9cc4200ce53 100644 --- a/src/main/github/client/fetch/get-pr-comments.ts +++ b/src/main/github/client/fetch/get-pr-comments.ts @@ -1,11 +1,13 @@ import type { PRComment } from '../../../../shared/github/comment-types' import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/work-items' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { mapGraphQLReactionGroups, type GitHubGraphQLReactionGroup } from '../../comment-reactions' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' import { assertRateLimitBudget } from './../lookup/pr-lookup-rate-limit' import { REVIEW_THREADS_QUERY } from './pr-review-threads-query' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + /** * Get all comments on a PR — both top-level conversation comments and inline * review comments (including suggestions). Uses GraphQL for review threads @@ -14,7 +16,7 @@ import { REVIEW_THREADS_QUERY } from './pr-review-threads-query' export async function getPRComments( repoPath: string, prNumber: number, - options?: { noCache?: boolean; prRepo?: GitHubApiRepository | null }, + options?: { noCache?: boolean; prRepo?: GitHubOwnerRepo | null }, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { diff --git a/src/main/github/client/fetch/get-work-item.ts b/src/main/github/client/fetch/get-work-item.ts index 3178a4c921a..1668b42506e 100644 --- a/src/main/github/client/fetch/get-work-item.ts +++ b/src/main/github/client/fetch/get-work-item.ts @@ -3,8 +3,7 @@ import { acquire, release, classifyGhError, type LocalGitExecOptions } from '../ import { resolveGitHubApiRepository, resolveGitHubApiRepositoryCandidates, - resolveIssueGitHubApiRepositorySource, - type GitHubApiRepository + resolveIssueGitHubApiRepositorySource } from '../../github-api-repository' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' import type { MainWorkItem } from './../map/work-item-field-coercion' @@ -13,6 +12,8 @@ import { fetchPullRequestWorkItem, fetchPullRequestWorkItemFromCandidates } from './work-item-fetch' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function getWorkItem( repoPath: string, number: number, @@ -91,7 +92,7 @@ export async function getWorkItem( export async function getWorkItemByOwnerRepo( repoPath: string, - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, number: number, type: 'issue' | 'pr', connectionId?: string | null, diff --git a/src/main/github/client/fetch/repo-slug-upstream.ts b/src/main/github/client/fetch/repo-slug-upstream.ts index 0e67b06b4e3..2206dd06315 100644 --- a/src/main/github/client/fetch/repo-slug-upstream.ts +++ b/src/main/github/client/fetch/repo-slug-upstream.ts @@ -1,4 +1,4 @@ -import { ghExecFileAsync, acquire, release, type OwnerRepo } from '../../gh-utils' +import { ghExecFileAsync, acquire, release, type GitHubOwnerRepo } from '../../gh-utils' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions @@ -7,15 +7,15 @@ import { getGitHubApiRepositoryForRemote, getOriginGitHubApiRepository, githubRepositorySlugArg, - resolveGitHubRepoExecution, - type GitHubApiRepository + resolveGitHubRepoExecution } from '../../github-api-repository' import { hostedReviewLocalGitOptionArgs, sameOwnerRepo } from './../github-exec-scope' + export async function getRepoSlug( repoPath: string, connectionId?: string | null, options: HostedReviewExecutionOptions = {} -): Promise { +): Promise { return getOriginGitHubApiRepository( repoPath, connectionId, @@ -33,7 +33,7 @@ export async function getRepoUpstream( repoPath: string, connectionId?: string | null, options: HostedReviewExecutionOptions = {} -): Promise { +): Promise { const localGitArgs = hostedReviewLocalGitOptionArgs(options) const localGitOptions = localGitArgs[0] ?? {} const { ownerRepo: origin, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/fetch/work-item-fetch.ts b/src/main/github/client/fetch/work-item-fetch.ts index f0c227d05e7..00d4c1310c2 100644 --- a/src/main/github/client/fetch/work-item-fetch.ts +++ b/src/main/github/client/fetch/work-item-fetch.ts @@ -6,7 +6,7 @@ import { githubRepoContext, type LocalGitExecOptions } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import { githubHostExecOptions } from '../../github-api-repository' import type { GhExecOptions } from './../github-exec-scope' import { resolvePullRequestLookupCandidates } from './../pull-request-lookup-candidates' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' @@ -17,9 +17,11 @@ import { type MainWorkItem } from './../map/work-item-field-coercion' import { mapIssueWorkItem, mapPullRequestWorkItem } from './../map/work-item' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function fetchIssueWorkItem( repoPath: string, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, number: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {}, @@ -60,7 +62,7 @@ export const WORK_ITEM_PR_REVIEW_JSON_FIELDS = 'reviewRequests,latestReviews' export async function fetchPullRequestReviewFields( number: number, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions ): Promise> { try { @@ -92,7 +94,7 @@ export async function fetchPullRequestReviewFields( export async function fetchPullRequestWorkItem( repoPath: string, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, number: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/client/github-exec-scope.ts b/src/main/github/client/github-exec-scope.ts index fbe3cd78c7a..02043b0f6d7 100644 --- a/src/main/github/client/github-exec-scope.ts +++ b/src/main/github/client/github-exec-scope.ts @@ -1,4 +1,4 @@ -import type { LocalGitExecOptions, OwnerRepo } from '../gh-utils' +import type { LocalGitExecOptions, GitHubOwnerRepo } from '../gh-utils' import { hasHostedReviewLocalGitOptions, getHostedReviewLocalGitOptions @@ -23,13 +23,16 @@ export function githubPRStackExecutionScope( return connectionId ? `ssh:${connectionId}` : `local:${localGitOptions.wslDistro ?? 'host'}` } -export function sameOwnerRepo(left: OwnerRepo | null, right: OwnerRepo | null): boolean { +export function sameOwnerRepo( + left: GitHubOwnerRepo | null, + right: GitHubOwnerRepo | null +): boolean { // Why: casing does not distinguish GitHub repos, but the same slug on different hosts does. return Boolean(left && right && githubRepoIdentityKey(left) === githubRepoIdentityKey(right)) } // Why: exact-linked fallback has no dataRepo; derive its host-aware identity from the web URL for merged-PR membership checks. -export function ownerRepoFromPullRequestUrl(url: string): OwnerRepo | null { +export function ownerRepoFromPullRequestUrl(url: string): GitHubOwnerRepo | null { const match = url.match(/^https?:\/\/([^/\s]+)\/([^/\s]+)\/([^/\s]+)\/pull\/\d+/) return match ? { owner: match[2], repo: match[3], host: match[1] } : null } diff --git a/src/main/github/client/list/count-work-items.ts b/src/main/github/client/list/count-work-items.ts index 570220586f9..eafdc1a6ea1 100644 --- a/src/main/github/client/list/count-work-items.ts +++ b/src/main/github/client/list/count-work-items.ts @@ -8,7 +8,7 @@ import { ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions, - type OwnerRepo + type GitHubOwnerRepo } from '../../gh-utils' import { githubHostExecOptions, @@ -27,7 +27,7 @@ import { buildSearchQueryString, defaultOpenWorkItemQuery } from './work-item-se import { searchWorkItemCount, usesGraphqlWorkItemSearch } from './work-item-search-page' export async function countWorkItemsForQuery( repoPath: string, - ownerRepo: OwnerRepo, + ownerRepo: GitHubOwnerRepo, query: ParsedTaskQuery, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/client/list/work-item-issue-page.ts b/src/main/github/client/list/work-item-issue-page.ts index ac1273887d2..b36a53be5a8 100644 --- a/src/main/github/client/list/work-item-issue-page.ts +++ b/src/main/github/client/list/work-item-issue-page.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import type { ParsedTaskQuery } from '../../../../shared/task-query' -import { ghExecFileAsync, type LocalGitExecOptions, type OwnerRepo } from '../../gh-utils' +import { ghExecFileAsync, type LocalGitExecOptions, type GitHubOwnerRepo } from '../../gh-utils' import { noteRepositoryRateLimitSpend } from '../../rate-limit' import type { GitHubRepoExecOptions } from '../../github-api-repository' import { fetchIssueWorkItem } from '../fetch/work-item-fetch' @@ -50,7 +50,7 @@ function restActor(actor: Actor | null): Record | null { export async function listIssueWorkItemPage(args: { repoPath: string - ownerRepo: OwnerRepo + ownerRepo: GitHubOwnerRepo query: ParsedTaskQuery limit: number page: number diff --git a/src/main/github/client/list/work-item-list-request.ts b/src/main/github/client/list/work-item-list-request.ts index 20c1d482f1a..6d053369119 100644 --- a/src/main/github/client/list/work-item-list-request.ts +++ b/src/main/github/client/list/work-item-list-request.ts @@ -3,7 +3,7 @@ import type { IssueSourcePreference } from '../../../../shared/repo-types' import type { ParsedTaskQuery } from '../../../../shared/task-query' import { GITHUB_WORK_ITEMS_SSH_REMOTE_REQUIRED_MESSAGE } from '../../../../shared/work-items' import { shouldProbeGitRemote } from '../../../git/remote-name-listing' -import type { LocalGitExecOptions, OwnerRepo } from '../../gh-utils' +import type { LocalGitExecOptions, GitHubOwnerRepo } from '../../gh-utils' import { getGitHubApiRepositoryForRemote, getOriginGitHubApiRepository @@ -21,7 +21,7 @@ export function normalizeWorkItemPage(page: number | undefined): number { export function buildWorkItemListRequest(args: { kind: 'issue' | 'pr' - ownerRepo: OwnerRepo + ownerRepo: GitHubOwnerRepo limit: number query: ParsedTaskQuery page: number @@ -110,8 +110,8 @@ export type PartialWorkItemsResult = { export function assertSshRepoHasResolvedGitHubSource(args: { connectionId?: string | null - issueOwnerRepo: OwnerRepo | null - prOwnerRepo: OwnerRepo | null + issueOwnerRepo: GitHubOwnerRepo | null + prOwnerRepo: GitHubOwnerRepo | null }): void { if (!args.connectionId || args.issueOwnerRepo || args.prOwnerRepo) { return @@ -121,9 +121,9 @@ export function assertSshRepoHasResolvedGitHubSource(args: { } export type ResolvedPrWorkItemSource = { - source: OwnerRepo | null - originCandidate: OwnerRepo | null - upstreamCandidate: OwnerRepo | null + source: GitHubOwnerRepo | null + originCandidate: GitHubOwnerRepo | null + upstreamCandidate: GitHubOwnerRepo | null } // Why: only an explicit `origin` preference is origin-only; `upstream`/`auto`/ diff --git a/src/main/github/client/list/work-item-pages.ts b/src/main/github/client/list/work-item-pages.ts index 73871ae1a45..26ad7605a0a 100644 --- a/src/main/github/client/list/work-item-pages.ts +++ b/src/main/github/client/list/work-item-pages.ts @@ -9,7 +9,7 @@ import { ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions, - type OwnerRepo + type GitHubOwnerRepo } from '../../gh-utils' import { githubHostExecOptions } from '../../github-api-repository' import { githubPRStackExecutionScope } from './../github-exec-scope' @@ -24,8 +24,8 @@ import { import { listIssueWorkItemPage } from './work-item-issue-page' export async function listRecentWorkItems( repoPath: string, - issueOwnerRepo: OwnerRepo | null, - prOwnerRepo: OwnerRepo | null, + issueOwnerRepo: GitHubOwnerRepo | null, + prOwnerRepo: GitHubOwnerRepo | null, limit: number, page: number, connectionId?: string | null, @@ -112,8 +112,8 @@ export async function listRecentWorkItems( export async function listQueriedWorkItems( repoPath: string, - issueOwnerRepo: OwnerRepo | null, - prOwnerRepo: OwnerRepo | null, + issueOwnerRepo: GitHubOwnerRepo | null, + prOwnerRepo: GitHubOwnerRepo | null, query: ParsedTaskQuery, limit: number, page?: number, diff --git a/src/main/github/client/list/work-item-search-page.ts b/src/main/github/client/list/work-item-search-page.ts index e03c16e777b..f568dd77c10 100644 --- a/src/main/github/client/list/work-item-search-page.ts +++ b/src/main/github/client/list/work-item-search-page.ts @@ -1,6 +1,6 @@ import { BoundedMap } from '../../../../shared/bounded-map' import { isDefaultGitHubHost } from '../../../../shared/github/repository-identity-key' -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import type { GitHubRepoExecOptions } from '../../github-api-repository' import { requestWorkItemSearch, @@ -17,7 +17,7 @@ const cursors = new BoundedMap({ }) export function usesGraphqlWorkItemSearch( - ownerRepo: OwnerRepo, + ownerRepo: GitHubOwnerRepo, options: GitHubRepoExecOptions ): boolean { return isDefaultGitHubHost( diff --git a/src/main/github/client/lookup/branch-lookup-derived-data.ts b/src/main/github/client/lookup/branch-lookup-derived-data.ts index 295b55ef630..21af7f05070 100644 --- a/src/main/github/client/lookup/branch-lookup-derived-data.ts +++ b/src/main/github/client/lookup/branch-lookup-derived-data.ts @@ -1,5 +1,5 @@ import { getPRConflictSummary } from '../../conflict-summary' -import type { ghRepoExecOptions, OwnerRepo } from '../../gh-utils' +import type { ghRepoExecOptions, GitHubOwnerRepo } from '../../gh-utils' import { hydrateGitHubPRStack } from '../../github-pr-stack' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import { derivePullRequestMergeable, type PullRequestLookupData } from './pull-request-lookup-data' @@ -7,7 +7,7 @@ import { getCachedGitHubPRStackSummary } from './pr-stack-summary-cache' export async function derivePRRefreshData(args: { data: PullRequestLookupData - dataRepo: OwnerRepo | null + dataRepo: GitHubOwnerRepo | null repoPath: string connectionId?: string | null localGitOptions: { wslDistro?: string } diff --git a/src/main/github/client/lookup/branch-lookup-resolution.ts b/src/main/github/client/lookup/branch-lookup-resolution.ts index 2efe7d9cd25..56540bd7ac0 100644 --- a/src/main/github/client/lookup/branch-lookup-resolution.ts +++ b/src/main/github/client/lookup/branch-lookup-resolution.ts @@ -1,5 +1,5 @@ import type { PRRefreshOutcome } from '../../../../shared/github/pull-request-refresh-types' -import type { ghRepoExecOptions, OwnerRepo } from '../../gh-utils' +import type { ghRepoExecOptions, GitHubOwnerRepo } from '../../gh-utils' import { isCommitPartOfMergedPR, type MergedPRCommitMembership @@ -68,8 +68,8 @@ export async function resolvePRForBranchOutcome(input: { noteRepositoryRateLimitSpend(headRepo ?? candidates[0], bucket, 1, ghOptions) } let data: PullRequestLookupData | null = null - let dataRepo: OwnerRepo | null = null - let dataHeadRepo: OwnerRepo | null = headRepo + let dataRepo: GitHubOwnerRepo | null = null + let dataHeadRepo: GitHubOwnerRepo | null = headRepo let pendingBranchLookupError: unknown let hasPendingBranchLookupError = false let currentHeadOidForMergedImplicit: string | null | undefined @@ -83,7 +83,7 @@ export async function resolvePRForBranchOutcome(input: { let headDivergedFromMergedPRAtOid: string | null = null const mergedPRContainsHead = async ( candidate: PullRequestLookupData, - candidateRepo: OwnerRepo | null, + candidateRepo: GitHubOwnerRepo | null, headOid: string | null ): Promise => { if (!candidateRepo || !headOid) { @@ -102,7 +102,7 @@ export async function resolvePRForBranchOutcome(input: { } const recordLinkedMergedPRDivergence = async ( candidate: PullRequestLookupData | null, - candidateRepo: OwnerRepo | null + candidateRepo: GitHubOwnerRepo | null ): Promise => { if ( typeof linkedPRNumber !== 'number' || @@ -125,7 +125,7 @@ export async function resolvePRForBranchOutcome(input: { } const hideMergedImplicitPR = async ( candidate: PullRequestLookupData | null, - candidateRepo: OwnerRepo | null + candidateRepo: GitHubOwnerRepo | null ) => { if (!candidate || !isMergedImplicitPR(candidate, linkedPRNumber)) { return false diff --git a/src/main/github/client/lookup/pr-branch-lookup.ts b/src/main/github/client/lookup/pr-branch-lookup.ts index 2ee667ceacc..67314e0b9b4 100644 --- a/src/main/github/client/lookup/pr-branch-lookup.ts +++ b/src/main/github/client/lookup/pr-branch-lookup.ts @@ -1,6 +1,6 @@ import { ghExecFileAsync } from '../../gh-utils' -import type { OwnerRepo, ghRepoExecOptions } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import type { GitHubOwnerRepo, ghRepoExecOptions } from '../../gh-utils' +import { githubHostExecOptions } from '../../github-api-repository' import type { GhExecOptions } from './../github-exec-scope' import { isNoPullRequestError } from './../gh-error-predicates' import { @@ -12,8 +12,9 @@ import { type RestPullRequest } from './pull-request-lookup-data' import { getPRByNumber } from './pr-number-lookup' + export async function getRestPRForBranch( - prRepo: GitHubApiRepository, + prRepo: GitHubOwnerRepo, headOwner: string, branchName: string, ghOptions: ReturnType @@ -29,7 +30,7 @@ export async function getRestPRForBranch( } export async function getFallbackPRListForBranch( - prRepo: GitHubApiRepository, + prRepo: GitHubOwnerRepo, branchName: string, ghOptions: ReturnType ): Promise { @@ -55,7 +56,7 @@ export async function getFallbackPRListForBranch( } export async function hydrateBranchLookupWithExactPR( - ownerRepo: OwnerRepo, + ownerRepo: GitHubOwnerRepo, branchData: PullRequestLookupData | null, ghOptions: GhExecOptions, executionScope: string @@ -74,14 +75,14 @@ export async function hydrateBranchLookupWithExactPR( } export async function lookupPRByBranchName(args: { - candidates: OwnerRepo[] - headRepo: OwnerRepo | null + candidates: GitHubOwnerRepo[] + headRepo: GitHubOwnerRepo | null branchName: string ghOptions: GhExecOptions executionScope: string }): Promise<{ data: PullRequestLookupData | null - dataRepo: OwnerRepo | null + dataRepo: GitHubOwnerRepo | null pendingError?: unknown }> { if (args.candidates.length > 0) { diff --git a/src/main/github/client/lookup/pr-lookup-rate-limit.ts b/src/main/github/client/lookup/pr-lookup-rate-limit.ts index 3905d27622b..cfbba98e35e 100644 --- a/src/main/github/client/lookup/pr-lookup-rate-limit.ts +++ b/src/main/github/client/lookup/pr-lookup-rate-limit.ts @@ -1,5 +1,5 @@ import { ghRepoExecOptions, githubRepoContext, type LocalGitExecOptions } from '../../gh-utils' -import { getOriginGitHubApiRepository, type GitHubApiRepository } from '../../github-api-repository' +import { getOriginGitHubApiRepository } from '../../github-api-repository' import { getRateLimit, repositoryRateLimitGuard, @@ -7,6 +7,8 @@ import { type RateLimitBucketKind } from '../../rate-limit' import type { GhExecOptions } from './../github-exec-scope' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + // Why: a branch lookup prefers REST but can fall back to `gh pr list` and // `gh pr view`, so both buckets are guarded and charged. Mirrors the PR refresh // coordinator's own estimate. @@ -55,7 +57,7 @@ export async function getGitHubPRLookupRateLimitBlock( export async function assertRateLimitBudget( bucket: RateLimitBucketKind, - repository?: GitHubApiRepository | null, + repository?: GitHubOwnerRepo | null, executionOptions?: Pick ): Promise { if (spendsSharedGitHubComQuota(repository, executionOptions)) { diff --git a/src/main/github/client/lookup/pr-number-lookup.ts b/src/main/github/client/lookup/pr-number-lookup.ts index 42a605eb295..99691614de6 100644 --- a/src/main/github/client/lookup/pr-number-lookup.ts +++ b/src/main/github/client/lookup/pr-number-lookup.ts @@ -1,6 +1,6 @@ import { ghExecFileAsync } from '../../gh-utils' -import type { OwnerRepo, ghRepoExecOptions } from '../../gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from '../../github-api-repository' +import type { GitHubOwnerRepo, ghRepoExecOptions } from '../../gh-utils' +import { githubHostExecOptions } from '../../github-api-repository' import { isNoPullRequestError, isNotFoundGhError, @@ -16,7 +16,7 @@ import { import { hydratePullRequestLookupData } from './pull-request-lookup-hydration' import { isGitObjectId, isUsableRestStackMetadata } from './rest-stack-metadata-validation' export async function getRestPRByNumber( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, number: number, ghOptions: ReturnType, options: { requireUsableStackMetadata?: boolean } = {} @@ -48,7 +48,7 @@ export async function getRestPRByNumber( } export async function getPRByNumber( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, number: number, ghOptions: ReturnType, executionScope: string, @@ -104,11 +104,11 @@ export async function getPRByNumber( } export async function lookupPRByNumber(args: { - candidates: OwnerRepo[] + candidates: GitHubOwnerRepo[] number: number ghOptions: ReturnType executionScope: string -}): Promise<{ data: PullRequestLookupData | null; dataRepo: OwnerRepo | null }> { +}): Promise<{ data: PullRequestLookupData | null; dataRepo: GitHubOwnerRepo | null }> { for (const candidate of args.candidates) { try { const linkedData = await getPRByNumber( diff --git a/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts b/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts index 1a494c407cc..96d2fc30e2a 100644 --- a/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts +++ b/src/main/github/client/lookup/pr-refresh-outcome-assembly.ts @@ -5,13 +5,13 @@ import type { GitHubPRStack } from '../../../../shared/github/pull-request-types' import { deriveCheckStatus, mapPRState } from '../../mappers' -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import type { PullRequestLookupData } from './pull-request-lookup-data' export function assemblePRRefreshFoundOutcome(args: { data: PullRequestLookupData - dataRepo: OwnerRepo | null - dataHeadRepo: OwnerRepo | null + dataRepo: GitHubOwnerRepo | null + dataHeadRepo: GitHubOwnerRepo | null stack: GitHubPRStack | undefined mergeable: PRMergeableState stackMergeQueueRequired: boolean | null | undefined diff --git a/src/main/github/client/lookup/pr-stack-summary-cache.ts b/src/main/github/client/lookup/pr-stack-summary-cache.ts index 0c48915c1c4..d85ae8927db 100644 --- a/src/main/github/client/lookup/pr-stack-summary-cache.ts +++ b/src/main/github/client/lookup/pr-stack-summary-cache.ts @@ -1,6 +1,5 @@ -import type { GitHubPRStack } from '../../../../shared/github/pull-request-types' +import type { GitHubOwnerRepo, GitHubPRStack } from '../../../../shared/github/pull-request-types' import type { ghRepoExecOptions } from '../../gh-utils' -import type { GitHubApiRepository } from '../../github-api-repository' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' import { getRestPRByNumber } from './pr-number-lookup' export const PR_STACK_SUMMARY_CACHE_TTL_MS = 60_000 @@ -37,7 +36,7 @@ export function prunePRStackSummaryCache(now = Date.now()): void { } export async function getCachedGitHubPRStackSummary( - ownerRepo: GitHubApiRepository, + ownerRepo: GitHubOwnerRepo, number: number, ghOptions: ReturnType, executionScope: string diff --git a/src/main/github/client/lookup/pull-request-lookup-hydration.ts b/src/main/github/client/lookup/pull-request-lookup-hydration.ts index bf2993a0f6d..77122374302 100644 --- a/src/main/github/client/lookup/pull-request-lookup-hydration.ts +++ b/src/main/github/client/lookup/pull-request-lookup-hydration.ts @@ -1,4 +1,4 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import type { GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import { @@ -6,7 +6,7 @@ import { type PullRequestLookupData } from './pull-request-lookup-data' export async function hydratePullRequestLookupData( - ownerRepo: OwnerRepo, + ownerRepo: GitHubOwnerRepo, data: PullRequestLookupData, ghOptions: GhExecOptions, executionScope: string diff --git a/src/main/github/client/lookup/pull-request-push-target.ts b/src/main/github/client/lookup/pull-request-push-target.ts index 3dc19733c6c..dd568ddf1f2 100644 --- a/src/main/github/client/lookup/pull-request-push-target.ts +++ b/src/main/github/client/lookup/pull-request-push-target.ts @@ -9,14 +9,12 @@ import { getRemoteUrlForRepo, type LocalGitExecOptions } from '../../gh-utils' -import { - getGitHubApiRepositoryForRemote, - githubHostExecOptions, - type GitHubApiRepository -} from '../../github-api-repository' +import { getGitHubApiRepositoryForRemote, githubHostExecOptions } from '../../github-api-repository' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' import { isNotFoundGhError } from './../gh-error-predicates' import { resolvePullRequestLookupCandidates } from './../pull-request-lookup-candidates' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export function pickPushRemoteUrl(args: { originUrl: string | null cloneUrl: string @@ -71,7 +69,7 @@ export async function getPullRequestPushTarget( await acquire() try { let prStdout = '' - let matchedRepository: GitHubApiRepository | null = null + let matchedRepository: GitHubOwnerRepo | null = null for (const candidate of candidates) { try { const { stdout } = await ghExecFileAsync( diff --git a/src/main/github/client/lookup/tracked-upstream-cache.ts b/src/main/github/client/lookup/tracked-upstream-cache.ts index 167bdad435d..97d7cab33cb 100644 --- a/src/main/github/client/lookup/tracked-upstream-cache.ts +++ b/src/main/github/client/lookup/tracked-upstream-cache.ts @@ -1,5 +1,5 @@ import { splitRemoteBranchName } from '../../../../shared/git-effective-upstream' -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import { readLocalGitConfigSignature } from '../../local-git-config-signature' import { githubRepoIdentityKey } from '../../../../shared/github/repository-identity-key' export type TrackedUpstreamBranch = { @@ -91,8 +91,8 @@ export function parseTrackedUpstreamBranch(upstreamRef: string): TrackedUpstream export function shouldRetryTrackedUpstreamBranch( upstreamBranch: TrackedUpstreamBranch, branchName: string, - upstreamHeadRepo: OwnerRepo, - headRepo: OwnerRepo | null + upstreamHeadRepo: GitHubOwnerRepo, + headRepo: GitHubOwnerRepo | null ): boolean { if (upstreamBranch.branchName !== branchName) { return true diff --git a/src/main/github/client/map/work-item.ts b/src/main/github/client/map/work-item.ts index d127c1a6e67..2f5eb4bf609 100644 --- a/src/main/github/client/map/work-item.ts +++ b/src/main/github/client/map/work-item.ts @@ -1,4 +1,4 @@ -import type { OwnerRepo } from '../../gh-utils' +import type { GitHubOwnerRepo } from '../../gh-utils' import { authorFieldsFromUnknown, extractHeadOwnerLogin, @@ -36,7 +36,7 @@ export function mapIssueWorkItem(item: Record): MainWorkItem { export function mapPullRequestWorkItem( item: Record, - baseOwnerRepo: OwnerRepo | null = null + baseOwnerRepo: GitHubOwnerRepo | null = null ): MainWorkItem { // Why: fork PRs are disabled in the Start-from picker; compare head owner to the selected repo's owner. const headOwnerLogin = extractHeadOwnerLogin(item) diff --git a/src/main/github/client/merge/merge-pr.ts b/src/main/github/client/merge/merge-pr.ts index 07518d7993c..45a46196735 100644 --- a/src/main/github/client/merge/merge-pr.ts +++ b/src/main/github/client/merge/merge-pr.ts @@ -1,7 +1,10 @@ -import type { PRConflictSummary } from '../../../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + PRConflictSummary +} from '../../../../shared/github/pull-request-types' import { getPRConflictSummary } from '../../conflict-summary' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { mergeGitHubPRStack } from '../../github-pr-stack' import { githubPRStackExecutionScope, type GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' @@ -17,7 +20,7 @@ export async function mergePR( prNumber: number, method: 'merge' | 'squash' | 'rebase' = 'squash', connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( @@ -104,7 +107,7 @@ export async function mergePR( export async function getPRMergeBlocker( repoPath: string, prNumber: number, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/github/client/merge/pr-auto-merge.ts b/src/main/github/client/merge/pr-auto-merge.ts index 0cc65c5fb03..e62d7063947 100644 --- a/src/main/github/client/merge/pr-auto-merge.ts +++ b/src/main/github/client/merge/pr-auto-merge.ts @@ -1,4 +1,7 @@ -import type { GitHubPRMergeMethod } from '../../../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + GitHubPRMergeMethod +} from '../../../../shared/github/pull-request-types' import { ghExecFileAsync, acquire, @@ -6,7 +9,7 @@ import { classifyGhError, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { githubPRStackExecutionScope, type GhExecOptions } from './../github-exec-scope' import { detectRepositoryMergeMetadata } from './../detect/repository-merge-metadata' import { getRestPRByNumber } from './../lookup/pr-number-lookup' @@ -35,7 +38,7 @@ export type PRAutoMergeIdentity = { export async function getPRAutoMergeIdentity( prNumber: number, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions ): Promise { const args = ['pr', 'view', String(prNumber), '--json', PR_AUTO_MERGE_IDENTITY_JSON_FIELDS] @@ -54,7 +57,7 @@ export async function getPRAutoMergeIdentity( export async function runPRAutoMergeCommand( prNumber: number, method: GitHubPRMergeMethod, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions ): Promise { const args = ['pr', 'merge', String(prNumber), '--auto', `--${method}`] @@ -69,7 +72,7 @@ export async function runPRAutoMergeCommand( export async function shouldUseMergeQueueAutoMerge( pr: PRAutoMergeIdentity, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions, executionScope?: string ): Promise { @@ -88,7 +91,7 @@ export async function shouldUseMergeQueueAutoMerge( export async function enablePRAutoMerge( prNumber: number, method: GitHubPRMergeMethod, - ownerRepo: GitHubApiRepository | null, + ownerRepo: GitHubOwnerRepo | null, ghOptions: GhExecOptions, executionScope?: string ): Promise<{ ok: true } | { ok: false; error: string }> { @@ -150,7 +153,7 @@ export async function setPRAutoMerge( enabled: boolean, method: GitHubPRMergeMethod = 'squash', connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/pull-request-lookup-candidates.ts b/src/main/github/client/pull-request-lookup-candidates.ts index 4fedbd3d42e..cc3112eab89 100644 --- a/src/main/github/client/pull-request-lookup-candidates.ts +++ b/src/main/github/client/pull-request-lookup-candidates.ts @@ -2,16 +2,17 @@ import type { IssueSourcePreference } from '../../../shared/repo-types' import type { LocalGitExecOptions } from '../gh-utils' import { getOriginGitHubApiRepository, - resolveGitHubApiRepositoryCandidates, - type GitHubApiRepository + resolveGitHubApiRepositoryCandidates } from '../github-api-repository' +import type { GitHubOwnerRepo } from '../../../shared/github/pull-request-types' + // resolvePrWorkItemSource list semantics. export async function resolvePullRequestLookupCandidates( repoPath: string, preference: IssueSourcePreference | undefined, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { if (preference === 'origin') { const origin = await getOriginGitHubApiRepository(repoPath, connectionId, localGitOptions) return origin ? [origin] : [] diff --git a/src/main/github/client/update/pr-comment-reaction.ts b/src/main/github/client/update/pr-comment-reaction.ts index 872ba463eb9..a57c27286c5 100644 --- a/src/main/github/client/update/pr-comment-reaction.ts +++ b/src/main/github/client/update/pr-comment-reaction.ts @@ -1,15 +1,17 @@ import type { GitHubReactionContent } from '../../../../shared/github/comment-types' import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { toGraphQLReactionContent } from '../../comment-reactions' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function setPRCommentReaction( repoPath: string, reactionSubjectId: string, content: GitHubReactionContent, reacted: boolean, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const mutation = reacted ? 'addReaction' : 'removeReaction' diff --git a/src/main/github/client/update/pr-details.ts b/src/main/github/client/update/pr-details.ts index 2348174e931..0ac1cb7b560 100644 --- a/src/main/github/client/update/pr-details.ts +++ b/src/main/github/client/update/pr-details.ts @@ -5,7 +5,9 @@ import { classifyPullRequestUpdateError, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + /** * Update a PR's title. */ @@ -14,7 +16,7 @@ export async function updatePRTitle( prNumber: number, title: string, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( @@ -49,7 +51,7 @@ export async function updatePRDetails( prNumber: number, updates: { title?: string; body?: string }, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/update/pr-file-viewed.ts b/src/main/github/client/update/pr-file-viewed.ts index 018de3c328b..0b861d9fb6e 100644 --- a/src/main/github/client/update/pr-file-viewed.ts +++ b/src/main/github/client/update/pr-file-viewed.ts @@ -1,6 +1,8 @@ import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + /** * Mark or unmark a PR file as viewed via GitHub's GraphQL API. */ @@ -8,7 +10,7 @@ export async function setPRFileViewed(args: { repoPath: string connectionId?: string | null localGitOptions?: LocalGitExecOptions - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null pullRequestId: string path: string viewed: boolean diff --git a/src/main/github/client/update/pr-ready.ts b/src/main/github/client/update/pr-ready.ts index 40c764d290d..840823478af 100644 --- a/src/main/github/client/update/pr-ready.ts +++ b/src/main/github/client/update/pr-ready.ts @@ -5,13 +5,14 @@ import { release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' export async function markPRReadyForReview( repoPath: string, prNumber: number, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/update/pr-reviewers.ts b/src/main/github/client/update/pr-reviewers.ts index e502158f661..623d1d4d865 100644 --- a/src/main/github/client/update/pr-reviewers.ts +++ b/src/main/github/client/update/pr-reviewers.ts @@ -1,11 +1,13 @@ import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function requestPRReviewers( repoPath: string, prNumber: number, reviewers: string[], connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const logins = reviewers.map((reviewer) => reviewer.trim()).filter(Boolean) @@ -46,7 +48,7 @@ export async function removePRReviewers( prNumber: number, reviewers: string[], connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const logins = reviewers.map((reviewer) => reviewer.trim()).filter(Boolean) diff --git a/src/main/github/client/update/pr-state.ts b/src/main/github/client/update/pr-state.ts index d22916c8d54..3515dbbfdc4 100644 --- a/src/main/github/client/update/pr-state.ts +++ b/src/main/github/client/update/pr-state.ts @@ -6,13 +6,15 @@ import { classifyPullRequestUpdateError, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + export async function updatePRState( repoPath: string, prNumber: number, updates: GitHubPullRequestStateUpdate, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/client/update/resolve-review-thread.ts b/src/main/github/client/update/resolve-review-thread.ts index 2bad5d69b9c..840646f993e 100644 --- a/src/main/github/client/update/resolve-review-thread.ts +++ b/src/main/github/client/update/resolve-review-thread.ts @@ -1,6 +1,8 @@ import { ghExecFileAsync, acquire, release, type LocalGitExecOptions } from '../../gh-utils' -import { resolveGitHubRepoExecution, type GitHubApiRepository } from '../../github-api-repository' +import { resolveGitHubRepoExecution } from '../../github-api-repository' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from '../../rate-limit' +import type { GitHubOwnerRepo } from '../../../../shared/github/pull-request-types' + /** * Resolve or unresolve a PR review thread via GraphQL. */ @@ -9,7 +11,7 @@ export async function resolveReviewThread( threadId: string, resolve: boolean, connectionId?: string | null, - prRepo?: GitHubApiRepository | null, + prRepo?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const mutation = resolve ? 'resolveReviewThread' : 'unresolveReviewThread' diff --git a/src/main/github/gh-utils.ts b/src/main/github/gh-utils.ts index 61b3c2d877d..b4838a29206 100644 --- a/src/main/github/gh-utils.ts +++ b/src/main/github/gh-utils.ts @@ -29,9 +29,9 @@ export type { GitHubRemoteIdentity, GitHubRemoteIdentityProbeOptions, GitHubRepoContext, - LocalGitExecOptions, - OwnerRepo + LocalGitExecOptions } from './github-repository-identity' +export type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' export { getIssueOwnerRepo, getOwnerRepo, diff --git a/src/main/github/github-api-repository-remote-probe.ts b/src/main/github/github-api-repository-remote-probe.ts index 31159a4a8c4..af4eba8e3c9 100644 --- a/src/main/github/github-api-repository-remote-probe.ts +++ b/src/main/github/github-api-repository-remote-probe.ts @@ -1,4 +1,3 @@ -import type { GitHubApiRepository } from './github-api-repository' import { getOwnerRepoForRemote, type GitHubRemoteIdentityProbeOptions, @@ -12,12 +11,13 @@ import { githubApiRepositoryProbeCacheKey, resolveGitHubApiRepositoryProbe } from './github-api-repository-probe' +import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' // Why: cache the uncached Enterprise remote probe used by hot paths. const ORIGIN_REPO_CACHE_TTL_MS = 30_000 const ORIGIN_REPO_CACHE_MAX_ENTRIES = 512 -const originRepoCache = new Map() -const originRepoInFlight = new Map>() +const originRepoCache = new Map() +const originRepoInFlight = new Map>() /** @internal - exposed for tests only */ export function _resetOriginGitHubApiRepositoryCache(): void { @@ -51,7 +51,7 @@ export async function getGitHubApiRepositoryForRemote( connectionId?: string | null, localGitOptions: LocalGitExecOptions = {}, probeOptions: GitHubRemoteIdentityProbeOptions = {} -): Promise { +): Promise { // Why: generic PR resolution prefers upstream, but this API represents the // caller-selected remote exactly (#7331). const requireVerifiedSshProbe = probeOptions.requireVerifiedSshProbe === true @@ -127,6 +127,6 @@ export async function getOriginGitHubApiRepository( repoPath: string, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { return getGitHubApiRepositoryForRemote(repoPath, 'origin', connectionId, localGitOptions) } diff --git a/src/main/github/github-api-repository.ts b/src/main/github/github-api-repository.ts index db3eccbc106..31059b2d255 100644 --- a/src/main/github/github-api-repository.ts +++ b/src/main/github/github-api-repository.ts @@ -23,13 +23,12 @@ export { githubRepositorySlugArg, githubRepositoryWebHost } from './github-repository-host' -export type GitHubApiRepository = GitHubOwnerRepo export type GitHubRepoExecOptions = ReturnType & { host?: string env?: NodeJS.ProcessEnv } export type GitHubRepoExecution = { - ownerRepo: GitHubApiRepository | null + ownerRepo: GitHubOwnerRepo | null ghOptions: GitHubRepoExecOptions } export { @@ -43,7 +42,7 @@ export async function getIssueGitHubApiRepository( repoPath: string, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { const originPromise = getGitHubApiRepositoryForRemote( repoPath, 'origin', @@ -67,8 +66,8 @@ export async function getIssueGitHubApiRepository( } export type GitHubApiRepositoryCandidates = { - candidates: GitHubApiRepository[] - headRepo: GitHubApiRepository | null + candidates: GitHubOwnerRepo[] + headRepo: GitHubOwnerRepo | null } /** Hosted mirror of resolvePRRepositoryCandidates: upstream first, then origin. */ @@ -108,7 +107,7 @@ export async function resolveGitHubApiRepositoryCandidates( } const origin = originResult.value const seen = new Set() - const candidates: GitHubApiRepository[] = [] + const candidates: GitHubOwnerRepo[] = [] for (const candidate of [upstream, origin]) { if (!candidate) { continue @@ -124,7 +123,7 @@ export async function resolveGitHubApiRepositoryCandidates( } export type ResolvedGitHubApiRepositorySource = { - source: GitHubApiRepository | null + source: GitHubOwnerRepo | null /** True when explicit upstream is gone and resolver fell back to origin. */ fellBack: boolean } @@ -173,10 +172,10 @@ export async function resolveIssueGitHubApiRepositorySource( export async function resolveGitHubApiRepository( repoPath: string, - repository?: GitHubApiRepository | null, + repository?: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { if (repository && !isValidGitHubApiRepository(repository)) { return null } diff --git a/src/main/github/github-owner-repo-selection.ts b/src/main/github/github-owner-repo-selection.ts index 2e05613d0b8..b38447fa082 100644 --- a/src/main/github/github-owner-repo-selection.ts +++ b/src/main/github/github-owner-repo-selection.ts @@ -1,17 +1,14 @@ import type { IssueSourcePreference } from '../../shared/repo-types' import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' import { shouldProbeGitRemote } from '../git/remote-name-listing' -import { - getOwnerRepoForRemote, - type LocalGitExecOptions, - type OwnerRepo -} from './github-repository-identity' +import { getOwnerRepoForRemote, type LocalGitExecOptions } from './github-repository-identity' +import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' export async function getOwnerRepo( repoPath: string, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { // Why: on a fork checkout PRs live on the upstream parent, not origin (#7331). const originPromise = getOwnerRepoForRemote(repoPath, 'origin', connectionId, localGitOptions) if (await shouldProbeGitRemote(repoPath, 'upstream', connectionId, localGitOptions)) { @@ -31,8 +28,8 @@ export async function getOwnerRepo( export const getIssueOwnerRepo = getOwnerRepo export type PRRepositoryCandidates = { - candidates: OwnerRepo[] - headRepo: OwnerRepo | null + candidates: GitHubOwnerRepo[] + headRepo: GitHubOwnerRepo | null } export async function resolvePRRepositoryCandidates( @@ -54,7 +51,7 @@ export async function resolvePRRepositoryCandidates( originPromise ]) const seen = new Set() - const candidates: OwnerRepo[] = [] + const candidates: GitHubOwnerRepo[] = [] for (const candidate of [upstream, origin]) { if (!candidate) { @@ -72,7 +69,7 @@ export async function resolvePRRepositoryCandidates( } export type ResolvedIssueSource = { - source: OwnerRepo | null + source: GitHubOwnerRepo | null /** True when explicit upstream is gone and resolver fell back to origin. */ fellBack: boolean } diff --git a/src/main/github/github-pr-stack-async-merge.ts b/src/main/github/github-pr-stack-async-merge.ts index d5375a468ac..b94446cc6e3 100644 --- a/src/main/github/github-pr-stack-async-merge.ts +++ b/src/main/github/github-pr-stack-async-merge.ts @@ -1,11 +1,7 @@ -import type { GitHubPRMergeMethod } from '../../shared/github/pull-request-types' +import type { GitHubOwnerRepo, GitHubPRMergeMethod } from '../../shared/github/pull-request-types' import { ghExecFileAsync } from '../git/runner' import { acquire, release } from './gh-utils' -import { - githubHostExecOptions, - type GitHubApiRepository, - type GitHubRepoExecOptions -} from './github-api-repository' +import { githubHostExecOptions, type GitHubRepoExecOptions } from './github-api-repository' const POLL_INTERVAL_MS = 1_000 const MAX_POLLS = 180 @@ -72,7 +68,7 @@ async function runStackMergeCommand( } export async function mergeGitHubPRStack(args: { - repository: GitHubApiRepository + repository: GitHubOwnerRepo prNumber: number method: GitHubPRMergeMethod mergeAction: GitHubPRStackMergeAction diff --git a/src/main/github/github-pr-stack.ts b/src/main/github/github-pr-stack.ts index 3dc65f6175a..e9ac674749d 100644 --- a/src/main/github/github-pr-stack.ts +++ b/src/main/github/github-pr-stack.ts @@ -1,5 +1,6 @@ import type { CheckStatus, + GitHubOwnerRepo, GitHubPRStack, GitHubPRStackEntry, PRMergeableState, @@ -8,11 +9,7 @@ import type { } from '../../shared/github/pull-request-types' import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' import { ghExecFileAsync } from '../git/runner' -import { - githubHostExecOptions, - type GitHubApiRepository, - type GitHubRepoExecOptions -} from './github-api-repository' +import { githubHostExecOptions, type GitHubRepoExecOptions } from './github-api-repository' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' const STACK_CACHE_TTL_MS = 30_000 @@ -65,7 +62,7 @@ export function _resetGitHubPRStackCacheForTests(): void { } function stackCacheKey( - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, stackNumber: number, executionScope: string ): string { @@ -206,7 +203,7 @@ query($owner: String!, $repo: String!, $pr: Int!) { }` async function fetchStackDetails( - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, prNumber: number, summary: GitHubPRStack, ghOptions: GitHubRepoExecOptions @@ -239,7 +236,7 @@ async function fetchStackDetails( } export async function hydrateGitHubPRStack( - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, prNumber: number, summary: GitHubPRStack, ghOptions: GitHubRepoExecOptions, diff --git a/src/main/github/github-repository-identity.ts b/src/main/github/github-repository-identity.ts index c601252deda..947641f72c4 100644 --- a/src/main/github/github-repository-identity.ts +++ b/src/main/github/github-repository-identity.ts @@ -16,8 +16,6 @@ import { classifyGitHubOwnerRepoFromRemoteUrl } from './github-ssh-host-alias-re import { isStableMissingGitRemoteError } from '../git/stable-missing-git-remote-error' import type { GitAdmissionTier } from '../git/command-runner/git-exec-options' -export type OwnerRepo = GitHubOwnerRepo - export type { GitHubRemoteIdentity } export { parseGitHubOwnerRepo, parseGitHubRemoteIdentity } @@ -72,13 +70,13 @@ const OWNER_REPO_SIGNED_CACHE_TTL_MS = 5 * 60_000 const OWNER_REPO_CACHE_MAX_ENTRIES = 512 type OwnerRepoCacheEntry = { - value: OwnerRepo | null + value: GitHubOwnerRepo | null expiresAt: number configSignature?: string } const ownerRepoCache = new Map() -const ownerRepoInFlight: CoalescedProbes = new Map() +const ownerRepoInFlight: CoalescedProbes = new Map() /** @internal - exposed for tests only */ export function _resetOwnerRepoCache(): void { @@ -113,7 +111,7 @@ export async function getRemoteUrlForRepo( return readRemoteUrl(context, remoteName) } -function getOwnerRepoCacheTtl(value: OwnerRepo | null, configSignature?: string): number { +function getOwnerRepoCacheTtl(value: GitHubOwnerRepo | null, configSignature?: string): number { if (configSignature) { return value ? OWNER_REPO_SIGNED_CACHE_TTL_MS : OWNER_REPO_NEGATIVE_CACHE_TTL_MS } @@ -126,7 +124,7 @@ export async function getOwnerRepoForRemote( connectionId?: string | null, localGitOptions: LocalGitExecOptions = {}, probeOptions: GitHubRemoteIdentityProbeOptions = {} -): Promise { +): Promise { const context = githubRepoContext(repoPath, connectionId, localGitOptions) if ( probeOptions.requireVerifiedSshProbe && @@ -188,7 +186,7 @@ async function resolveOwnerRepoForRemote( cacheKey: string, configSignature: string | undefined, requireVerifiedSshProbe: boolean -): Promise { +): Promise { const now = Date.now() try { const remoteUrl = await getRemoteUrlForRepo(context, remoteName) diff --git a/src/main/github/issue-comment.ts b/src/main/github/issue-comment.ts index ac3f6a205de..c6ffda2a1bd 100644 --- a/src/main/github/issue-comment.ts +++ b/src/main/github/issue-comment.ts @@ -1,5 +1,5 @@ import type { GitHubCommentResult, PRComment } from '../../shared/github/comment-types' -import type { LocalGitExecOptions, OwnerRepo } from './gh-utils' +import type { LocalGitExecOptions, GitHubOwnerRepo } from './gh-utils' import { getIssueGitHubApiRepository, resolveGitHubRepoExecution } from './github-api-repository' import { acquire, classifyGhError, ghExecFileAsync, release } from './gh-utils' @@ -20,7 +20,7 @@ export async function addIssueComment( issueNumber: number, body: string, connectionId?: string | null, - ownerRepoOverride?: OwnerRepo | null, + ownerRepoOverride?: GitHubOwnerRepo | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const { ownerRepo, ghOptions } = await resolveGitHubRepoExecution( diff --git a/src/main/github/issue-timeline.ts b/src/main/github/issue-timeline.ts index 3cc1f36eaac..071b446bcf3 100644 --- a/src/main/github/issue-timeline.ts +++ b/src/main/github/issue-timeline.ts @@ -3,9 +3,10 @@ import type { GitHubIssueTimelineTarget } from '../../shared/github/comment-types' import { ghExecFileAsync } from './gh-utils' -import type { GitHubApiRepository, GitHubRepoExecOptions } from './github-api-repository' +import type { GitHubRepoExecOptions } from './github-api-repository' import { githubHostExecOptions } from './github-api-repository' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' +import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' const MAX_ISSUE_TIMELINE_ITEMS = 300 const GITHUB_REST_PAGE_SIZE = 100 @@ -145,7 +146,7 @@ function parseRestTimelineEventLines(stdout: string): RestTimelineEvent[] { } export async function getIssueTimelineItems( - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, issueNumber: number, ghOptions: GitHubRepoExecOptions ): Promise { diff --git a/src/main/github/issue-work-item-details.ts b/src/main/github/issue-work-item-details.ts index 5cb95f50d48..bddca22a4be 100644 --- a/src/main/github/issue-work-item-details.ts +++ b/src/main/github/issue-work-item-details.ts @@ -1,8 +1,8 @@ import type { GitHubIssueTimelineItem, PRComment } from '../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../shared/github/pull-request-types' +import type { GitHubAssignableUser, GitHubOwnerRepo } from '../../shared/github/pull-request-types' import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from './gh-utils' import type { LocalGitExecOptions } from './gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' +import { githubHostExecOptions } from './github-api-repository' import { getIssueTimelineItems } from './issue-timeline' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' @@ -69,7 +69,7 @@ export type CollapsedIssueDetails = { export async function getIssueDetailsViaGraphQL( repoPath: string, issueNumber: number, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { @@ -151,7 +151,7 @@ export async function getIssueDetailsViaGraphQL( export async function getIssueBodyAndComments( repoPath: string, issueNumber: number, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ diff --git a/src/main/github/merged-pr-commit-membership.ts b/src/main/github/merged-pr-commit-membership.ts index dd595da69f4..67eba8ff8cc 100644 --- a/src/main/github/merged-pr-commit-membership.ts +++ b/src/main/github/merged-pr-commit-membership.ts @@ -2,7 +2,7 @@ import { ghExecFileAsync } from './gh-utils' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' import { githubHostExecOptions } from './github-api-repository' import { githubRepoIdentityKey } from '../../shared/github/repository-identity-key' -import type { OwnerRepo } from './github-repository-identity' +import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' type GhExecOptions = Parameters[1] @@ -50,7 +50,7 @@ export function resetMergedPRCommitMembershipCacheForTest(): void { * reused branch name. Conservative on any failure: returns unknown. */ export async function isCommitPartOfMergedPR(args: { - ownerRepo: OwnerRepo + ownerRepo: GitHubOwnerRepo prNumber: number commitOid: string ghOptions: GhExecOptions diff --git a/src/main/github/pull-request-file-contents.ts b/src/main/github/pull-request-file-contents.ts index fa367e1c68b..740b5a44c5d 100644 --- a/src/main/github/pull-request-file-contents.ts +++ b/src/main/github/pull-request-file-contents.ts @@ -1,11 +1,11 @@ -import type { GitHubPRFile, GitHubPRFileContents } from '../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + GitHubPRFile, + GitHubPRFileContents +} from '../../shared/github/pull-request-types' import { acquire, ghExecFileAsync, ghRepoExecOptions, githubRepoContext, release } from './gh-utils' import type { LocalGitExecOptions } from './gh-utils' -import { - githubHostExecOptions, - resolveGitHubRepoExecution, - type GitHubApiRepository -} from './github-api-repository' +import { githubHostExecOptions, resolveGitHubRepoExecution } from './github-api-repository' import { isMaxBufferOverflowError } from '../git/max-buffer-overflow' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' @@ -19,7 +19,7 @@ async function fetchContentAtRef(args: { repoPath: string connectionId?: string | null localGitOptions?: LocalGitExecOptions - ownerRepo: GitHubApiRepository + ownerRepo: GitHubOwnerRepo path: string ref: string }): Promise<{ content: string; isBinary: boolean; tooLarge?: boolean }> { @@ -61,7 +61,7 @@ export async function getPRFileContents(args: { repoPath: string connectionId?: string | null localGitOptions?: LocalGitExecOptions - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null prNumber: number path: string oldPath?: string diff --git a/src/main/github/pull-request-file-data.ts b/src/main/github/pull-request-file-data.ts index 62622e4583f..d97f78c260b 100644 --- a/src/main/github/pull-request-file-data.ts +++ b/src/main/github/pull-request-file-data.ts @@ -1,7 +1,11 @@ -import type { GitHubPRFile, GitHubPRFileViewedState } from '../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + GitHubPRFile, + GitHubPRFileViewedState +} from '../../shared/github/pull-request-types' import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from './gh-utils' import type { LocalGitExecOptions } from './gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' +import { githubHostExecOptions } from './github-api-repository' import { getPRReviewCommentLineNumbersFromPatch } from './pr-review-comment-lines' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' @@ -65,7 +69,7 @@ function isBinaryHint(file: RESTPRFile): boolean { export async function getPRMetadata( repoPath: string, prNumber: number, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ body: string; headSha?: string; baseSha?: string }> { @@ -104,7 +108,7 @@ export async function getPRMetadata( export async function getPRFiles( repoPath: string, prNumber: number, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { @@ -155,7 +159,7 @@ export async function getPRFiles( export async function getPRFileViewedStates( repoPath: string, prNumber: number, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { diff --git a/src/main/github/stacked-pr-creation.ts b/src/main/github/stacked-pr-creation.ts index 588348913a1..5c1b1b8edb5 100644 --- a/src/main/github/stacked-pr-creation.ts +++ b/src/main/github/stacked-pr-creation.ts @@ -1,7 +1,7 @@ import type { ExecutionHostId } from '../../shared/execution-host' import { hostedReviewSshConnectionId } from '../source-control/hosted-review-execution-host' import type { - CreateStackedHostedReviewInput, + CreateHostedReviewInput, CreateStackedHostedReviewResult } from '../../shared/hosted-review' import { isDefaultGitHubHost } from '../../shared/github/repository-identity-key' @@ -10,11 +10,7 @@ import { normalizeHostedReviewHeadRef } from '../../shared/hosted-review-refs' import { acquire, ghExecFileAsync, ghRepoExecOptions, githubRepoContext, release } from './gh-utils' -import { - getOriginGitHubApiRepository, - githubHostExecOptions, - type GitHubApiRepository -} from './github-api-repository' +import { getOriginGitHubApiRepository, githubHostExecOptions } from './github-api-repository' import { getHostedReviewLocalGitOptions, type HostedReviewExecutionOptions @@ -26,11 +22,12 @@ import { type GitHubStackPullRequest, type NumberedHostedReviewSummary } from './github-stack-api-responses' +import type { GitHubOwnerRepo } from '../../shared/github/pull-request-types' type StackedPullRequestPlan = | { ok: true - repository: GitHubApiRepository + repository: GitHubOwnerRepo parentReview: GitHubStackPullRequest currentReview: GitHubStackPullRequest | null } @@ -47,7 +44,7 @@ function isStacksUnavailableError(error: unknown): boolean { function ghOptions( repoPath: string, - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, connectionId?: string | null, options: HostedReviewExecutionOptions = {} ) { @@ -62,7 +59,7 @@ function ghOptions( async function findOpenPullRequestsForBranch( repoPath: string, - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, branch: string, connectionId?: string | null, options: HostedReviewExecutionOptions = {}, @@ -80,7 +77,7 @@ async function findOpenPullRequestsForBranch( async function getStacksForPullRequest( repoPath: string, - repository: GitHubApiRepository, + repository: GitHubOwnerRepo, pullRequestNumber: number, connectionId?: string | null, options: HostedReviewExecutionOptions = {} @@ -117,7 +114,7 @@ function validateParentStack( export async function prepareGitHubStackedPullRequest( repoPath: string, - input: CreateStackedHostedReviewInput, + input: CreateHostedReviewInput, executionHostId: ExecutionHostId, options: HostedReviewExecutionOptions = {} ): Promise { @@ -223,7 +220,7 @@ function registeredStackNumber( export async function registerGitHubStackedPullRequest(args: { repoPath: string - repository: GitHubApiRepository + repository: GitHubOwnerRepo parentReview: NumberedHostedReviewSummary currentReview: NumberedHostedReviewSummary executionHostId: ExecutionHostId diff --git a/src/main/github/work-item-details-api-parity.test.ts b/src/main/github/work-item-details-api-parity.test.ts index 187680f64cd..02eb25bf996 100644 --- a/src/main/github/work-item-details-api-parity.test.ts +++ b/src/main/github/work-item-details-api-parity.test.ts @@ -1,9 +1,12 @@ import { describe, expect, expectTypeOf, it } from 'vitest' -import type { GitHubPRFile, GitHubPRFileContents } from '../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + GitHubPRFile, + GitHubPRFileContents +} from '../../shared/github/pull-request-types' import type { GitHubWorkItemDetails } from '../../shared/github/work-item-types' import type { IssueSourcePreference } from '../../shared/repo-types' import type { LocalGitExecOptions } from './gh-utils' -import type { GitHubApiRepository } from './github-api-repository' import * as workItemDetails from './work-item-details' type GetWorkItemDetails = ( @@ -19,7 +22,7 @@ type GetPRFileContents = (args: { repoPath: string connectionId?: string | null localGitOptions?: LocalGitExecOptions - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null prNumber: number path: string oldPath?: string diff --git a/src/main/github/work-item-details.ts b/src/main/github/work-item-details.ts index dafc4f514d6..df0a9d25956 100644 --- a/src/main/github/work-item-details.ts +++ b/src/main/github/work-item-details.ts @@ -1,14 +1,14 @@ import type { PRCheckDetail } from '../../shared/github/check-types' -import type { GitHubPRFile, GitHubPRFileContents } from '../../shared/github/pull-request-types' +import type { + GitHubOwnerRepo, + GitHubPRFile, + GitHubPRFileContents +} from '../../shared/github/pull-request-types' import type { GitHubWorkItem, GitHubWorkItemDetails } from '../../shared/github/work-item-types' import type { IssueSourcePreference } from '../../shared/repo-types' import { getPRChecks, getPRComments, getWorkItem } from './client' import { acquire, release, type LocalGitExecOptions } from './gh-utils' -import { - getIssueGitHubApiRepository, - resolveGitHubRepoExecution, - type GitHubApiRepository -} from './github-api-repository' +import { getIssueGitHubApiRepository, resolveGitHubRepoExecution } from './github-api-repository' import { getIssueBodyAndComments, getIssueDetailsViaGraphQL } from './issue-work-item-details' import { getPRFiles, @@ -40,7 +40,7 @@ async function getPRChecksForDetails( repoPath: string, prNumber: number, headSha: string | undefined, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { @@ -217,7 +217,7 @@ export async function getPRFileContents(args: { repoPath: string connectionId?: string | null localGitOptions?: LocalGitExecOptions - prRepo?: GitHubApiRepository | null + prRepo?: GitHubOwnerRepo | null prNumber: number path: string oldPath?: string diff --git a/src/main/github/work-item-participants.ts b/src/main/github/work-item-participants.ts index 0ac793b4812..e22167db74b 100644 --- a/src/main/github/work-item-participants.ts +++ b/src/main/github/work-item-participants.ts @@ -1,9 +1,9 @@ import type { PRComment } from '../../shared/github/comment-types' -import type { GitHubAssignableUser } from '../../shared/github/pull-request-types' +import type { GitHubAssignableUser, GitHubOwnerRepo } from '../../shared/github/pull-request-types' import type { GitHubWorkItem } from '../../shared/github/work-item-types' import { ghExecFileAsync, ghRepoExecOptions, githubRepoContext } from './gh-utils' import type { LocalGitExecOptions } from './gh-utils' -import { githubHostExecOptions, type GitHubApiRepository } from './github-api-repository' +import { githubHostExecOptions } from './github-api-repository' import { noteRepositoryRateLimitSpend, repositoryRateLimitGuard } from './rate-limit' const WORK_ITEM_PARTICIPANTS_QUERY = `query($owner: String!, $repo: String!, $number: Int!, $isPr: Boolean!) { @@ -49,7 +49,7 @@ function mergeGitHubUsers(users: GitHubAssignableUser[]): GitHubAssignableUser[] export async function getWorkItemParticipants( repoPath: string, item: Pick, - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { @@ -109,7 +109,7 @@ export async function getWorkItemParticipants( async function getGitHubUsersByLogin( repoPath: string, logins: string[], - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { @@ -172,7 +172,7 @@ export async function getMentionParticipants( >, comments: PRComment[], participants: GitHubAssignableUser[], - repository: GitHubApiRepository | null, + repository: GitHubOwnerRepo | null, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { diff --git a/src/main/gitlab/gitlab-project-ref-resolution.ts b/src/main/gitlab/gitlab-project-ref-resolution.ts index c337c01ae89..3aa3b6702f3 100644 --- a/src/main/gitlab/gitlab-project-ref-resolution.ts +++ b/src/main/gitlab/gitlab-project-ref-resolution.ts @@ -14,16 +14,16 @@ import { rememberGlabKnownHost, type LocalGitExecOptions } from './gitlab-known-host-probe' +import type { GitLabProjectRef } from '../../shared/gitlab-types' import { DEFAULT_GITLAB_HOSTS, normalizeGitLabHost, parseGitLabProjectRef, - parseRemoteProjectRefCandidate, - type ProjectRef + parseRemoteProjectRefCandidate } from './project-ref-parser' export { DEFAULT_GITLAB_HOSTS, parseGitLabProjectRef } -export type { ProjectRef } +export type { GitLabProjectRef } export { _resetKnownHostsCache, getGlabKnownHosts, @@ -33,7 +33,7 @@ export type { LocalGitExecOptions } from './gitlab-known-host-probe' const PROJECT_REF_CACHE_MAX_ENTRIES = 512 -type CachedProjectRef = { value: ProjectRef | null; expiresAt: number } +type CachedProjectRef = { value: GitLabProjectRef | null; expiresAt: number } const projectRefCache = new Map() @@ -49,7 +49,7 @@ export function _getProjectRefCacheSize(): number { return projectRefCache.size } -function rememberProjectRefCacheEntry(cacheKey: string, value: ProjectRef | null): void { +function rememberProjectRefCacheEntry(cacheKey: string, value: GitLabProjectRef | null): void { // Why: "not GitLab" only holds until someone configures `origin` or logs into // `glab` — a repo probed before either kept hosted-review detection stale for // the life of the process. Negatives expire the way every other forge's do; @@ -73,7 +73,7 @@ export async function getProjectRefForRemote( knownHosts: readonly string[] = DEFAULT_GITLAB_HOSTS, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { // Why: a reconnect replaces the host an answer came from under the same id, so // the generation is part of the signature; `knownHosts` carries the glab auth // state, so logging into a self-hosted instance re-asks rather than reusing a @@ -111,11 +111,11 @@ async function resolveProjectRefForRemote( cacheKey: string, ownsKey: () => boolean, localGitOptions: LocalGitExecOptions -): Promise { +): Promise { // Why: a probe abandoned as stale still runs, and the repo state it read is // older than whatever its successor already published. It may answer its own // callers; it may not overwrite the cache. - const publish = (value: ProjectRef | null): void => { + const publish = (value: GitLabProjectRef | null): void => { if (ownsKey()) { rememberProjectRefCacheEntry(cacheKey, value) } @@ -171,7 +171,7 @@ export async function getProjectRef( knownHosts?: readonly string[], connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { return getProjectRefForRemote(repoPath, 'origin', knownHosts, connectionId, localGitOptions) } @@ -180,7 +180,7 @@ export async function getIssueProjectRef( knownHosts?: readonly string[], connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} -): Promise { +): Promise { const originPromise = getProjectRefForRemote( repoPath, 'origin', @@ -204,7 +204,7 @@ export async function getIssueProjectRef( } export type ResolvedIssueSource = { - source: ProjectRef | null + source: GitLabProjectRef | null /** True when explicit upstream is gone and resolver fell back to origin. */ fellBack: boolean } @@ -269,7 +269,7 @@ export function glabRepoExecOptions( } export function glabHostnameArgs( - projectRef: Pick | null | undefined, + projectRef: Pick | null | undefined, connectionId?: string | null ): string[] { return connectionId && projectRef?.host ? ['--hostname', projectRef.host] : [] @@ -277,7 +277,7 @@ export function glabHostnameArgs( async function isGlabConfiguredForRemoteHost( repoPath: string, - projectRef: Pick, + projectRef: Pick, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} ): Promise { diff --git a/src/main/gitlab/gl-utils.ts b/src/main/gitlab/gl-utils.ts index f31c47105de..20dc417eddf 100644 --- a/src/main/gitlab/gl-utils.ts +++ b/src/main/gitlab/gl-utils.ts @@ -26,7 +26,7 @@ export { } from './gitlab-project-ref-resolution' export type { LocalGitExecOptions, - ProjectRef, + GitLabProjectRef, ResolvedIssueSource } from './gitlab-project-ref-resolution' export { diff --git a/src/main/gitlab/issue-update.ts b/src/main/gitlab/issue-update.ts index 369e889ff09..ccd5527f0be 100644 --- a/src/main/gitlab/issue-update.ts +++ b/src/main/gitlab/issue-update.ts @@ -10,7 +10,7 @@ import { release, resolveIssueSource, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' @@ -27,7 +27,7 @@ export async function updateIssue( updates: GitLabIssueUpdate, preference?: IssueSourcePreference, connectionId?: string | null, - projectRefOverride?: ProjectRef | null, + projectRefOverride?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { const projectRef = diff --git a/src/main/gitlab/issues.ts b/src/main/gitlab/issues.ts index 41d9228bcae..2e0aec8c24e 100644 --- a/src/main/gitlab/issues.ts +++ b/src/main/gitlab/issues.ts @@ -3,7 +3,7 @@ import type { GitLabCommentResult, GitLabIssueInfo, MRComment } from '../../shar import type { IssueSourcePreference } from '../../shared/repo-types' import { mapGitLabIssueInfo } from './mappers' // prettier-ignore -import { glabApiWithHeaders, glabExecFileAsync, acquire, release, getIssueProjectRef, resolveIssueSource, classifyGlabError, classifyListFetchError, getGlabKnownHosts, glabRepoExecOptions, glabHostnameArgs, parseGlabJsonList, parseGlabPaginationHeader, type LocalGitExecOptions, type ProjectRef } from './gl-utils' +import { glabApiWithHeaders, glabExecFileAsync, acquire, release, getIssueProjectRef, resolveIssueSource, classifyGlabError, classifyListFetchError, getGlabKnownHosts, glabRepoExecOptions, glabHostnameArgs, parseGlabJsonList, parseGlabPaginationHeader, type LocalGitExecOptions, type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' // Why: parallel to GitHub's IssueListResult — distinguishes a successful- @@ -218,7 +218,7 @@ export async function addIssueComment( body: string, preference?: IssueSourcePreference, connectionId?: string | null, - projectRefOverride?: ProjectRef | null, + projectRefOverride?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { const projectRef = diff --git a/src/main/gitlab/merge-request-creation-lookup.ts b/src/main/gitlab/merge-request-creation-lookup.ts index e5c8303ce1a..e099d150e00 100644 --- a/src/main/gitlab/merge-request-creation-lookup.ts +++ b/src/main/gitlab/merge-request-creation-lookup.ts @@ -6,7 +6,7 @@ import { glabExecFileAsync, glabHostnameArgs, glabRepoExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' export function parseMergeRequestPayload(stdout: string): { number: number; url: string } | null { @@ -46,7 +46,7 @@ export function parseMergeRequestPayload(stdout: string): { number: number; url: export async function findOpenMRByHeadBase(args: { repoPath: string - projectRef: ProjectRef + projectRef: GitLabProjectRef head: string base: string connectionId?: string | null diff --git a/src/main/gitlab/merge-request-lookup.ts b/src/main/gitlab/merge-request-lookup.ts index 96b6f7bad28..425db7a016b 100644 --- a/src/main/gitlab/merge-request-lookup.ts +++ b/src/main/gitlab/merge-request-lookup.ts @@ -8,7 +8,7 @@ import { glabRepoExecOptions, glabExecFileAsync, release, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' import { @@ -30,7 +30,7 @@ export async function getProjectSlug( repoPath: string, connectionId?: string | null, options: HostedReviewExecutionOptions = {} -): Promise { +): Promise { const localGitArgs = hostedReviewLocalGitOptionArgs(options) const knownHosts = await getGlabKnownHosts(connectionId, localGitArgs[0]) return getProjectRef(repoPath, knownHosts, connectionId, ...localGitArgs) diff --git a/src/main/gitlab/merge-request-project-resolution.ts b/src/main/gitlab/merge-request-project-resolution.ts index 4a28a3644ed..526f65010bb 100644 --- a/src/main/gitlab/merge-request-project-resolution.ts +++ b/src/main/gitlab/merge-request-project-resolution.ts @@ -3,15 +3,15 @@ import { getGlabKnownHosts, resolveIssueSource, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' export async function withProjectRef( repoPath: string, preference: IssueSourcePreference | undefined, connectionId: string | null | undefined, - explicitProjectRef: ProjectRef | null | undefined, - fn: (projectRef: ProjectRef, repoFlag: string) => Promise, + explicitProjectRef: GitLabProjectRef | null | undefined, + fn: (projectRef: GitLabProjectRef, repoFlag: string) => Promise, fallback: T, localGitOptions: LocalGitExecOptions = {} ): Promise { diff --git a/src/main/gitlab/merge-request-review-mutations.ts b/src/main/gitlab/merge-request-review-mutations.ts index 0b27e0026c4..d231c9d7619 100644 --- a/src/main/gitlab/merge-request-review-mutations.ts +++ b/src/main/gitlab/merge-request-review-mutations.ts @@ -14,7 +14,7 @@ import { glabExecFileAsync, release, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' import { withProjectRef } from './merge-request-project-resolution' @@ -25,7 +25,7 @@ export async function addMRComment( body: string, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true; comment: MRComment } | { ok: false; error: string }> { return withProjectRef<{ ok: true; comment: MRComment } | { ok: false; error: string }>( @@ -83,7 +83,7 @@ export async function addMRInlineComment( input: GitLabMRInlineCommentInput, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true; comment: MRComment } | { ok: false; error: string }> { return withProjectRef<{ ok: true; comment: MRComment } | { ok: false; error: string }>( @@ -171,7 +171,7 @@ export async function resolveMRDiscussion( resolved: boolean, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { return withProjectRef( @@ -237,7 +237,7 @@ export async function updateMRReviewers( reviewerIds: number[], preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { return withProjectRef( diff --git a/src/main/gitlab/merge-request-state-mutations.ts b/src/main/gitlab/merge-request-state-mutations.ts index fb5d33bb670..3dd9b10967e 100644 --- a/src/main/gitlab/merge-request-state-mutations.ts +++ b/src/main/gitlab/merge-request-state-mutations.ts @@ -6,7 +6,7 @@ import { glabExecFileAsync, release, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { withProjectRef } from './merge-request-project-resolution' @@ -15,7 +15,7 @@ export async function closeMR( iid: number, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { return withProjectRef<{ ok: true } | { ok: false; error: string }>( @@ -59,7 +59,7 @@ export async function reopenMR( iid: number, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { return withProjectRef<{ ok: true } | { ok: false; error: string }>( @@ -103,7 +103,7 @@ export async function mergeMR( method: 'merge' | 'squash' | 'rebase' = 'merge', preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { return withProjectRef<{ ok: true } | { ok: false; error: string }>( diff --git a/src/main/gitlab/merge-request-update.ts b/src/main/gitlab/merge-request-update.ts index 5fe1584ce2d..41661abdc19 100644 --- a/src/main/gitlab/merge-request-update.ts +++ b/src/main/gitlab/merge-request-update.ts @@ -8,7 +8,7 @@ import { glabExecFileAsync, release, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' import { stripGitLabDraftTitlePrefix } from './merge-request-draft-title' @@ -20,7 +20,7 @@ export async function updateMR( updates: GitLabMRUpdate, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise<{ ok: true } | { ok: false; error: string }> { return withProjectRef<{ ok: true } | { ok: false; error: string }>( diff --git a/src/main/gitlab/mr-discussion-notes.ts b/src/main/gitlab/mr-discussion-notes.ts index fa3aac2dc6a..46cb67f7ebb 100644 --- a/src/main/gitlab/mr-discussion-notes.ts +++ b/src/main/gitlab/mr-discussion-notes.ts @@ -5,7 +5,7 @@ import { glabRepoExecOptions, glabExecFileAsync, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' // ── Discussion → MRComment flattening ────────────────────────────── @@ -62,7 +62,7 @@ export function flattenDiscussions(discussions: GitLabRawDiscussion[]): MRCommen export async function fetchDiscussions( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, type: 'issue' | 'mr', iid: number, connectionId?: string | null, diff --git a/src/main/gitlab/mr-file-diffs.ts b/src/main/gitlab/mr-file-diffs.ts index 8f6d10ce410..6ffa622aefe 100644 --- a/src/main/gitlab/mr-file-diffs.ts +++ b/src/main/gitlab/mr-file-diffs.ts @@ -5,7 +5,7 @@ import { glabRepoExecOptions, glabExecFileAsync, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' /** @@ -78,7 +78,7 @@ function mapMRFile(raw: { export async function fetchMRFiles( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/gitlab/mr-reviewers-and-approvals.ts b/src/main/gitlab/mr-reviewers-and-approvals.ts index cdbe9870e37..7165c096d86 100644 --- a/src/main/gitlab/mr-reviewers-and-approvals.ts +++ b/src/main/gitlab/mr-reviewers-and-approvals.ts @@ -6,12 +6,12 @@ import { glabRepoExecOptions, glabExecFileAsync, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' export async function fetchMRReviewers( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} @@ -32,7 +32,7 @@ export async function fetchMRReviewers( export async function fetchMRApprovalState( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/gitlab/pipeline-job-graph.ts b/src/main/gitlab/pipeline-job-graph.ts index 46de918f49a..489b7a6da24 100644 --- a/src/main/gitlab/pipeline-job-graph.ts +++ b/src/main/gitlab/pipeline-job-graph.ts @@ -5,7 +5,7 @@ import { glabRepoExecOptions, glabExecFileAsync, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' // ── Pipeline jobs ────────────────────────────────────────────────── @@ -77,7 +77,7 @@ function mapBridgeAsJob(raw: GitLabRawBridge, pipelineId: number): GitLabPipelin async function fetchPipelineJobPage( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, pipelineId: number, connectionId: string | null | undefined, localGitOptions: LocalGitExecOptions @@ -99,7 +99,7 @@ async function fetchPipelineJobPage( async function fetchPipelineBridges( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, pipelineId: number, connectionId: string | null | undefined, localGitOptions: LocalGitExecOptions @@ -118,8 +118,8 @@ async function fetchPipelineBridges( function childPipelineTarget( bridge: GitLabRawBridge, - parentProjectRef: ProjectRef -): { projectRef: ProjectRef; pipelineId: number } | null { + parentProjectRef: GitLabProjectRef +): { projectRef: GitLabProjectRef; pipelineId: number } | null { const childId = bridge.downstream_pipeline?.id if (typeof childId !== 'number') { return null @@ -171,7 +171,7 @@ async function mapWithConcurrencyLimit( export async function fetchPipelineJobs( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, pipelineId: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} @@ -184,7 +184,7 @@ export async function fetchPipelineJobs( ]) const bridgeRows = bridges.map((bridge) => mapBridgeAsJob(bridge, pipelineId)) - const childTargets: { projectRef: ProjectRef; pipelineId: number }[] = [] + const childTargets: { projectRef: GitLabProjectRef; pipelineId: number }[] = [] const seenChildIds = new Set() for (const bridge of bridges) { const target = childPipelineTarget(bridge, projectRef) diff --git a/src/main/gitlab/pipeline-job-mutations.ts b/src/main/gitlab/pipeline-job-mutations.ts index eb59e80d45d..79508c7df1d 100644 --- a/src/main/gitlab/pipeline-job-mutations.ts +++ b/src/main/gitlab/pipeline-job-mutations.ts @@ -14,7 +14,7 @@ import { isMissingJobLogError, release, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' import { withProjectRef } from './merge-request-project-resolution' @@ -50,7 +50,7 @@ export async function getJobTrace( jobId: number, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { return withProjectRef( @@ -95,7 +95,7 @@ export async function retryJob( jobId: number, preference?: IssueSourcePreference, connectionId?: string | null, - projectRef?: ProjectRef | null, + projectRef?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { return withProjectRef( diff --git a/src/main/gitlab/project-ref-inflight.ts b/src/main/gitlab/project-ref-inflight.ts index cea4173c48f..04af244d20d 100644 --- a/src/main/gitlab/project-ref-inflight.ts +++ b/src/main/gitlab/project-ref-inflight.ts @@ -1,7 +1,7 @@ import { runCoalescedProbe, type CoalescedProbes } from '../git/coalesced-probe' -import type { ProjectRef } from './gl-utils' +import type { GitLabProjectRef } from './gl-utils' -const projectRefInFlight: CoalescedProbes = new Map() +const projectRefInFlight: CoalescedProbes = new Map() export function clearProjectRefInFlight(): void { projectRefInFlight.clear() @@ -9,8 +9,8 @@ export function clearProjectRefInFlight(): void { export async function runProjectRefProbeOnce( cacheKey: string, - createProbe: (ownsKey: () => boolean) => Promise -): Promise { + createProbe: (ownsKey: () => boolean) => Promise +): Promise { // Why: joining only a probe that is still young keeps a wedged host's dead // promise from pinning every later retry for the process lifetime (P1-D). return runCoalescedProbe(projectRefInFlight, cacheKey, createProbe) diff --git a/src/main/gitlab/project-ref-parser.ts b/src/main/gitlab/project-ref-parser.ts index 575e6093179..3bb9bd41fb2 100644 --- a/src/main/gitlab/project-ref-parser.ts +++ b/src/main/gitlab/project-ref-parser.ts @@ -1,7 +1,5 @@ import type { GitLabProjectRef } from '../../shared/gitlab-types' -export type ProjectRef = GitLabProjectRef - /** * Hosts always treated as GitLab. Self-hosted instances are added at * runtime via `getGlabKnownHosts()`, which inspects `glab auth status`. @@ -39,7 +37,7 @@ function hostIdentityFromUrl(url: URL): string { return url.hostname } -function makeProjectRefForTrustedHost(host: string, path: string): ProjectRef | null { +function makeProjectRefForTrustedHost(host: string, path: string): GitLabProjectRef | null { const normalizedHost = normalizeGitLabHost(host) const normalizedPath = stripGitSuffix(path.replace(/^\/+/, '')).trim() // Reject paths without at least one group segment — `gitlab.com:foo` @@ -75,7 +73,7 @@ function makeProjectRef( host: string, path: string, knownHosts: readonly string[] -): ProjectRef | null { +): GitLabProjectRef | null { const normalizedHost = normalizeGitLabHost(host) const normalizedKnownHosts = knownHosts.map(normalizeGitLabHost) if (!normalizedKnownHosts.some((knownHost) => knownHostMatches(normalizedHost, knownHost))) { @@ -84,7 +82,7 @@ function makeProjectRef( return makeProjectRefForTrustedHost(normalizedHost, path) } -export function parseRemoteProjectRefCandidate(remoteUrl: string): ProjectRef | null { +export function parseRemoteProjectRefCandidate(remoteUrl: string): GitLabProjectRef | null { const trimmed = remoteUrl.trim() if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) { const scpLike = trimmed.match(/^(?:[^@/:]+@)?([^:\s/]+):([^\s]+?)(?:\.git)?$/) @@ -107,7 +105,7 @@ export function parseRemoteProjectRefCandidate(remoteUrl: string): ProjectRef | export function parseGitLabProjectRef( remoteUrl: string, knownHosts: readonly string[] = DEFAULT_GITLAB_HOSTS -): ProjectRef | null { +): GitLabProjectRef | null { const trimmed = remoteUrl.trim() if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(trimmed)) { const scpLike = trimmed.match(/^(?:[^@/:]+@)?([^:\s/]+):([^\s]+?)(?:\.git)?$/) diff --git a/src/main/gitlab/work-item-details.ts b/src/main/gitlab/work-item-details.ts index 637baa0c78b..284e90ebce7 100644 --- a/src/main/gitlab/work-item-details.ts +++ b/src/main/gitlab/work-item-details.ts @@ -24,7 +24,7 @@ import { release, resolveIssueSource, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' // ── Top-level aggregator ─────────────────────────────────────────── @@ -56,7 +56,7 @@ export async function getWorkItemDetails( type: 'issue' | 'mr', preference?: IssueSourcePreference, connectionId?: string | null, - projectRefOverride?: ProjectRef | null, + projectRefOverride?: GitLabProjectRef | null, localGitOptions: LocalGitExecOptions = {} ): Promise { // Why: detail fetches must use the same project source as the list row @@ -91,7 +91,7 @@ export async function getWorkItemDetails( async function fetchIssueDetails( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} @@ -130,7 +130,7 @@ async function fetchIssueDetails( async function fetchMRDetails( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, connectionId?: string | null, localGitOptions: LocalGitExecOptions = {} diff --git a/src/main/gitlab/work-item-queries.ts b/src/main/gitlab/work-item-queries.ts index 4e6e3c3fc53..a6918467c32 100644 --- a/src/main/gitlab/work-item-queries.ts +++ b/src/main/gitlab/work-item-queries.ts @@ -19,7 +19,7 @@ import { release, resolveIssueSource, type LocalGitExecOptions, - type ProjectRef + type GitLabProjectRef } from './gl-utils' import { encodedProject } from './project-path-encoding' import type { IssueListState } from './issues' @@ -27,7 +27,7 @@ import { listMergeRequests } from './merge-request-list' export async function getWorkItemByProjectRef( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, iid: number, type: 'issue' | 'mr', connectionId?: string | null, @@ -150,7 +150,7 @@ export async function listWorkItems( export async function fetchIssuesAsWorkItems( repoPath: string, - projectRef: ProjectRef, + projectRef: GitLabProjectRef, state: IssueListState, page: number, perPage: number, diff --git a/src/main/ipc/filesystem-import-result-types.ts b/src/main/ipc/filesystem-import-result-types.ts index 1d3e1a3fcad..cb6ff3f119a 100644 --- a/src/main/ipc/filesystem-import-result-types.ts +++ b/src/main/ipc/filesystem-import-result-types.ts @@ -1,8 +1,3 @@ -import type { - StagedRuntimeUploadEntry, - StagedRuntimeUploadSource -} from '../../shared/runtime-upload-staging-contract' - export type ImportSkipReason = 'missing' | 'symlink' | 'permission-denied' | 'unsupported' export type ResolveDroppedPathsResult = { @@ -31,8 +26,3 @@ export type ImportItemResult = status: 'failed' reason: string } - -// Why: staging crosses IPC to the renderer and back into the streamer, so the -// shape lives in shared and every layer names the same type. -export type StagedExternalImportSource = StagedRuntimeUploadSource -export type StagedExternalImportEntry = StagedRuntimeUploadEntry diff --git a/src/main/ipc/filesystem-mutations.ts b/src/main/ipc/filesystem-mutations.ts index 7cce83aa3e5..8563608e49d 100644 --- a/src/main/ipc/filesystem-mutations.ts +++ b/src/main/ipc/filesystem-mutations.ts @@ -14,9 +14,9 @@ import { assertNotExists, rethrowWithUserMessage } from './filesystem-create-pat import type { ImportItemResult, ImportSkipReason, - ResolveDroppedPathsResult, - StagedExternalImportSource + ResolveDroppedPathsResult } from './filesystem-import-result-types' +import type { StagedRuntimeUploadSource } from '../../shared/runtime-upload-staging-contract' import { importOneSource } from './filesystem-import-local' import { stagedRuntimeUploadByteLength, @@ -202,8 +202,8 @@ export function registerFilesystemMutationHandlers(store: Store): void { async ( _event, args: { sourcePaths: string[] } - ): Promise<{ sources: StagedExternalImportSource[] }> => { - const sources: StagedExternalImportSource[] = [] + ): Promise<{ sources: StagedRuntimeUploadSource[] }> => { + const sources: StagedRuntimeUploadSource[] = [] // Why: one budget for the whole drop — per-source counters would let five // 2 GB files through a ceiling meant to cap the drop. let totalBytes = 0 diff --git a/src/main/ipc/filesystem-runtime-upload-staging.ts b/src/main/ipc/filesystem-runtime-upload-staging.ts index 86b5f884820..286ae945870 100644 --- a/src/main/ipc/filesystem-runtime-upload-staging.ts +++ b/src/main/ipc/filesystem-runtime-upload-staging.ts @@ -9,14 +9,14 @@ import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path' import { authorizeExternalPath } from './filesystem-auth' import { isENOENT } from './filesystem-path-containment' import type { - StagedExternalImportEntry, - StagedExternalImportSource -} from './filesystem-import-result-types' + StagedRuntimeUploadEntry, + StagedRuntimeUploadSource +} from '../../shared/runtime-upload-staging-contract' class RuntimeUploadSymlinkError extends Error {} /** Bytes this source contributes to the drop budget; 0 unless it staged. */ -export function stagedRuntimeUploadByteLength(source: StagedExternalImportSource): number { +export function stagedRuntimeUploadByteLength(source: StagedRuntimeUploadSource): number { if (source.status !== 'staged') { return 0 } @@ -33,7 +33,7 @@ export function stagedRuntimeUploadByteLength(source: StagedExternalImportSource export async function stageOneSourceForRuntimeUpload( sourcePath: string, totalBytesBefore = 0 -): Promise { +): Promise { const resolvedSource = resolve(sourcePath) // Why: runtime uploads read client-local paths in the client main process; @@ -94,8 +94,8 @@ export async function stageOneSourceForRuntimeUpload( async function stageDirectoryEntries( rootPath: string, totalBytesBefore: number -): Promise { - const entries: StagedExternalImportEntry[] = [{ relativePath: '', kind: 'directory' }] +): Promise { + const entries: StagedRuntimeUploadEntry[] = [{ relativePath: '', kind: 'directory' }] let totalBytes = totalBytesBefore const rootRealPath = await realpath(rootPath) @@ -148,7 +148,7 @@ async function stageFileEntry( filePath: string, relativePath: string, options: { rootRealPath?: string; totalBytesBefore: number } -): Promise<{ entry: StagedExternalImportEntry; byteLength: number }> { +): Promise<{ entry: StagedRuntimeUploadEntry; byteLength: number }> { const statResult = await lstat(filePath) const displayPath = normalizeRelativeUploadPath(relativePath) // Why: a dropped file's relative path is '', so errors would name nothing. diff --git a/src/main/ipc/gitlab-ci-job-handlers.ts b/src/main/ipc/gitlab-ci-job-handlers.ts index f80eda00c2a..07251ffc6f8 100644 --- a/src/main/ipc/gitlab-ci-job-handlers.ts +++ b/src/main/ipc/gitlab-ci-job-handlers.ts @@ -2,7 +2,7 @@ import { ipcMain } from 'electron' import { toGitLabJobLogExcerptResult } from '../../shared/gitlab-job-log-excerpt' import type { Store } from '../persistence' import { getJobTrace, retryJob } from '../gitlab/client' -import type { ProjectRef } from '../gitlab/gl-utils' +import type { GitLabProjectRef } from '../gitlab/gl-utils' import type { GitLabRepoSelectorArgs } from './gitlab-repo-access' import { assertRegisteredRepo, localGitOptionArgs, repoConnectionId } from './gitlab-repo-access' @@ -13,7 +13,7 @@ export function registerGitLabCiJobHandlers(store: Store): void { _event, args: GitLabRepoSelectorArgs & { jobId: number - projectRef?: ProjectRef | null + projectRef?: GitLabProjectRef | null logExcerpt?: boolean } ) => { @@ -34,7 +34,7 @@ export function registerGitLabCiJobHandlers(store: Store): void { 'gitlab:retryJob', async ( _event, - args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: ProjectRef | null } + args: GitLabRepoSelectorArgs & { jobId: number; projectRef?: GitLabProjectRef | null } ) => { const repo = assertRegisteredRepo(args, store) return retryJob( diff --git a/src/main/ipc/gitlab-merge-request-mutation-handlers.ts b/src/main/ipc/gitlab-merge-request-mutation-handlers.ts index b47b9523e46..1ce5e127443 100644 --- a/src/main/ipc/gitlab-merge-request-mutation-handlers.ts +++ b/src/main/ipc/gitlab-merge-request-mutation-handlers.ts @@ -12,7 +12,7 @@ import { updateMR, updateMRReviewers } from '../gitlab/client' -import type { ProjectRef } from '../gitlab/gl-utils' +import type { GitLabProjectRef } from '../gitlab/gl-utils' import type { GitLabRepoSelectorArgs } from './gitlab-repo-access' import { assertRegisteredRepo, localGitOptionArgs, repoConnectionId } from './gitlab-repo-access' @@ -92,7 +92,7 @@ export function registerGitLabMergeRequestMutationHandlers(store: Store): void { sourceContext?: TaskSourceContext | null iid: number reviewerIds: number[] - projectRef?: ProjectRef | null + projectRef?: GitLabProjectRef | null } ) => { const repo = assertRegisteredRepo(args, store) @@ -134,7 +134,7 @@ export function registerGitLabMergeRequestMutationHandlers(store: Store): void { sourceContext?: TaskSourceContext | null iid: number input: GitLabMRInlineCommentInput - projectRef?: ProjectRef | null + projectRef?: GitLabProjectRef | null } ) => { const repo = assertRegisteredRepo(args, store) diff --git a/src/main/ipc/gitlab-work-item-handlers.ts b/src/main/ipc/gitlab-work-item-handlers.ts index 4f98680d3f1..1387a7a5851 100644 --- a/src/main/ipc/gitlab-work-item-handlers.ts +++ b/src/main/ipc/gitlab-work-item-handlers.ts @@ -9,7 +9,7 @@ import { import { recordGitLabProjectRecent } from '../gitlab/gitlab-project-recents' import { getWorkItemByProjectRef, listWorkItems } from '../gitlab/client' import { getWorkItemDetails } from '../gitlab/work-item-details' -import type { ProjectRef } from '../gitlab/gl-utils' +import type { GitLabProjectRef } from '../gitlab/gl-utils' import type { GitLabRepoSelectorArgs } from './gitlab-repo-access' import { assertRegisteredRepo, localGitOptionArgs, repoConnectionId } from './gitlab-repo-access' @@ -79,7 +79,7 @@ export function registerGitLabWorkItemHandlers(store: Store): void { } ) => { const repo = assertRegisteredRepo(args, store) - const projectRef: ProjectRef = { host: args.host, path: args.path } + const projectRef: GitLabProjectRef = { host: args.host, path: args.path } const result = await getWorkItemByProjectRef( repo.path, projectRef, diff --git a/src/main/ipc/ssh-ipc-mock-shapes.ts b/src/main/ipc/ssh-ipc-mock-shapes.ts index 28efa30ee39..a314d9108e6 100644 --- a/src/main/ipc/ssh-ipc-mock-shapes.ts +++ b/src/main/ipc/ssh-ipc-mock-shapes.ts @@ -1,12 +1,9 @@ import type { Mock } from 'vitest' -import type { SshPtySourceFrame } from '../providers/ssh-pty-source-frame' // Declared shapes for the SSH IPC mock registry. The suites assert on recorded calls rather // than argument types, so every spy stays `Mock` (vi.fn()'s untyped default) — naming the // shapes here is what keeps declaration emit from reaching into @vitest/spy internals. -export type SshIpcTestSource = SshPtySourceFrame - /** Callbacks production code hands a manager mock at construction / setCallbacks time. */ export type MockCallbacksRef = { current: unknown } diff --git a/src/main/ipc/ssh-ipc-test-harness.ts b/src/main/ipc/ssh-ipc-test-harness.ts index 581fcc916ca..6446c1bb49d 100644 --- a/src/main/ipc/ssh-ipc-test-harness.ts +++ b/src/main/ipc/ssh-ipc-test-harness.ts @@ -10,11 +10,8 @@ import { getPtyIdsForConnection } from './pty' import type { SshIpcMocks } from './ssh-ipc-module-mocks' -import type { - SshConnectionManagerMock, - SshIpcTestSource, - SshPortForwardManagerMock -} from './ssh-ipc-mock-shapes' +import type { SshConnectionManagerMock, SshPortForwardManagerMock } from './ssh-ipc-mock-shapes' +import type { SshPtySourceFrame } from '../providers/ssh-pty-source-frame' export type RelayDisposeCallback = (reason: 'shutdown' | 'connection_lost') => void @@ -49,7 +46,7 @@ export type RelayLaunchResultMock = { export type SshIpcHarness = { relayBuildId: string - ipcTestSource: SshIpcTestSource + ipcTestSource: SshPtySourceFrame handlers: Map unknown> mockStore: SshLeaseStoreMock mockWindow: MockBrowserWindow diff --git a/src/main/ipc/telemetry.ts b/src/main/ipc/telemetry.ts index 5e649f51b84..ea0d092f244 100644 --- a/src/main/ipc/telemetry.ts +++ b/src/main/ipc/telemetry.ts @@ -14,7 +14,8 @@ import { consumeConsentMutationToken } from '../telemetry/burst-cap' import { persistBannerAcknowledgeWithoutEmitting, setOptIn, track } from '../telemetry/client' import { getCohortAtEmit } from '../telemetry/cohort-classifier' import { getOnboardingCohortAtEmit } from '../telemetry/onboarding-cohort-classifier' -import { resolveConsent, type ConsentState } from '../telemetry/consent' +import { resolveConsent } from '../telemetry/consent' +import type { TelemetryConsentState } from '../../shared/telemetry-consent-types' import type { Store } from '../persistence' import { isCohortExtendedEvent, isOnboardingEvent } from '../../shared/telemetry-events' import type { EventName, EventProps, OptInVia } from '../../shared/telemetry-events' @@ -99,7 +100,7 @@ export function registerTelemetryHandlers(store: Store): void { }) // Read-only getter: lets the Privacy pane see env-var blocks (DO_NOT_TRACK/ORCA_TELEMETRY_DISABLED/CI), which are main-side state the renderer can't read. - ipcMain.handle('telemetry:getConsentState', (): ConsentState => { + ipcMain.handle('telemetry:getConsentState', (): TelemetryConsentState => { if (!storeRef) { // Fail closed: no store means we can't honor the stored preference, so surface pending_banner, not a misleading 'enabled'. return { effective: 'pending_banner' } diff --git a/src/main/ipc/worktrees/metadata/lineage-owner-resolution.ts b/src/main/ipc/worktrees/metadata/lineage-owner-resolution.ts index a8a58bf81d1..3d637da9c0b 100644 --- a/src/main/ipc/worktrees/metadata/lineage-owner-resolution.ts +++ b/src/main/ipc/worktrees/metadata/lineage-owner-resolution.ts @@ -12,16 +12,13 @@ export type LineageOwner = | { status: 'owned'; hostId: ExecutionHostId } | { status: 'ambiguous' | 'contradictory' | 'runtime' } -export type LineageFolder = FolderWorkspace -export type LineageGroup = ProjectGroup - export type LineageResolutionContext = { store: Store repos: Repo[] - groups: LineageGroup[] + groups: ProjectGroup[] reposById: Map - foldersById: Map - groupsById: Map + foldersById: Map + groupsById: Map groupSubtreeIdsByRoot: Map> worktreeOwners: Map folderOwners: Map diff --git a/src/main/ipc/worktrees/metadata/workspace-lineage-filtering.ts b/src/main/ipc/worktrees/metadata/workspace-lineage-filtering.ts index 3106812418c..cfec4e4c7d3 100644 --- a/src/main/ipc/worktrees/metadata/workspace-lineage-filtering.ts +++ b/src/main/ipc/worktrees/metadata/workspace-lineage-filtering.ts @@ -11,15 +11,12 @@ import { resolveRepoLineageOwner, resolveWorktreeLineageOwner } from './lineage-owner-resolution' -import type { - LineageFolder, - LineageOwner, - LineageResolutionContext -} from './lineage-owner-resolution' +import type { FolderWorkspace } from '../../../../shared/folder-workspace-types' +import type { LineageOwner, LineageResolutionContext } from './lineage-owner-resolution' export function getFolderLineageCandidateRepos( context: LineageResolutionContext, - folder: LineageFolder + folder: FolderWorkspace ): Repo[] { let groupIds = context.groupSubtreeIdsByRoot.get(folder.projectGroupId) if (!groupIds) { diff --git a/src/main/memory/hydrate-local-pty-registry.ts b/src/main/memory/hydrate-local-pty-registry.ts index 4e5063b5fc1..fd993d6a576 100644 --- a/src/main/memory/hydrate-local-pty-registry.ts +++ b/src/main/memory/hydrate-local-pty-registry.ts @@ -18,8 +18,6 @@ import { getLocalProjectWorktreeGitOptions } from '../project-runtime-git-option import { listLocalRepoWorktreesStrict } from '../repo-worktrees' import { listRegisteredPtys, registerPty } from './pty-registry' -type HydrationStore = Store - type DaemonInventory = { complete: boolean sessions: SessionInfo[] @@ -37,7 +35,7 @@ export const LOCAL_PTY_REGISTRY_GIT_ENUMERATION_CONCURRENCY = 4 let hasHydrated = false let hydrationInFlight: Promise | null = null -export function hydrateLocalPtyRegistryAtBoot(store: HydrationStore): Promise { +export function hydrateLocalPtyRegistryAtBoot(store: Store): Promise { if (hasHydrated) { return Promise.resolve() } @@ -75,10 +73,7 @@ export function hydrateLocalPtyRegistryAtBoot(store: HydrationStore): Promise { +async function hydrateLocalPtyRegistry(store: Store, signal: AbortSignal): Promise { throwIfSignalAborted(signal) const provider = getDaemonProvider() if (!provider) { @@ -215,10 +210,7 @@ function getLocalRepoCatalog(repos: Repo[]): LocalRepoCatalog { return { byId, ownerCountById } } -function getVerifiedFolderWorktreeIds( - store: HydrationStore, - repoCatalog: LocalRepoCatalog -): Set { +function getVerifiedFolderWorktreeIds(store: Store, repoCatalog: LocalRepoCatalog): Set { const verified = new Set() const folders = store.getFolderWorkspaces() const counts = new Map() diff --git a/src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts b/src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts index 978f076d6e9..6f4f5758ac1 100644 --- a/src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts +++ b/src/main/native-chat/agent-session-journal/journal-corruption-repair.test.ts @@ -59,7 +59,7 @@ function open(overrides: Partial[0]> }) } -async function withJournalDatabase(run: (db: Database.Database) => void): Promise { +async function withJournalDatabase(run: (db: Database) => void): Promise { const opened = openJournalDatabase(journalDatabaseFile(root)) try { run(opened.db) diff --git a/src/main/native-chat/agent-session-journal/journal-database.test.ts b/src/main/native-chat/agent-session-journal/journal-database.test.ts index 358c42c8d55..85608bc30af 100644 --- a/src/main/native-chat/agent-session-journal/journal-database.test.ts +++ b/src/main/native-chat/agent-session-journal/journal-database.test.ts @@ -173,7 +173,7 @@ describe('schema creation', () => { it('publishes no table until the version bump commits with it', () => { const original = Database.prototype.pragma const pragma = vi.spyOn(Database.prototype, 'pragma').mockImplementation(function ( - this: Database.Database, + this: Database, sql: string, options?: { simple?: boolean } ) { diff --git a/src/main/native-chat/agent-session-journal/journal-database.ts b/src/main/native-chat/agent-session-journal/journal-database.ts index 6f1cd60733b..5ffcb666c53 100644 --- a/src/main/native-chat/agent-session-journal/journal-database.ts +++ b/src/main/native-chat/agent-session-journal/journal-database.ts @@ -11,12 +11,12 @@ import { createJournalTablesSql, JOURNAL_DB_SCHEMA_VERSION } from './journal-dat export const JOURNAL_BUSY_TIMEOUT_MS = 5000 export type OpenJournalDatabase = { - db: Database.Database + db: Database /** A newer `user_version` was met: this build reads and never writes. */ readOnly: boolean } -export function journalPragmaNumber(db: Database.Database, name: string): number { +export function journalPragmaNumber(db: Database, name: string): number { return Number(db.pragma(name, { simple: true }) ?? 0) } @@ -48,7 +48,7 @@ export function openJournalDatabase(dbPath: string): OpenJournalDatabase { } } -function configureJournalPragmas(db: Database.Database): void { +function configureJournalPragmas(db: Database): void { db.pragma('journal_mode = WAL') db.pragma(`busy_timeout = ${JOURNAL_BUSY_TIMEOUT_MS}`) db.pragma('foreign_keys = ON') @@ -64,7 +64,7 @@ function configureJournalPragmas(db: Database.Database): void { * build does not latch read-only: it stamped its own version on and wrote * through SQL for a schema it did not have. */ -function createJournalSchema(db: Database.Database, stored: number): void { +function createJournalSchema(db: Database, stored: number): void { if (stored >= JOURNAL_DB_SCHEMA_VERSION) { return } diff --git a/src/main/native-chat/agent-session-journal/journal-epoch-controller.ts b/src/main/native-chat/agent-session-journal/journal-epoch-controller.ts index f428675fb2c..b230875166a 100644 --- a/src/main/native-chat/agent-session-journal/journal-epoch-controller.ts +++ b/src/main/native-chat/agent-session-journal/journal-epoch-controller.ts @@ -16,7 +16,7 @@ export class JournalEpochController { now: () => number mintEpoch: () => string serialize: (run: () => Promise) => Promise - database: () => { db: Database.Database } + database: () => { db: Database } readOnly: () => boolean setReadOnly: (readOnly: boolean) => void highestFence: () => number diff --git a/src/main/native-chat/agent-session-journal/journal-epoch-replacement.ts b/src/main/native-chat/agent-session-journal/journal-epoch-replacement.ts index 9512d5e9fa1..ceea0aab2b4 100644 --- a/src/main/native-chat/agent-session-journal/journal-epoch-replacement.ts +++ b/src/main/native-chat/agent-session-journal/journal-epoch-replacement.ts @@ -29,7 +29,7 @@ export type JournalReplacementItem = { } export function replaceJournalEpoch(input: { - db: Database.Database + db: Database identity: AgentSessionJournalIdentity reason: AgentJournalEpochReason fence: number diff --git a/src/main/native-chat/agent-session-journal/journal-epoch-rollover.ts b/src/main/native-chat/agent-session-journal/journal-epoch-rollover.ts index 5a5f39e5a62..cad3a998369 100644 --- a/src/main/native-chat/agent-session-journal/journal-epoch-rollover.ts +++ b/src/main/native-chat/agent-session-journal/journal-epoch-rollover.ts @@ -19,7 +19,7 @@ import { import type { AgentJournalEpochReason, JournalRow } from './journal-row-schema' export function publishNewEpoch(input: { - db: Database.Database + db: Database sessionId: string providerHandle: AgentSessionProviderHandle epoch: string diff --git a/src/main/native-chat/agent-session-journal/journal-open.ts b/src/main/native-chat/agent-session-journal/journal-open.ts index d86da773f71..9f7b4529b14 100644 --- a/src/main/native-chat/agent-session-journal/journal-open.ts +++ b/src/main/native-chat/agent-session-journal/journal-open.ts @@ -46,7 +46,7 @@ export type JournalLoad = { * session has no journal yet. */ export function replayJournal( - db: Database.Database, + db: Database, readOnly: boolean, sessionId: string ): JournalLoad | null { @@ -124,7 +124,7 @@ export function replayJournal( /** Rows after a cursor, in sequence order. Stops at the first row this build * cannot parse, exactly as replay does. */ export function readJournalRowsAfterCursor( - db: Database.Database, + db: Database, sessionId: string, epoch: string, afterSequence: number, diff --git a/src/main/native-chat/agent-session-journal/journal-repair-marker.ts b/src/main/native-chat/agent-session-journal/journal-repair-marker.ts index f9ef09e688d..1b8c0bc8c1d 100644 --- a/src/main/native-chat/agent-session-journal/journal-repair-marker.ts +++ b/src/main/native-chat/agent-session-journal/journal-repair-marker.ts @@ -29,7 +29,7 @@ const DELETE_REPAIR = 'DELETE FROM journal_repairs WHERE session_id = ?' * superseded says nothing about the live one. */ export function pendingJournalRepairSequence( - db: Database.Database, + db: Database, sessionId: string, epoch: string ): number | null { @@ -41,13 +41,13 @@ export function pendingJournalRepairSequence( /** Retires the marker. Called from inside the epoch transactions, whose new * epoch is the rebuilt history the marker was holding out for. */ -export function clearJournalRepairMarker(db: Database.Database, sessionId: string): void { +export function clearJournalRepairMarker(db: Database, sessionId: string): void { db.prepare(DELETE_REPAIR).run(sessionId) } /** Drop the rejected suffix and record that it is owed, atomically. */ export function deleteJournalRepairedSuffix(input: { - db: Database.Database + db: Database sessionId: string epoch: string /** First sequence of the rejected suffix. */ diff --git a/src/main/native-chat/agent-session-journal/journal-row-table.ts b/src/main/native-chat/agent-session-journal/journal-row-table.ts index f07ad19d4d5..84ecf62c2a0 100644 --- a/src/main/native-chat/agent-session-journal/journal-row-table.ts +++ b/src/main/native-chat/agent-session-journal/journal-row-table.ts @@ -23,13 +23,13 @@ WHERE session_id = ? AND epoch = ? AND seq > ? ORDER BY seq ASC` const SELECT_ROWS_AFTER_LIMITED = `${SELECT_ROWS_AFTER} LIMIT ?` const DELETE_SUFFIX = 'DELETE FROM journal_rows WHERE session_id = ? AND epoch = ? AND seq >= ?' -export function readJournalSessionEpoch(db: Database.Database, sessionId: string): string | null { +export function readJournalSessionEpoch(db: Database, sessionId: string): string | null { const row = db.prepare(SELECT_SESSION).get(sessionId) as { epoch?: string } | undefined return row?.epoch ?? null } export function upsertJournalSessionRow( - db: Database.Database, + db: Database, sessionId: string, epoch: string, updatedAt: number @@ -37,18 +37,14 @@ export function upsertJournalSessionRow( db.prepare(UPSERT_SESSION).run(sessionId, epoch, updatedAt) } -export function insertJournalRow( - db: Database.Database, - sessionId: string, - row: JournalRow -): number { +export function insertJournalRow(db: Database, sessionId: string, row: JournalRow): number { const rowJson = serializeJournalRow(row) db.prepare(INSERT_ROW).run(sessionId, row.epoch, row.seq, row.ts, rowJson) return Buffer.byteLength(rowJson, 'utf8') } export function readJournalEpochRows( - db: Database.Database, + db: Database, sessionId: string, epoch: string ): JournalStoredRow[] { @@ -62,7 +58,7 @@ const EPOCH_ROW_PAGE_SIZE = 128 /** Epoch rows in sequence order, fetched one completed statement at a time. */ export function* iterateJournalEpochRows( - db: Database.Database, + db: Database, sessionId: string, epoch: string ): Generator { @@ -79,7 +75,7 @@ export function* iterateJournalEpochRows( } export function readJournalRowsAfter( - db: Database.Database, + db: Database, sessionId: string, epoch: string, afterSeq: number, @@ -99,13 +95,13 @@ export function readJournalRowsAfter( * optimization: measured at 0.26% of the database in WAL bytes where the * `WHERE session_id = ?` form rewrote every emptied leaf at up to 99%. */ -export function deleteAllJournalRows(db: Database.Database): void { +export function deleteAllJournalRows(db: Database): void { db.exec('DELETE FROM journal_rows') } /** Drop the rejected suffix a repair found, from `fromSeq` to the tip. */ export function deleteJournalRowSuffix( - db: Database.Database, + db: Database, sessionId: string, epoch: string, fromSeq: number diff --git a/src/main/native-chat/agent-session-journal/journal-row-writer.ts b/src/main/native-chat/agent-session-journal/journal-row-writer.ts index 85ff7da7a3f..6edcdf1eebd 100644 --- a/src/main/native-chat/agent-session-journal/journal-row-writer.ts +++ b/src/main/native-chat/agent-session-journal/journal-row-writer.ts @@ -7,7 +7,7 @@ export type JournalRowWriterDeps = { sessionId: string now: () => number serialize: (run: () => Promise) => Promise - database: () => { db: Database.Database } + database: () => { db: Database } readOnly: () => boolean highestFence: () => number nextSequence: () => number diff --git a/src/main/native-chat/agent-session-journal/journal-store-close.ts b/src/main/native-chat/agent-session-journal/journal-store-close.ts index f1b56d6a0db..7654df73df6 100644 --- a/src/main/native-chat/agent-session-journal/journal-store-close.ts +++ b/src/main/native-chat/agent-session-journal/journal-store-close.ts @@ -61,7 +61,7 @@ export class JournalConnectionCloser { constructor( private readonly deps: { - connection: () => Database.Database | null + connection: () => Database | null /** Chains onto the store's write queue past the closed gate. */ enqueue: (run: () => Promise) => Promise } diff --git a/src/main/native-chat/agent-session-journal/journal-store-open.ts b/src/main/native-chat/agent-session-journal/journal-store-open.ts index 7b5b6d0dff8..22379877aad 100644 --- a/src/main/native-chat/agent-session-journal/journal-store-open.ts +++ b/src/main/native-chat/agent-session-journal/journal-store-open.ts @@ -12,10 +12,6 @@ import type { JournalLoad } from './journal-open' import { journalRepairDisclosure, type JournalRepairDisclosure } from './journal-repair-disclosure' import { staleSubagentRosterRevisions } from './journal-subagent-liveness' -/** What any of this file's disclosures hands the store — a repair's, or the - * pre-SQLite notice's. Same shape, and neither is only a repair. */ -type JournalDisclosure = JournalRepairDisclosure - export async function ensureJournalDir(journalDir: string): Promise { await mkdir(journalDir, { recursive: true }) } @@ -99,8 +95,8 @@ async function discloseFileFormatRemnant(input: { journalDir: string agent: AgentType appendItem: ( - identity: JournalDisclosure['identity'], - body: JournalDisclosure['body'], + identity: JournalRepairDisclosure['identity'], + body: JournalRepairDisclosure['body'], fence: number ) => Promise highestFence: () => number diff --git a/src/main/native-chat/agent-session-journal/journal-store-schema.test.ts b/src/main/native-chat/agent-session-journal/journal-store-schema.test.ts index df34ef73e73..618acecaaa8 100644 --- a/src/main/native-chat/agent-session-journal/journal-store-schema.test.ts +++ b/src/main/native-chat/agent-session-journal/journal-store-schema.test.ts @@ -55,7 +55,7 @@ function open(): Promise { }) } -async function withDatabase(run: (db: Database.Database) => void): Promise { +async function withDatabase(run: (db: Database) => void): Promise { const opened = openJournalDatabase(journalDatabaseFile(root)) try { run(opened.db) diff --git a/src/main/native-chat/agent-session-journal/journal-store.test.ts b/src/main/native-chat/agent-session-journal/journal-store.test.ts index 3592947faaa..8b70f945b64 100644 --- a/src/main/native-chat/agent-session-journal/journal-store.test.ts +++ b/src/main/native-chat/agent-session-journal/journal-store.test.ts @@ -372,10 +372,7 @@ describe('on-disk layout', () => { /** Opens the session database directly, so a case can stage a fault or read * back what a commit actually stored. */ -async function withJournalDatabase( - journalDir: string, - run: (db: Database.Database) => void -): Promise { +async function withJournalDatabase(journalDir: string, run: (db: Database) => void): Promise { const { openJournalDatabase } = await import('./journal-database') const opened = openJournalDatabase(journalDatabaseFile(journalDir)) try { diff --git a/src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts b/src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts index 214c889b5c9..2bd4288c89c 100644 --- a/src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts +++ b/src/main/native-chat/agent-session-wire/agent-session-journal-recovery.test.ts @@ -102,10 +102,7 @@ async function seedRepairableSession(): Promise { await deleteRow(1) } -async function withJournalDatabase( - directory: string, - run: (db: Database.Database) => void -): Promise { +async function withJournalDatabase(directory: string, run: (db: Database) => void): Promise { const opened = openJournalDatabase(journalDatabaseFile(directory)) try { run(opened.db) diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart-tui.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart-tui.ts index c16ac681226..80f14e720b3 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart-tui.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart-tui.ts @@ -13,7 +13,7 @@ import type { StructuredTuiOwner } from './structured-agent-session-handoff-types' -export type StructuredAgentSessionRestartAccess = { +export type RestartAccess = { deps: StructuredAgentSessionHandoffDeps requireRecord: (sessionId: string) => AgentSessionRecord flowContext: () => StructuredAgentSessionHandoffFlowContext @@ -21,13 +21,10 @@ export type StructuredAgentSessionRestartAccess = { setStatus: (sessionId: string, status: AgentSessionHandoffStatus) => void } -type ContinueHandoff = ( - input: StructuredAgentSessionRestartAccess, - record: AgentSessionRecord -) => Promise +type ContinueHandoff = (input: RestartAccess, record: AgentSessionRecord) => Promise export async function recoverUnavailableTuiAsNative( - input: StructuredAgentSessionRestartAccess, + input: RestartAccess, record: AgentSessionRecord, continueHandoff: ContinueHandoff ): Promise { @@ -64,7 +61,7 @@ export async function recoverUnavailableTuiAsNative( } export async function recoverTuiOwnerOrContinue( - input: StructuredAgentSessionRestartAccess, + input: RestartAccess, record: AgentSessionRecord, continueHandoff: ContinueHandoff ): Promise { @@ -84,7 +81,7 @@ export async function recoverTuiOwnerOrContinue( } export async function persistReprovedTuiOwner( - input: StructuredAgentSessionRestartAccess, + input: RestartAccess, sessionId: string, owner: StructuredTuiOwner ): Promise { diff --git a/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart.ts b/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart.ts index 13a26f2a7a9..cd026921a04 100644 --- a/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart.ts +++ b/src/main/native-chat/agent-session-wire/structured-agent-session-handoff-restart.ts @@ -16,11 +16,9 @@ import { persistReprovedTuiOwner, recoverTuiOwnerOrContinue, recoverUnavailableTuiAsNative, - type StructuredAgentSessionRestartAccess + type RestartAccess } from './structured-agent-session-handoff-restart-tui' -type RestartAccess = StructuredAgentSessionRestartAccess - export async function restoreStructuredAgentSessionHandoff( input: RestartAccess, sessionId: string diff --git a/src/main/opencode-usage/opencode-usage-row-queries.ts b/src/main/opencode-usage/opencode-usage-row-queries.ts index 2e633d7cda2..5378b64ee72 100644 --- a/src/main/opencode-usage/opencode-usage-row-queries.ts +++ b/src/main/opencode-usage/opencode-usage-row-queries.ts @@ -29,17 +29,17 @@ type OpenCodeSessionUsageRow = { tokens_cache_read: number } -function getProjectJoin(db: Database.Database): string { +function getProjectJoin(db: Database): string { return tableExists(db, 'project') && columnExists(db, 'session', 'project_id') ? 'LEFT JOIN project p ON p.id = s.project_id' : 'LEFT JOIN (SELECT NULL AS id, NULL AS worktree) p ON 1 = 0' } -function getSessionModelSelect(db: Database.Database): string { +function getSessionModelSelect(db: Database): string { return columnExists(db, 'session', 'model') ? 's.model AS session_model' : 'NULL AS session_model' } -function getAssistantSessionMessageCount(db: Database.Database): number { +function getAssistantSessionMessageCount(db: Database): number { if (!tableExists(db, 'session_message')) { return 0 } @@ -52,7 +52,7 @@ function getAssistantSessionMessageCount(db: Database.Database): number { return row?.count ?? 0 } -function canReadSessionUsageRows(db: Database.Database): boolean { +function canReadSessionUsageRows(db: Database): boolean { if (!tableExists(db, 'session')) { return false } @@ -61,7 +61,7 @@ function canReadSessionUsageRows(db: Database.Database): boolean { ) } -function getSessionUsageRowCount(db: Database.Database): number { +function getSessionUsageRowCount(db: Database): number { if (!canReadSessionUsageRows(db)) { return 0 } @@ -75,7 +75,7 @@ function getSessionUsageRowCount(db: Database.Database): number { return row?.count ?? 0 } -function selectSessionUsageRows(db: Database.Database): OpenCodeUsageRow[] { +function selectSessionUsageRows(db: Database): OpenCodeUsageRow[] { const projectJoin = getProjectJoin(db) const sessionModelSelect = getSessionModelSelect(db) const rows = db @@ -115,7 +115,7 @@ function selectSessionUsageRows(db: Database.Database): OpenCodeUsageRow[] { })) } -export function selectUsageRows(db: Database.Database): OpenCodeUsageRow[] { +export function selectUsageRows(db: Database): OpenCodeUsageRow[] { if (!tableExists(db, 'session')) { return [] } diff --git a/src/main/opencode-usage/opencode-usage-worktree-attribution.ts b/src/main/opencode-usage/opencode-usage-worktree-attribution.ts index 03d0e563059..20ff7287cc6 100644 --- a/src/main/opencode-usage/opencode-usage-worktree-attribution.ts +++ b/src/main/opencode-usage/opencode-usage-worktree-attribution.ts @@ -10,8 +10,6 @@ import { import type { UsageScanWorktreeRef } from '../usage/usage-provider-contract' import type { OpenCodeUsageAttributedEvent, OpenCodeUsageParsedEvent } from './types' -export type OpenCodeUsageWorktreeRef = UsageScanWorktreeRef - function getDefaultProjectLabel(cwd: string | null): string { if (!cwd) { return 'Unknown location' @@ -56,8 +54,8 @@ function isContainingPath(candidatePath: string, targetPath: string): boolean { } export async function buildWorktreesWithCanonicalPaths( - worktrees: OpenCodeUsageWorktreeRef[] -): Promise<(OpenCodeUsageWorktreeRef & { canonicalPath: string })[]> { + worktrees: UsageScanWorktreeRef[] +): Promise<(UsageScanWorktreeRef & { canonicalPath: string })[]> { return canonicalizeUsageWorktreePaths(worktrees, canonicalizePath) } @@ -71,8 +69,8 @@ async function canonicalizePath(pathValue: string): Promise { function findContainingWorktree( cwd: string, - worktrees: (OpenCodeUsageWorktreeRef & { canonicalPath: string })[] -): OpenCodeUsageWorktreeRef | null { + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[] +): UsageScanWorktreeRef | null { const normalizedCwd = normalizeFsPath(cwd) for (const worktree of worktrees) { if (areWorktreePathsEqual(worktree.canonicalPath, normalizedCwd)) { @@ -87,7 +85,7 @@ function findContainingWorktree( export async function attributeOpenCodeUsageEvent( event: OpenCodeUsageParsedEvent, - worktrees: (OpenCodeUsageWorktreeRef & { canonicalPath: string })[] + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[] ): Promise { const day = localDayFromTimestamp(event.timestamp) if (!day) { diff --git a/src/main/opencode-usage/scanner.test.ts b/src/main/opencode-usage/scanner.test.ts index f4ce66b51e4..ca520c574fd 100644 --- a/src/main/opencode-usage/scanner.test.ts +++ b/src/main/opencode-usage/scanner.test.ts @@ -12,7 +12,7 @@ const WORKTREE = '/workspace/repo' let tempDirs: string[] = [] -function createTempDb(): { db: Database.Database; path: string } { +function createTempDb(): { db: Database; path: string } { const dir = mkdtempSync(join(tmpdir(), 'orca-opencode-usage-')) tempDirs.push(dir) const path = join(dir, 'opencode.db') @@ -31,7 +31,7 @@ function worktrees() { ] } -function createSessionTotalsSchema(db: Database.Database): void { +function createSessionTotalsSchema(db: Database): void { db.exec(` CREATE TABLE session ( id TEXT PRIMARY KEY, @@ -49,11 +49,7 @@ function createSessionTotalsSchema(db: Database.Database): void { `) } -function insertSessionTotalsRow( - db: Database.Database, - sessionId: string, - inputTokens: number -): void { +function insertSessionTotalsRow(db: Database, sessionId: string, inputTokens: number): void { db.prepare( `INSERT INTO session ( id, directory, title, model, cost, diff --git a/src/main/opencode-usage/scanner.ts b/src/main/opencode-usage/scanner.ts index bd1ae8e3670..2142602110d 100644 --- a/src/main/opencode-usage/scanner.ts +++ b/src/main/opencode-usage/scanner.ts @@ -10,9 +10,9 @@ import { parseOpenCodeUsageRow } from './opencode-usage-row-parsing' import { selectUsageRows } from './opencode-usage-row-queries' import { attributeOpenCodeUsageEvent, - buildWorktreesWithCanonicalPaths, - type OpenCodeUsageWorktreeRef + buildWorktreesWithCanonicalPaths } from './opencode-usage-worktree-attribution' +import type { UsageScanWorktreeRef } from '../usage/usage-provider-contract' import type { OpenCodeUsageAttributedEvent, OpenCodeUsageDailyAggregate, @@ -50,7 +50,7 @@ const { finalizeSessions, mergeSessions, mergeDailyAggregates, sortDailyAggregat export async function parseOpenCodeUsageDatabase( dbPath: string, - worktrees: (OpenCodeUsageWorktreeRef & { canonicalPath: string })[], + worktrees: (UsageScanWorktreeRef & { canonicalPath: string })[], options: { claimSession?: (sessionId: string) => boolean } = {} ): Promise { const processedDatabase = await getProcessedDatabaseInfo(dbPath) @@ -95,7 +95,7 @@ export async function parseOpenCodeUsageDatabase( } export async function scanOpenCodeUsageDatabases( - worktrees: OpenCodeUsageWorktreeRef[], + worktrees: UsageScanWorktreeRef[], previousProcessedDatabases: OpenCodeUsagePersistedDatabase[] ): Promise<{ processedDatabases: OpenCodeUsagePersistedDatabase[] diff --git a/src/main/opencode-usage/schema-helpers.ts b/src/main/opencode-usage/schema-helpers.ts index d392eb58d7d..33337e88d21 100644 --- a/src/main/opencode-usage/schema-helpers.ts +++ b/src/main/opencode-usage/schema-helpers.ts @@ -3,7 +3,6 @@ import type SyncDatabase from '../sqlite/sync-database' // Why: OpenCode's usage scanner and the AI Vault session scanner both need to // probe the opencode.db schema shape across multiple DB generations. Centralizing // the probes here avoids two private copies and keeps the contract testable. -type Database = SyncDatabase.Database /** * Check whether a table exists in the given SQLite database. @@ -11,7 +10,7 @@ type Database = SyncDatabase.Database * @param tableName - The table name to look up in sqlite_master. * @returns `true` if the table exists, `false` otherwise. */ -export function tableExists(db: Database, tableName: string): boolean { +export function tableExists(db: SyncDatabase, tableName: string): boolean { const row = db .prepare("SELECT 1 AS found FROM sqlite_master WHERE type = 'table' AND name = ?") .get(tableName) as { found?: number } | undefined @@ -25,7 +24,7 @@ export function tableExists(db: Database, tableName: string): boolean { * @param columnName - The column name to find. * @returns `true` if the column exists on the table, `false` otherwise. */ -export function columnExists(db: Database, tableName: string, columnName: string): boolean { +export function columnExists(db: SyncDatabase, tableName: string, columnName: string): boolean { const rows = db.prepare(`PRAGMA table_info(${tableName})`).all() as { name?: string }[] return rows.some((row) => row.name === columnName) } diff --git a/src/main/orca-profiles/profile-cloud-dev-service.ts b/src/main/orca-profiles/profile-cloud-dev-service.ts index bb7a6e3599c..70a86d43cee 100644 --- a/src/main/orca-profiles/profile-cloud-dev-service.ts +++ b/src/main/orca-profiles/profile-cloud-dev-service.ts @@ -7,8 +7,6 @@ import { createCloudLinkedOrcaProfileRecord, linkOrcaProfileToCloud } from './pr import { readOrcaCloudSession, saveOrcaCloudSessionExchange } from './profile-cloud-session-store' import { createDevOrcaCloudSession } from './profile-cloud-dev-auth' -type DevProfileListResult = OrcaProfileListState - type DevCreateProfileResult = | { status: 'created' @@ -19,14 +17,14 @@ type DevCreateProfileResult = type DevMutationResult = | { status: 'updated' - list: DevProfileListResult + list: OrcaProfileListState } | { status: 'reconnect-required' } export function connectDevOrcaCloudProfile( active: ActiveOrcaProfileState, userDataPath: string -): DevProfileListResult { +): OrcaProfileListState { const session = createDevOrcaCloudSession({ localProfileId: active.profile.id }) saveOrcaCloudSessionExchange(active.profile.id, userDataPath, session) return linkOrcaProfileToCloud(active.profile.id, session.cloud, userDataPath) diff --git a/src/main/orca-profiles/profile-project-retired-name-transfer.ts b/src/main/orca-profiles/profile-project-retired-name-transfer.ts index f49ca4fef3b..afb086f163f 100644 --- a/src/main/orca-profiles/profile-project-retired-name-transfer.ts +++ b/src/main/orca-profiles/profile-project-retired-name-transfer.ts @@ -1,3 +1,4 @@ +import type { PersistedState } from '../../shared/persisted-state-types' import type { Repo } from '../../shared/repo-types' import { mergeRetiredNameRegistries, @@ -5,10 +6,9 @@ import { } from '../../shared/worktree/retired-name-registry' import { getRemoteRetirementNamespaceKey } from '../worktree-name-retirement' import { retirementNamespaceKeysToRead } from '../worktree-retirement-namespace' -import type { TransferProfileState } from './profile-project-state-file' export function extractRetiredNameRegistriesByNamespace( - sourceState: TransferProfileState, + sourceState: PersistedState, sourceRepo: Repo ): Record { const lookup = (targetId: string) => diff --git a/src/main/orca-profiles/profile-project-source-removal.ts b/src/main/orca-profiles/profile-project-source-removal.ts index 37480164ebc..23238db6359 100644 --- a/src/main/orca-profiles/profile-project-source-removal.ts +++ b/src/main/orca-profiles/profile-project-source-removal.ts @@ -1,20 +1,15 @@ +import type { PersistedState } from '../../shared/persisted-state-types' import type { WorkspaceKey } from '../../shared/folder-workspace-types' import { parseWorkspaceKey } from '../../shared/workspace-scope' -import { - rebuildRepoBackedProjectState, - type TransferProfileState -} from './profile-project-state-file' +import { rebuildRepoBackedProjectState } from './profile-project-state-file' import { removeRepoFromHostWorkspaceSessions, removeRepoFromWorkspaceSession } from './profile-project-session-state' import { isRepoWorktreeId, removeRepoWorktreeRecord } from './profile-project-worktree-identity' -export function removeSourceRepo( - state: TransferProfileState, - repoId: string -): TransferProfileState { - const next: TransferProfileState = { +export function removeSourceRepo(state: PersistedState, repoId: string): PersistedState { + const next: PersistedState = { ...state, repos: state.repos.filter((repo) => repo.id !== repoId), sparsePresetsByRepo: { ...state.sparsePresetsByRepo }, @@ -44,7 +39,7 @@ export function removeSourceRepo( return rebuildRepoBackedProjectState(next) } -function removeRepoWorktreeMetadata(state: TransferProfileState, repoId: string): void { +function removeRepoWorktreeMetadata(state: PersistedState, repoId: string): void { for (const key of Object.keys(state.worktreeMeta)) { if (isRepoWorktreeId(repoId, key)) { delete state.worktreeMeta[key] diff --git a/src/main/orca-profiles/profile-project-state-file.ts b/src/main/orca-profiles/profile-project-state-file.ts index d4f291a09e4..226c5845b57 100644 --- a/src/main/orca-profiles/profile-project-state-file.ts +++ b/src/main/orca-profiles/profile-project-state-file.ts @@ -17,8 +17,6 @@ import type { SparsePreset } from '../../shared/worktree/create-types' import type { RetiredNameRegistry } from '../../shared/worktree/retired-name-registry' import { getOrcaProfileDataFile } from './profile-index-store' -export type TransferProfileState = PersistedState - function isRecord(value: unknown): value is Record { return typeof value === 'object' && value !== null && !Array.isArray(value) } @@ -31,7 +29,7 @@ function recordOrEmpty(value: unknown): Record { return isRecord(value) ? value : {} } -export function readProfileState(profileId: string, userDataPath: string): TransferProfileState { +export function readProfileState(profileId: string, userDataPath: string): PersistedState { const defaults = getDefaultPersistedState(homedir()) const dataFile = getOrcaProfileDataFile(profileId, userDataPath) if (!existsSync(dataFile)) { @@ -94,7 +92,7 @@ export function readProfileState(profileId: string, userDataPath: string): Trans export function writeProfileState( profileId: string, userDataPath: string, - state: TransferProfileState + state: PersistedState ): void { const dataFile = getOrcaProfileDataFile(profileId, userDataPath) mkdirSync(dirname(dataFile), { recursive: true }) @@ -110,7 +108,7 @@ function isRepoBackedProjectHostSetup( return Boolean(setup.repoId && currentRepoIds.has(setup.repoId)) } -export function rebuildRepoBackedProjectState(state: TransferProfileState): TransferProfileState { +export function rebuildRepoBackedProjectState(state: PersistedState): PersistedState { const projection = projectHostSetupProjectionFromRepos(state.repos) const succession = carryProjectStateThroughIdentityChange(projection.projects, state.projects) const currentRepoIds = new Set(state.repos.map((repo) => repo.id)) diff --git a/src/main/orca-profiles/profile-project-transfer-payload.ts b/src/main/orca-profiles/profile-project-transfer-payload.ts index b2f74fd3609..752942d7da5 100644 --- a/src/main/orca-profiles/profile-project-transfer-payload.ts +++ b/src/main/orca-profiles/profile-project-transfer-payload.ts @@ -11,7 +11,6 @@ import { extractRetiredNameRegistriesByNamespace, mergeRetiredNameRegistryMaps } from './profile-project-retired-name-transfer' -import type { TransferProfileState } from './profile-project-state-file' import { rebuildRepoBackedProjectState } from './profile-project-state-file' import { mergeHostWorkspaceSessions, mergeWorkspaceSessions } from './profile-project-session-state' import { @@ -39,7 +38,7 @@ export type TransferPayload = { export function createTargetRepo( sourceRepo: Repo, - targetState: TransferProfileState, + targetState: PersistedState, copy: boolean ): Repo { const targetRepoId = @@ -56,7 +55,7 @@ export function createTargetRepo( return repo } -function createUniqueRepoId(state: TransferProfileState): string { +function createUniqueRepoId(state: PersistedState): string { const existingRepoIds = new Set(state.repos.map((repo) => repo.id)) let candidate = randomUUID() while (existingRepoIds.has(candidate)) { @@ -124,7 +123,7 @@ function rekeyWorkspaceLineageRecord( } export function createTransferPayload(args: { - sourceState: TransferProfileState + sourceState: PersistedState sourceRepo: Repo targetRepo: Repo includeSessions: boolean @@ -192,10 +191,10 @@ export function createTransferPayload(args: { } export function applyPayloadToTarget( - targetState: TransferProfileState, + targetState: PersistedState, payload: TransferPayload -): TransferProfileState { - const next: TransferProfileState = { +): PersistedState { + const next: PersistedState = { ...targetState, repos: [...targetState.repos, payload.repo], sparsePresetsByRepo: { diff --git a/src/main/orca-profiles/profile-project-transfer-worktree-ids.ts b/src/main/orca-profiles/profile-project-transfer-worktree-ids.ts index 485861d748d..e01db102c00 100644 --- a/src/main/orca-profiles/profile-project-transfer-worktree-ids.ts +++ b/src/main/orca-profiles/profile-project-transfer-worktree-ids.ts @@ -1,16 +1,12 @@ import type { PersistedState } from '../../shared/persisted-state-types' import { parseWorkspaceKey } from '../../shared/workspace-scope' -import type { TransferProfileState } from './profile-project-state-file' import { isRepoWorktreeId } from './profile-project-worktree-identity' import { getWorktreeIdFromHostIdentity, isWorktreeHostIdentity } from '../../shared/worktree/host-qualified-identity' -export function collectTransferWorktreeIds( - state: TransferProfileState, - repoId: string -): Set { +export function collectTransferWorktreeIds(state: PersistedState, repoId: string): Set { const ids = new Set() const add = (value: string | null | undefined): void => { if (value && isRepoWorktreeId(repoId, value)) { diff --git a/src/main/orcad/electron-serve-browser-process.ts b/src/main/orcad/electron-serve-browser-process.ts index ae3508289c9..2c395cff928 100644 --- a/src/main/orcad/electron-serve-browser-process.ts +++ b/src/main/orcad/electron-serve-browser-process.ts @@ -12,7 +12,8 @@ import type { import type { RuntimeMetadata } from '../../shared/runtime-bootstrap' import { BROWSER_UNAVAILABLE_ERROR_CODE } from '../../shared/runtime-types' import { readRuntimeMetadata } from '../runtime/runtime-metadata' -import { spawnProcess, type SpawnedProcess } from '../../shared/child-process/run-process' +import { spawnProcess } from '../../shared/child-process/run-process' +import type { ChildProcess } from 'node:child_process' import { sendOrcadSidecarRequest } from './orcad-sidecar-runtime-client' import { ElectronSidecarTabRegistry, @@ -90,7 +91,7 @@ function processIsLive(pid: number): boolean { } export class ElectronServeBrowserProcess { - private child: SpawnedProcess | null = null + private child: ChildProcess | null = null private metadata: RuntimeMetadata | null = null private readonly tabs = new ElectronSidecarTabRegistry() private sidecarDataPath: string | null = null diff --git a/src/main/persistence/loading-store/store.ts b/src/main/persistence/loading-store/store.ts index 017583e8f86..10e442bd4a8 100644 --- a/src/main/persistence/loading-store/store.ts +++ b/src/main/persistence/loading-store/store.ts @@ -33,7 +33,6 @@ import type { RetiredWorktreeNamePersistence } from './retired-worktree-name-per import type { SshLeaseRecoveryOperations } from './ssh-lease-recovery-operations' import type { WriteFlushBarrierOperations } from './write-flush-barriers' -export type StoreOptions = StoreRuntimeOptions export type PtyBindingSourceExpectation = { worktreeId?: string tabId: string @@ -49,7 +48,7 @@ export class Store { private readonly domains: StoreDomains private readonly state: PersistedState - constructor(options: StoreOptions = {}) { + constructor(options: StoreRuntimeOptions = {}) { this.runtime = new StoreRuntimeState(options) this.domains = createStoreDomains(this.runtime) installStoreDomainContexts(this, this.domains) diff --git a/src/main/ports/port-scan-command-client.ts b/src/main/ports/port-scan-command-client.ts index 27232ee8833..7d5c7b42b9d 100644 --- a/src/main/ports/port-scan-command-client.ts +++ b/src/main/ports/port-scan-command-client.ts @@ -36,7 +36,6 @@ export const MAX_CONSECUTIVE_DEATHS = 3 export const MAX_QUEUED_CALLS = 8 export type PortScanCommandResult = { stdout: string; spawnMs: number } -export type PortScanWorkerFactory = WorkerThreadFactory // Distinguishes "no worker at all" from a timeout or crash so the scanner can // log it once and callers never mistake it for a command timeout. @@ -68,7 +67,7 @@ export class PortScanCommandClient { private nextId = 1 private readonly host: LazyWorkerThreadHost - constructor(options: { workerFactory: PortScanWorkerFactory; log?: (message: string) => void }) { + constructor(options: { workerFactory: WorkerThreadFactory; log?: (message: string) => void }) { const log = options.log ?? ((message: string) => console.warn(message)) this.host = new LazyWorkerThreadHost({ factory: options.workerFactory, diff --git a/src/main/providers/posix-pane-foreground-fingerprint.test.ts b/src/main/providers/posix-pane-foreground-fingerprint.test.ts index 76cb68f79dc..5651e66d3a4 100644 --- a/src/main/providers/posix-pane-foreground-fingerprint.test.ts +++ b/src/main/providers/posix-pane-foreground-fingerprint.test.ts @@ -8,9 +8,7 @@ const SHELL = 4242 const AGENT = 4300 const OTHER_PANE = 9000 -type Row = PaneFingerprintRow - -const shell = (over: Partial = {}): Row => ({ +const shell = (over: Partial = {}): PaneFingerprintRow => ({ pid: SHELL, ppid: 1, pgid: SHELL, @@ -19,7 +17,7 @@ const shell = (over: Partial = {}): Row => ({ startTime: 'Thu Sep 3 16:02:01 2026', ...over }) -const agent = (over: Partial = {}): Row => ({ +const agent = (over: Partial = {}): PaneFingerprintRow => ({ pid: AGENT, ppid: SHELL, pgid: AGENT, @@ -28,7 +26,11 @@ const agent = (over: Partial = {}): Row => ({ startTime: 'Thu Sep 3 16:02:05 2026', ...over }) -const child = (pid: number, ppid: number, over: Partial = {}): Row => ({ +const child = ( + pid: number, + ppid: number, + over: Partial = {} +): PaneFingerprintRow => ({ pid, ppid, pgid: AGENT, @@ -37,7 +39,7 @@ const child = (pid: number, ppid: number, over: Partial = {}): Row => ({ startTime: `Thu Sep 3 16:03:${String(pid % 60).padStart(2, '0')} 2026`, ...over }) -const foreign = (): Row => ({ +const foreign = (): PaneFingerprintRow => ({ pid: OTHER_PANE, ppid: 1, pgid: OTHER_PANE, @@ -46,7 +48,7 @@ const foreign = (): Row => ({ startTime: 'Thu Sep 3 12:00:00 2026' }) -const fp = (rows: Row[]): Promise => +const fp = (rows: PaneFingerprintRow[]): Promise => buildPaneProcessFingerprint(rows, SHELL, { platform: 'darwin' }) describe('buildPaneProcessFingerprint', () => { diff --git a/src/main/providers/pty-process-inspection.ts b/src/main/providers/pty-process-inspection.ts index 2c316ae05f9..d72594787cd 100644 --- a/src/main/providers/pty-process-inspection.ts +++ b/src/main/providers/pty-process-inspection.ts @@ -6,13 +6,11 @@ import { type TerminalProcessInspection } from '../../shared/terminal-process-inspection' -export type PtyProcessInspection = TerminalProcessInspection - type CompletionSensitivePtyProvider = IPtyProvider & { inspectProcess?: ( id: string, options?: PtyProcessInspectionOptions - ) => Promise + ) => Promise } /** @@ -32,7 +30,7 @@ export async function inspectPtyProviderProcess( provider: IPtyProvider, ptyId: string, options?: PtyProcessInspectionOptions -): Promise { +): Promise { if (provider.hasPty?.(ptyId) === false) { throw new Error('terminal_gone') } @@ -51,7 +49,7 @@ export async function inspectPtyProviderProcessForRenderer( provider: IPtyProvider, ptyId: string, options?: PtyProcessInspectionOptions -): Promise { +): Promise { try { return await inspectPtyProviderProcess(provider, ptyId, options) } catch (error) { diff --git a/src/main/providers/ssh-pty-provider-rpc-operations.ts b/src/main/providers/ssh-pty-provider-rpc-operations.ts index bcd847de27b..14003928006 100644 --- a/src/main/providers/ssh-pty-provider-rpc-operations.ts +++ b/src/main/providers/ssh-pty-provider-rpc-operations.ts @@ -1,5 +1,5 @@ import type { SshChannelMultiplexer } from '../ssh/ssh-channel-multiplexer' -import type { PtyProcessInspection } from './pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' import { writeToSshPty, writeToSshPtyWithSettlement } from './ssh-pty-write' import type { WriteSettlement } from '../../shared/pty-write-settlement' @@ -58,7 +58,7 @@ export function createSshPtyProviderRpcOperations({ mux, toRelayPtyId }: SshPtyP inspectProcess: async ( id: string, options?: { expectedIncarnationId?: string; scanChildProcesses?: boolean } - ): Promise => { + ): Promise => { return (await mux.request('pty.inspectProcess', { id: toRelayPtyId(id), ...(options?.expectedIncarnationId @@ -66,7 +66,7 @@ export function createSshPtyProviderRpcOperations({ mux, toRelayPtyId }: SshPtyP : {}), // Additive request member: an older relay ignores it and answers as it always did. ...(options?.scanChildProcesses === true ? { scanChildProcesses: true } : {}) - })) as PtyProcessInspection + })) as TerminalProcessInspection }, serialize: async (ids: string[]): Promise => { const result = await mux.request('pty.serialize', { diff --git a/src/main/providers/ssh-pty-provider.ts b/src/main/providers/ssh-pty-provider.ts index 76b5d9f85e4..4d0457a466a 100644 --- a/src/main/providers/ssh-pty-provider.ts +++ b/src/main/providers/ssh-pty-provider.ts @@ -22,7 +22,7 @@ import { import { buildSshPtySpawnRequest } from './ssh-pty-spawn-request' import { SshPtySpawnExitRaceTracker } from './ssh-pty-spawn-exit-race' import { SshAgentSessionCapabilities } from './ssh-agent-session-capabilities' -import type { PtyProcessInspection } from './pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' import { spawnWithTerminalRuntimeRepair, type TerminalRepairHook } from './ssh-pty-spawn-repair' import { createSshPtyProviderRpcOperations } from './ssh-pty-provider-rpc-operations' @@ -65,7 +65,7 @@ export class SshPtyProvider implements IPtyProvider { inspectProcess = ( id: string, options?: { expectedIncarnationId?: string; scanChildProcesses?: boolean } - ): Promise => this.rpcOperations.inspectProcess(id, options) + ): Promise => this.rpcOperations.inspectProcess(id, options) serialize = (ids: string[]): Promise => this.rpcOperations.serialize(ids) revive = (state: string): Promise => this.rpcOperations.revive(state) getDefaultShell = (): Promise => this.rpcOperations.getDefaultShell() diff --git a/src/main/rate-limits/claude-fetcher.ts b/src/main/rate-limits/claude-fetcher.ts index 42427766a26..d0866b0adca 100644 --- a/src/main/rate-limits/claude-fetcher.ts +++ b/src/main/rate-limits/claude-fetcher.ts @@ -7,19 +7,15 @@ import type { ClaudeRateLimitFetchOptions } from './claude-usage-fetch-options' -export type FetchClaudeRateLimitsOptions = ClaudeRateLimitFetchOptions -export type FetchManagedAccountUsageOptions = ClaudeManagedAccountUsageOptions -export type InactiveClaudeAccountInfo = InactiveClaudeAccount - export async function fetchClaudeRateLimits( - options?: FetchClaudeRateLimitsOptions + options?: ClaudeRateLimitFetchOptions ): Promise { return fetchActiveClaudeRateLimits(options) } export async function fetchManagedAccountUsage( - account: InactiveClaudeAccountInfo, - options: FetchManagedAccountUsageOptions = {} + account: InactiveClaudeAccount, + options: ClaudeManagedAccountUsageOptions = {} ): Promise { return fetchInactiveClaudeAccountUsage(account, options) } diff --git a/src/main/rate-limits/codex-fetcher.ts b/src/main/rate-limits/codex-fetcher.ts index da2904d524e..d460e183224 100644 --- a/src/main/rate-limits/codex-fetcher.ts +++ b/src/main/rate-limits/codex-fetcher.ts @@ -44,8 +44,6 @@ const WSL_RPC_TIMEOUT_MS = 25_000 const RPC_INIT_TIMEOUT_MS = 30_000 const WSL_RPC_INIT_TIMEOUT_MS = 40_000 -export type FetchCodexRateLimitsOptions = CodexRateLimitFetchOptions - function buildWslCodexCommand( codexHomePath: string, args: string[], @@ -185,7 +183,7 @@ async function fetchWslBackend( } export async function fetchCodexRateLimits( - options?: FetchCodexRateLimitsOptions + options?: CodexRateLimitFetchOptions ): Promise { if (options?.signal?.aborted) { return abortedCodexRateLimitResult() diff --git a/src/main/rate-limits/service/service-configuration.ts b/src/main/rate-limits/service/service-configuration.ts index 655ba9b93d8..fe16459b768 100644 --- a/src/main/rate-limits/service/service-configuration.ts +++ b/src/main/rate-limits/service/service-configuration.ts @@ -12,7 +12,7 @@ import { type MiniMaxRateLimitConfig, type GeminiCliOAuthEnabledResolver, type InactiveCodexAccountInfo, - type InactiveClaudeAccountInfo, + type InactiveClaudeAccount, type RateLimitState, normalizeCodexAccountSelectionTarget, normalizeClaudeAccountSelectionTarget, @@ -56,7 +56,7 @@ export abstract class RateLimitServiceConfiguration extends RateLimitServiceAcco this.networkProxySettingsResolver = resolver } - setInactiveClaudeAccountsResolver(resolver: () => InactiveClaudeAccountInfo[]): void { + setInactiveClaudeAccountsResolver(resolver: () => InactiveClaudeAccount[]): void { this.inactiveClaudeAccountsResolver = resolver this.inactiveClaudeAccountsGeneration += 1 } diff --git a/src/main/rate-limits/service/service-state.ts b/src/main/rate-limits/service/service-state.ts index c7e9aa4751a..e383f75acb9 100644 --- a/src/main/rate-limits/service/service-state.ts +++ b/src/main/rate-limits/service/service-state.ts @@ -16,7 +16,7 @@ import { type GeminiCliOAuthEnabledResolver, type NormalizedCodexAccountSelectionTarget, type NormalizedClaudeAccountSelectionTarget, - type InactiveClaudeAccountInfo, + type InactiveClaudeAccount, type NetworkProxySettings, DEFAULT_POLL_MS } from './service-types' @@ -91,7 +91,7 @@ export abstract class RateLimitServiceState { protected openCodeGoConfigResolver: (() => OpenCodeGoRateLimitConfig) | null = null protected miniMaxConfigResolver: (() => MiniMaxRateLimitConfig) | null = null protected geminiCliOAuthEnabledResolver: GeminiCliOAuthEnabledResolver | null = null - protected inactiveClaudeAccountsResolver: (() => InactiveClaudeAccountInfo[]) | null = null + protected inactiveClaudeAccountsResolver: (() => InactiveClaudeAccount[]) | null = null protected inactiveCodexAccountsResolver: (() => InactiveCodexAccountInfo[]) | null = null protected networkProxySettingsResolver: (() => NetworkProxySettings) | null = null protected inactiveClaudeCache = new Map() diff --git a/src/main/rate-limits/service/service-types.ts b/src/main/rate-limits/service/service-types.ts index 414fa9b8dfd..22d4f7a87ed 100644 --- a/src/main/rate-limits/service/service-types.ts +++ b/src/main/rate-limits/service/service-types.ts @@ -12,7 +12,7 @@ export type { InactiveAccountUsage, RateLimitRuntimeTarget } from '../../../shared/rate-limit-types' -export type { InactiveClaudeAccountInfo } from '../claude-fetcher' +export type { InactiveClaudeAccount } from '../claude-managed-account-credentials' export type { ClaudeStatusLineRateLimits } from '../../../shared/claude-statusline-rate-limits' export type { NetworkProxySettings } from '../../../shared/network-proxy' export type { ClaudeRuntimeAuthPreparation } from '../../claude-accounts/runtime-auth-service' diff --git a/src/main/runtime/agent-session-acquisition-failure-settlement.ts b/src/main/runtime/agent-session-acquisition-failure-settlement.ts index c790a149f31..a70a2e5ff03 100644 --- a/src/main/runtime/agent-session-acquisition-failure-settlement.ts +++ b/src/main/runtime/agent-session-acquisition-failure-settlement.ts @@ -37,9 +37,6 @@ export type AgentSessionFailedAcquisitionSettlement = { now: number } -export type AgentSessionFailedPostAcquisitionAttachmentSettlement = - AgentSessionFailedAcquisitionSettlement - /** Liveness invariant: a settled attach never leaves its reservation in new-owner-proving. */ export function settleFailedAgentSessionAcquisition( state: AgentSessionStoreState, @@ -62,7 +59,7 @@ export function settleFailedAgentSessionAcquisition( /** A proved native owner still is not publishable until its journal attaches. */ export function settleFailedAgentSessionPostAcquisitionAttachment( state: AgentSessionStoreState, - args: AgentSessionFailedPostAcquisitionAttachmentSettlement + args: AgentSessionFailedAcquisitionSettlement ): AgentSessionRecord { const operation = state.operations.get(agentSessionOperationKey(args.callerKey, args.operationId)) if (!operation || operation.outcome.status !== 'pending') { diff --git a/src/main/runtime/agent-session-record-store.ts b/src/main/runtime/agent-session-record-store.ts index e3655b273fc..88e01c76eaf 100644 --- a/src/main/runtime/agent-session-record-store.ts +++ b/src/main/runtime/agent-session-record-store.ts @@ -36,8 +36,7 @@ import { import { settleFailedAgentSessionAcquisition, settleFailedAgentSessionPostAcquisitionAttachment, - type AgentSessionFailedAcquisitionSettlement, - type AgentSessionFailedPostAcquisitionAttachmentSettlement + type AgentSessionFailedAcquisitionSettlement } from './agent-session-acquisition-failure-settlement' import { renewAgentSessionLeases, @@ -219,9 +218,8 @@ export class AgentSessionRecordStore { settleFailedAcquisition = (args: AgentSessionFailedAcquisitionSettlement) => this.transact(() => settleFailedAgentSessionAcquisition(this.state, args)) - settleFailedPostAcquisitionAttachment = ( - args: AgentSessionFailedPostAcquisitionAttachmentSettlement - ) => this.transact(() => settleFailedAgentSessionPostAcquisitionAttachment(this.state, args)) + settleFailedPostAcquisitionAttachment = (args: AgentSessionFailedAcquisitionSettlement) => + this.transact(() => settleFailedAgentSessionPostAcquisitionAttachment(this.state, args)) async renewLease(args: AgentSessionLeaseRenewal): Promise { const [renewed] = await this.renewLeases([args]) diff --git a/src/main/runtime/mobile-session-tabs-notify-coalescer.ts b/src/main/runtime/mobile-session-tabs-notify-coalescer.ts index 4bb66b41b2b..a208161796f 100644 --- a/src/main/runtime/mobile-session-tabs-notify-coalescer.ts +++ b/src/main/runtime/mobile-session-tabs-notify-coalescer.ts @@ -19,9 +19,6 @@ const SESSION_TABS_FLUSH_MS = 50 // keeps spinning never starves the emit indefinitely. const SESSION_TABS_MAX_WAIT_MS = 250 -/** Keys are worktree ids; `emit` reads the latest snapshot for the worktree itself. */ -export type MobileSessionTabsNotifyCoalescer = KeyedTrailingEdgeCoalescer - /** * Coalesces per-worktree session.tabs notifications on a short trailing-edge * window. `emit` is invoked once per settled worktree and is expected to read @@ -30,7 +27,7 @@ export type MobileSessionTabsNotifyCoalescer = KeyedTrailingEdgeCoalescer */ export function createMobileSessionTabsNotifyCoalescer( emit: (worktreeId: string) => void -): MobileSessionTabsNotifyCoalescer { +): KeyedTrailingEdgeCoalescer { return createKeyedTrailingEdgeCoalescer(emit, { flushMs: SESSION_TABS_FLUSH_MS, maxWaitMs: SESSION_TABS_MAX_WAIT_MS diff --git a/src/main/runtime/orca-runtime-attach-remote-terminal-source-range-consumer.ts b/src/main/runtime/orca-runtime-attach-remote-terminal-source-range-consumer.ts index 68ebabb8857..3fd2769bb77 100644 --- a/src/main/runtime/orca-runtime-attach-remote-terminal-source-range-consumer.ts +++ b/src/main/runtime/orca-runtime-attach-remote-terminal-source-range-consumer.ts @@ -1,4 +1,5 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. +import type { RuntimeTerminalDriverState } from '../../shared/runtime-types' import { OrcaRuntimeWithRecordAgentPromptLifecycleState } from './orca-runtime-record-agent-prompt-lifecycle-state' import type { RemoteTerminalSourceRangeReplacementPublication, @@ -6,7 +7,6 @@ import type { RemoteTerminalSourceRangeStreamIdentity } from './remote-terminal-source-range-consumer' import type { TerminalOutputSourceRange } from '../../shared/terminal-output-source-range' -import type { DriverState } from './orca-runtime-core' import { addListenerToMap } from './orca-runtime-core' import { notifyRuntimeListeners } from './runtime-async-boundaries' import type { RuntimeTerminalBufferSnapshot } from './runtime-terminal-state-records' @@ -136,7 +136,10 @@ export class OrcaRuntimeWithAttachRemoteTerminalSourceRangeConsumer extends Orca return addListenerToMap(this.fitOverrideListeners, ptyId, listener) } - subscribeToDriverChanges(ptyId: string, listener: (driver: DriverState) => void): () => void { + subscribeToDriverChanges( + ptyId: string, + listener: (driver: RuntimeTerminalDriverState) => void + ): () => void { return this.terminalDrivers.subscribe(ptyId, listener) } diff --git a/src/main/runtime/orca-runtime-core.ts b/src/main/runtime/orca-runtime-core.ts index 8214f750676..eb897173cb1 100644 --- a/src/main/runtime/orca-runtime-core.ts +++ b/src/main/runtime/orca-runtime-core.ts @@ -5,10 +5,7 @@ import type { ResolvedWorktree } from './runtime-worktree-path-identity' import type { RuntimeLeafRecord } from './runtime-terminal-state-records' import { isCursorAgentTitle } from '../../shared/agent-detection' import { isAbsolute, relative, resolve } from 'node:path' -import type { - RuntimeTerminalDriverState, - RuntimeTerminalPresentation -} from '../../shared/runtime-types' +import type { RuntimeTerminalPresentation } from '../../shared/runtime-types' import type { RuntimeEdgeCommandSurface } from './runtime-edge-command-controller' import type { RuntimeLinearCommandSurface } from './runtime-linear-command-surface' import type { RuntimeFileCommandSurface } from './runtime-file-command-surface' @@ -293,17 +290,6 @@ export type RuntimeWorktreeLifecycleEvent = | { kind: 'created'; worktreeId: string; path: string; branch: string } | { kind: 'removed'; worktreeId: string; path: string } -// Why: presence-based driver state for the mobile-presence lock. Exactly one -// driver per PTY at any moment. See docs/mobile-presence-lock.md. -// - `idle`: no mobile subscribers; desktop input flows freely -// - `desktop`: at least one mobile client subscribed but desktop reclaimed -// (or all mobile clients are passive `desktop`-mode watchers); desktop -// input flows freely -// - `mobile{clientId}`: a mobile client is the active driver; desktop -// input/resize are dropped server-side and the lock banner is mounted. -// `clientId` is the most recent mobile actor for this PTY. -export type DriverState = RuntimeTerminalDriverState - // Why: per-PTY layout target — what the PTY *should* be at right now. // `desktop` ⇒ runs at the desktop renderer's pane geometry; mobile passive // watchers (mode='desktop') still receive scrollback. `phone` ⇒ runs at diff --git a/src/main/runtime/orca-runtime-emulator.ts b/src/main/runtime/orca-runtime-emulator.ts index 2e08614b8df..11f62fd5db4 100644 --- a/src/main/runtime/orca-runtime-emulator.ts +++ b/src/main/runtime/orca-runtime-emulator.ts @@ -7,7 +7,7 @@ import { } from '../emulator/emulator-availability' import { resolveDefaultAttachDevice } from '../emulator/emulator-default-attach-device' import { setConfiguredAndroidSdkPath } from '../emulator/android/android-sdk-host-discovery' -import type { EmulatorGesturePoint } from '../emulator/emulator-gesture-sender' +import type { ServeSimTouchFrame } from '../../shared/emulator-touch-frame' import type { EmulatorSessionInfo } from '../emulator/emulator-types' import type { SimulatorDevice } from '../emulator/simctl-simulator-devices' import type { EmulatorDevice } from '../emulator/backends/emulator-backend' @@ -57,7 +57,7 @@ export class RuntimeEmulatorCommands { } async emulatorGesture(params: { - points: EmulatorGesturePoint[] + points: ServeSimTouchFrame[] device?: string emulator?: string worktree?: string diff --git a/src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts b/src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts index d0425cdc1f9..938224a0bd6 100644 --- a/src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts +++ b/src/main/runtime/orca-runtime-has-exact-persisted-terminal-surface-identity.ts @@ -1,4 +1,5 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. +import type { PtyControllerInventory } from './runtime-pty-controller-contract' import { OrcaRuntimeWithAutomationOperations } from './orca-runtime-automation-operations' import { resolveTerminalSessionWorktreeId, @@ -9,7 +10,6 @@ import type { LegacyWorkerTerminalRecoveryPlan } from './orchestration/orchestra import { retireTerminalSurfacesFromSnapshot } from './mobile-session-terminal-retirement' import type { LegacyWorkerRecoveryCandidate, - LegacyWorkerRecoveryInventory, TerminalWorkspaceLaunchScope } from './runtime-legacy-worker-terminal-recovery-types' import { getLatestPtyTitle } from './runtime-worktree-status-projection' @@ -126,7 +126,7 @@ export class OrcaRuntimeWithHasExactPersistedTerminalSurfaceIdentity extends Orc protected async adoptLegacyWorkerTerminal( candidate: LegacyWorkerRecoveryCandidate, workspace: TerminalWorkspaceLaunchScope, - inventory: LegacyWorkerRecoveryInventory, + inventory: PtyControllerInventory, activation: { activeTabId?: string; activeGroupId?: string } ): Promise { await this.adoptTerminalOrphansFromInventoryUnderMutation( diff --git a/src/main/runtime/orca-runtime-has-recent-terminal-output-path.ts b/src/main/runtime/orca-runtime-has-recent-terminal-output-path.ts index 8f277f71cd4..2067ae2cfd9 100644 --- a/src/main/runtime/orca-runtime-has-recent-terminal-output-path.ts +++ b/src/main/runtime/orca-runtime-has-recent-terminal-output-path.ts @@ -1,10 +1,11 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. +import type { RuntimeTerminalDriverState } from '../../shared/runtime-types' import { OrcaRuntimeWithGetOrchestrationDispatchAuthority } from './orca-runtime-get-orchestration-dispatch-authority' import { recentTerminalOutputIncludesPath, recentTerminalPathCandidatesIncludePath } from './terminal-output-path-candidates' -import type { ApplyLayoutResult, DriverState } from './orca-runtime-core' +import type { ApplyLayoutResult } from './orca-runtime-core' import { clampTerminalViewport } from './terminal-viewport' export class OrcaRuntimeWithHasRecentTerminalOutputPath extends OrcaRuntimeWithGetOrchestrationDispatchAuthority { @@ -162,7 +163,7 @@ export class OrcaRuntimeWithHasRecentTerminalOutputPath extends OrcaRuntimeWithG return result } - getAllTerminalDrivers(): Map { + getAllTerminalDrivers(): Map { return this.terminalDrivers.getAll() } diff --git a/src/main/runtime/orca-runtime-mark-pty-liveness-unverifiable.ts b/src/main/runtime/orca-runtime-mark-pty-liveness-unverifiable.ts index e86e10e41c3..743e46dac2f 100644 --- a/src/main/runtime/orca-runtime-mark-pty-liveness-unverifiable.ts +++ b/src/main/runtime/orca-runtime-mark-pty-liveness-unverifiable.ts @@ -1,7 +1,7 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. +import type { RuntimeTerminalDriverState } from '../../shared/runtime-types' import { OrcaRuntimeWithOnPtyExit } from './orca-runtime-on-pty-exit' import type { PtyLivenessVerdict } from '../../shared/pty-liveness-verdict' -import type { DriverState } from './orca-runtime-core' import { clampTerminalViewport } from './terminal-viewport' import { getPtyTerminalState, getTerminalState } from './terminal-wait-results' @@ -147,11 +147,11 @@ export class OrcaRuntimeWithMarkPtyLivenessUnverifiable extends OrcaRuntimeWithO // // See docs/mobile-presence-lock.md. - getDriver(ptyId: string): DriverState { + getDriver(ptyId: string): RuntimeTerminalDriverState { return this.terminalDrivers.get(ptyId) } - protected setDriver(ptyId: string, next: DriverState): void { + protected setDriver(ptyId: string, next: RuntimeTerminalDriverState): void { this.terminalDrivers.set(ptyId, next) } diff --git a/src/main/runtime/orca-runtime-mobile-took-floor.ts b/src/main/runtime/orca-runtime-mobile-took-floor.ts index 2fa2b6f0fd3..1a8d9c1bc4e 100644 --- a/src/main/runtime/orca-runtime-mobile-took-floor.ts +++ b/src/main/runtime/orca-runtime-mobile-took-floor.ts @@ -1,6 +1,7 @@ // @ts-nocheck -- mechanically split from OrcaRuntimeService; behavior is covered by AST equivalence and characterization tests. +import type { RuntimeTerminalDriverState } from '../../shared/runtime-types' import { OrcaRuntimeWithMarkPtyLivenessUnverifiable } from './orca-runtime-mark-pty-liveness-unverifiable' -import type { ApplyLayoutResult, DriverState } from './orca-runtime-core' +import type { ApplyLayoutResult } from './orca-runtime-core' import { clampTerminalViewport } from './terminal-viewport' export class OrcaRuntimeWithMobileTookFloor extends OrcaRuntimeWithMarkPtyLivenessUnverifiable { @@ -12,7 +13,7 @@ export class OrcaRuntimeWithMobileTookFloor extends OrcaRuntimeWithMarkPtyLivene async mobileTookFloor( ptyId: string, clientId: string, - previousFloor?: DriverState, + previousFloor?: RuntimeTerminalDriverState, isCurrent: () => boolean = () => true ): Promise { const inner = this.mobileSubscribers.get(ptyId) diff --git a/src/main/runtime/orca-runtime-restore-structured-agent-session-tabs-once.ts b/src/main/runtime/orca-runtime-restore-structured-agent-session-tabs-once.ts index f138970d5ad..9cae34374a3 100644 --- a/src/main/runtime/orca-runtime-restore-structured-agent-session-tabs-once.ts +++ b/src/main/runtime/orca-runtime-restore-structured-agent-session-tabs-once.ts @@ -24,7 +24,7 @@ import type { TerminalWorkspaceLaunchScope } from './runtime-legacy-worker-termi import { isWindowsAbsolutePathLike } from '../../shared/cross-platform-path' import { isWslUncPath } from '../../shared/wsl-paths' import { parseAppSshPtyId } from '../../shared/ssh-pty-id' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRuntimeWithResolveRecoveredStructuredTuiTranscript { async replaceStructuredAgentSessionTab(replacement: ConversationReplacement): Promise { @@ -183,7 +183,7 @@ export class OrcaRuntimeWithRestoreStructuredAgentSessionTabsOnce extends OrcaRu async inspectTerminalProcess( terminalSelector: string, options?: { expectedIncarnationId?: string; scanChildProcesses?: boolean } - ): Promise { + ): Promise { const leaf = this.resolveLiveLeafForHandle(terminalSelector) if (!leaf?.ptyId || !this.ptyController) { throw new Error('terminal_gone') diff --git a/src/main/runtime/orca-runtime-runtime-id.ts b/src/main/runtime/orca-runtime-runtime-id.ts index 00ccdde9da9..63dff069c35 100644 --- a/src/main/runtime/orca-runtime-runtime-id.ts +++ b/src/main/runtime/orca-runtime-runtime-id.ts @@ -30,7 +30,7 @@ import { ClientSessionTabSelectionStore } from './client-session-tab-selection' import { WorktreeTerminalMutationLock } from './worktree-terminal-mutation-lock' import { RemoteRuntimeTerminalCreateIdempotency } from './remote-runtime-terminal-create-idempotency' import type { PtyIncarnationId } from '../../shared/pty-incarnation' -import type { MobileSessionTabsNotifyCoalescer } from './mobile-session-tabs-notify-coalescer' +import type { KeyedTrailingEdgeCoalescer } from './keyed-trailing-edge-coalescer' import { createMobileSessionTabsNotifyCoalescer } from './mobile-session-tabs-notify-coalescer' import type { MobileSessionTabsAgentStatusHeartbeat } from './mobile-session-tabs-agent-status-heartbeat' import { createMobileSessionTabsAgentStatusHeartbeat } from './mobile-session-tabs-agent-status-heartbeat' @@ -243,7 +243,7 @@ export class OrcaRuntimeWithRuntimeId { // Why: coalesces title/status-driven session.tabs emits so spinner churn // doesn't fan out (and per-client JSON.stringify) a snapshot several times a // second. Emit reads the latest snapshot, so only the freshest version ships. - protected readonly mobileSessionTabsNotifyCoalescer: MobileSessionTabsNotifyCoalescer = + protected readonly mobileSessionTabsNotifyCoalescer: KeyedTrailingEdgeCoalescer = createMobileSessionTabsNotifyCoalescer((worktreeId) => this.flushScheduledMobileSessionTabsChanged(worktreeId) ) diff --git a/src/main/runtime/orca-runtime-test-fixtures.spec.ts b/src/main/runtime/orca-runtime-test-fixtures.spec.ts index c72bde039b9..1fcbfb10955 100644 --- a/src/main/runtime/orca-runtime-test-fixtures.spec.ts +++ b/src/main/runtime/orca-runtime-test-fixtures.spec.ts @@ -24,7 +24,7 @@ import type { } from './orca-runtime-test-mocks.spec' import { InMemoryOrchestrationMessages } from './orca-runtime-test-orchestration-messages.spec' import type { OrchestrationDb } from './orchestration/db' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' type RuntimeService = InstanceType type HeadlessTerminal = InstanceType @@ -464,7 +464,7 @@ function createRuntimeWithSshLease( async function createExplicitAgentStatusHarness(options: { getForegroundProcess: (ptyId: string) => Promise - inspectProcess?: (ptyId: string) => Promise + inspectProcess?: (ptyId: string) => Promise confirmForegroundProcess?: (ptyId: string) => Promise title?: string }): Promise<{ diff --git a/src/main/runtime/orca-runtime-test-mocks/setup.spec.ts b/src/main/runtime/orca-runtime-test-mocks/setup.spec.ts index 664fc95b3ec..3244f43f2e8 100644 --- a/src/main/runtime/orca-runtime-test-mocks/setup.spec.ts +++ b/src/main/runtime/orca-runtime-test-mocks/setup.spec.ts @@ -10,13 +10,11 @@ import type { OrcaRuntimeService as OrcaRuntimeServiceConstructor } from '../orc import type { RuntimeMobileSessionTabsResult } from '../../../shared/runtime-types' import { setWorktreeWatcherRemoval } from '../../ipc/worktree-watcher-removal' -type TestMock = Mock - export const ORIGINAL_PLATFORM = process.platform export const ORIGINAL_PLATFORM_DESCRIPTOR = Object.getOwnPropertyDescriptor(process, 'platform') const removeWorktreeLinkedPathsMock: ReturnType = vi.hoisted(() => vi.fn()) -const findExistingWorktreeSymlinkPathsMock: TestMock = vi.hoisted(() => vi.fn()) -const resolveLocalGitUsernameMock: TestMock = vi.hoisted(() => vi.fn(async () => '')) +const findExistingWorktreeSymlinkPathsMock: Mock = vi.hoisted(() => vi.fn()) +const resolveLocalGitUsernameMock: Mock = vi.hoisted(() => vi.fn(async () => '')) vi.mock('../../ipc/worktree-symlinks', () => ({ createWorktreeCopiedPaths: vi.fn(), @@ -85,13 +83,13 @@ const electronMocks = vi.hoisted(() => { } }) -const closeLocalWatcherForWorktreePathMock: TestMock = vi.hoisted(() => vi.fn()) -const closeRemoteWatcherForWorktreePathMock: TestMock = vi.hoisted(() => vi.fn()) -const restoreLocalWatcherAfterFailedRemovalMock: TestMock = vi.hoisted(() => vi.fn()) -const restoreRemoteWatcherAfterFailedRemovalMock: TestMock = vi.hoisted(() => vi.fn()) -const forgetLocalWatcherRemovalSnapshotMock: TestMock = vi.hoisted(() => vi.fn()) -const forgetRemoteWatcherRemovalSnapshotMock: TestMock = vi.hoisted(() => vi.fn()) -const scanLocalRepoWorktreesForResolutionMock: TestMock = vi.hoisted(() => vi.fn()) +const closeLocalWatcherForWorktreePathMock: Mock = vi.hoisted(() => vi.fn()) +const closeRemoteWatcherForWorktreePathMock: Mock = vi.hoisted(() => vi.fn()) +const restoreLocalWatcherAfterFailedRemovalMock: Mock = vi.hoisted(() => vi.fn()) +const restoreRemoteWatcherAfterFailedRemovalMock: Mock = vi.hoisted(() => vi.fn()) +const forgetLocalWatcherRemovalSnapshotMock: Mock = vi.hoisted(() => vi.fn()) +const forgetRemoteWatcherRemovalSnapshotMock: Mock = vi.hoisted(() => vi.fn()) +const scanLocalRepoWorktreesForResolutionMock: Mock = vi.hoisted(() => vi.fn()) vi.mock('electron', () => electronMocks) // Why install the port instead of mocking ../ipc/filesystem-watcher: the runtime calls @@ -205,12 +203,12 @@ const { isMainWorktree: false } ], - addSparseWorktreeMock: vi.fn() as TestMock, - addWorktreeMock: vi.fn() as TestMock, - removeWorktreeMock: vi.fn() as TestMock, - forceDeleteLocalBranchMock: vi.fn() as TestMock, - computeWorktreePathMock: vi.fn() as TestMock, - ensurePathWithinWorkspaceMock: vi.fn() as TestMock, + addSparseWorktreeMock: vi.fn() as Mock, + addWorktreeMock: vi.fn() as Mock, + removeWorktreeMock: vi.fn() as Mock, + forceDeleteLocalBranchMock: vi.fn() as Mock, + computeWorktreePathMock: vi.fn() as Mock, + ensurePathWithinWorkspaceMock: vi.fn() as Mock, sshGitProviders, sshProviderGenerations, getSshGitProviderMock: vi.fn((connectionId: string) => sshGitProviders.get(connectionId)), @@ -229,78 +227,76 @@ const { ) } }), - getActiveMultiplexerMock: vi.fn() as TestMock, - muxRequestMock: vi.fn() as TestMock, - invalidateAuthorizedRootsCacheMock: vi.fn() as TestMock, - prepareLocalWorktreeRootForRepoMock: vi.fn() as TestMock, - createHostedReviewMock: vi.fn() as TestMock, - createStackedHostedReviewMock: vi.fn() as TestMock, - getHostedReviewCreationEligibilityMock: vi.fn() as TestMock, - getHostedReviewForBranchMock: vi.fn() as TestMock, - getPRForBranchMock: vi.fn().mockResolvedValue(null) as TestMock, - getPRForBranchOutcomeMock: vi - .fn() - .mockResolvedValue({ kind: 'no-pr', fetchedAt: 0 }) as TestMock, - getRepoSlugMock: vi.fn().mockResolvedValue(null) as TestMock, - getRepoUpstreamMock: vi.fn().mockResolvedValue(null) as TestMock, - getGitHubWorkItemMock: vi.fn() as TestMock, - getPullRequestPushTargetMock: vi.fn() as TestMock, - getGitHubWorkItemByOwnerRepoMock: vi.fn() as TestMock, - getGitHubWorkItemDetailsMock: vi.fn() as TestMock, - getGitHubPRFileContentsMock: vi.fn() as TestMock, - getGitHubPRChecksMock: vi.fn() as TestMock, - rerunGitHubPRChecksMock: vi.fn() as TestMock, - getGitHubPRCheckDetailsMock: vi.fn() as TestMock, - getGitHubPRCommentsMock: vi.fn() as TestMock, - resolveGitHubReviewThreadMock: vi.fn() as TestMock, - setGitHubPRFileViewedMock: vi.fn() as TestMock, - updateGitHubPRTitleMock: vi.fn() as TestMock, - updateGitHubPRDetailsMock: vi.fn() as TestMock, - mergeGitHubPRMock: vi.fn() as TestMock, - setGitHubPRAutoMergeMock: vi.fn() as TestMock, - updateGitHubPRStateMock: vi.fn() as TestMock, - requestGitHubPRReviewersMock: vi.fn() as TestMock, - removeGitHubPRReviewersMock: vi.fn() as TestMock, - addGitHubPRReviewCommentMock: vi.fn() as TestMock, - addGitHubPRReviewCommentReplyMock: vi.fn() as TestMock, - listGitHubIssuesMock: vi.fn() as TestMock, - listGitHubWorkItemsMock: vi.fn() as TestMock, - countGitHubWorkItemsMock: vi.fn() as TestMock, - createGitHubIssueMock: vi.fn() as TestMock, - updateGitHubIssueMock: vi.fn() as TestMock, - addGitHubIssueCommentMock: vi.fn() as TestMock, - listGitHubLabelsMock: vi.fn() as TestMock, - listGitHubAssignableUsersMock: vi.fn() as TestMock, - applyAgentStatusHooksEnabledMock: vi.fn() as TestMock, - detectInstalledAgentsWithShellPathHydrationMock: vi.fn() as TestMock, - detectRemoteAgentsMock: vi.fn() as TestMock, - markCodexProjectTrustedMock: vi.fn() as TestMock, - markCopilotFolderTrustedMock: vi.fn() as TestMock, - markCursorWorkspaceTrustedMock: vi.fn() as TestMock, - listGitLabMergeRequestsMock: vi.fn() as TestMock, - listGitLabWorkItemsMock: vi.fn() as TestMock, - listGitLabIssuesMock: vi.fn() as TestMock, - listGitLabLabelsMock: vi.fn() as TestMock, - listGitLabTodosMock: vi.fn() as TestMock, - getGitLabProjectRefForRemoteMock: vi.fn() as TestMock, - getGitLabWorkItemByProjectRefMock: vi.fn() as TestMock, - createGitLabIssueMock: vi.fn() as TestMock, - updateGitLabIssueMock: vi.fn() as TestMock, - addGitLabIssueCommentMock: vi.fn() as TestMock, - addGitLabMRCommentMock: vi.fn() as TestMock, - addGitLabMRInlineCommentMock: vi.fn() as TestMock, - resolveGitLabMRDiscussionMock: vi.fn() as TestMock, - getGitLabJobTraceMock: vi.fn() as TestMock, - retryGitLabJobMock: vi.fn() as TestMock, - mergeGitLabMRMock: vi.fn() as TestMock, - closeGitLabMRMock: vi.fn() as TestMock, - reopenGitLabMRMock: vi.fn() as TestMock, - updateGitLabMRMock: vi.fn() as TestMock, - getGlabKnownHostsMock: vi.fn() as TestMock, - getGitLabWorkItemDetailsMock: vi.fn() as TestMock, - updateGitLabMRReviewersMock: vi.fn() as TestMock, - getIssueMock: vi.fn() as TestMock, - deleteWorktreeHistoryDirMock: vi.fn() as TestMock + getActiveMultiplexerMock: vi.fn() as Mock, + muxRequestMock: vi.fn() as Mock, + invalidateAuthorizedRootsCacheMock: vi.fn() as Mock, + prepareLocalWorktreeRootForRepoMock: vi.fn() as Mock, + createHostedReviewMock: vi.fn() as Mock, + createStackedHostedReviewMock: vi.fn() as Mock, + getHostedReviewCreationEligibilityMock: vi.fn() as Mock, + getHostedReviewForBranchMock: vi.fn() as Mock, + getPRForBranchMock: vi.fn().mockResolvedValue(null) as Mock, + getPRForBranchOutcomeMock: vi.fn().mockResolvedValue({ kind: 'no-pr', fetchedAt: 0 }) as Mock, + getRepoSlugMock: vi.fn().mockResolvedValue(null) as Mock, + getRepoUpstreamMock: vi.fn().mockResolvedValue(null) as Mock, + getGitHubWorkItemMock: vi.fn() as Mock, + getPullRequestPushTargetMock: vi.fn() as Mock, + getGitHubWorkItemByOwnerRepoMock: vi.fn() as Mock, + getGitHubWorkItemDetailsMock: vi.fn() as Mock, + getGitHubPRFileContentsMock: vi.fn() as Mock, + getGitHubPRChecksMock: vi.fn() as Mock, + rerunGitHubPRChecksMock: vi.fn() as Mock, + getGitHubPRCheckDetailsMock: vi.fn() as Mock, + getGitHubPRCommentsMock: vi.fn() as Mock, + resolveGitHubReviewThreadMock: vi.fn() as Mock, + setGitHubPRFileViewedMock: vi.fn() as Mock, + updateGitHubPRTitleMock: vi.fn() as Mock, + updateGitHubPRDetailsMock: vi.fn() as Mock, + mergeGitHubPRMock: vi.fn() as Mock, + setGitHubPRAutoMergeMock: vi.fn() as Mock, + updateGitHubPRStateMock: vi.fn() as Mock, + requestGitHubPRReviewersMock: vi.fn() as Mock, + removeGitHubPRReviewersMock: vi.fn() as Mock, + addGitHubPRReviewCommentMock: vi.fn() as Mock, + addGitHubPRReviewCommentReplyMock: vi.fn() as Mock, + listGitHubIssuesMock: vi.fn() as Mock, + listGitHubWorkItemsMock: vi.fn() as Mock, + countGitHubWorkItemsMock: vi.fn() as Mock, + createGitHubIssueMock: vi.fn() as Mock, + updateGitHubIssueMock: vi.fn() as Mock, + addGitHubIssueCommentMock: vi.fn() as Mock, + listGitHubLabelsMock: vi.fn() as Mock, + listGitHubAssignableUsersMock: vi.fn() as Mock, + applyAgentStatusHooksEnabledMock: vi.fn() as Mock, + detectInstalledAgentsWithShellPathHydrationMock: vi.fn() as Mock, + detectRemoteAgentsMock: vi.fn() as Mock, + markCodexProjectTrustedMock: vi.fn() as Mock, + markCopilotFolderTrustedMock: vi.fn() as Mock, + markCursorWorkspaceTrustedMock: vi.fn() as Mock, + listGitLabMergeRequestsMock: vi.fn() as Mock, + listGitLabWorkItemsMock: vi.fn() as Mock, + listGitLabIssuesMock: vi.fn() as Mock, + listGitLabLabelsMock: vi.fn() as Mock, + listGitLabTodosMock: vi.fn() as Mock, + getGitLabProjectRefForRemoteMock: vi.fn() as Mock, + getGitLabWorkItemByProjectRefMock: vi.fn() as Mock, + createGitLabIssueMock: vi.fn() as Mock, + updateGitLabIssueMock: vi.fn() as Mock, + addGitLabIssueCommentMock: vi.fn() as Mock, + addGitLabMRCommentMock: vi.fn() as Mock, + addGitLabMRInlineCommentMock: vi.fn() as Mock, + resolveGitLabMRDiscussionMock: vi.fn() as Mock, + getGitLabJobTraceMock: vi.fn() as Mock, + retryGitLabJobMock: vi.fn() as Mock, + mergeGitLabMRMock: vi.fn() as Mock, + closeGitLabMRMock: vi.fn() as Mock, + reopenGitLabMRMock: vi.fn() as Mock, + updateGitLabMRMock: vi.fn() as Mock, + getGlabKnownHostsMock: vi.fn() as Mock, + getGitLabWorkItemDetailsMock: vi.fn() as Mock, + updateGitLabMRReviewersMock: vi.fn() as Mock, + getIssueMock: vi.fn() as Mock, + deleteWorktreeHistoryDirMock: vi.fn() as Mock } }) diff --git a/src/main/runtime/orca-runtime-test-scenario-builders.spec.ts b/src/main/runtime/orca-runtime-test-scenario-builders.spec.ts index 1f87d901c81..939c47a7742 100644 --- a/src/main/runtime/orca-runtime-test-scenario-builders.spec.ts +++ b/src/main/runtime/orca-runtime-test-scenario-builders.spec.ts @@ -23,22 +23,21 @@ import type { import type { OrchestrationDb } from './orchestration/db' type RuntimeService = InstanceType -type TestMock = Mock type MobileCreateTestNotifier = { - focusTerminal: TestMock - worktreesChanged: TestMock - reposChanged: TestMock - activateWorktree: TestMock - createTerminal: TestMock - revealTerminalSession: TestMock - splitTerminal: TestMock - renameTerminal: TestMock + focusTerminal: Mock + worktreesChanged: Mock + reposChanged: Mock + activateWorktree: Mock + createTerminal: Mock + revealTerminalSession: Mock + splitTerminal: Mock + renameTerminal: Mock closeTerminal: (tabId: string, paneRuntimeId?: number) => void - closeSessionTab: TestMock - sleepWorktree: TestMock - terminalFitOverrideChanged: TestMock - terminalDriverChanged: TestMock + closeSessionTab: Mock + sleepWorktree: Mock + terminalFitOverrideChanged: Mock + terminalDriverChanged: Mock } function attachClientBrowserHost(runtime: RuntimeService) { diff --git a/src/main/runtime/orca-runtime.ts b/src/main/runtime/orca-runtime.ts index f6a1ff5ac19..d359fff3006 100644 --- a/src/main/runtime/orca-runtime.ts +++ b/src/main/runtime/orca-runtime.ts @@ -45,7 +45,6 @@ export { projectTerminalTailLines } from './orca-runtime-terminal-projection' export { resolveWorktreeScanCacheTtlMs } from './runtime-worktree-scan-cache' export type { RuntimeWorktreeLifecycleEvent, - DriverState, PtyLayoutTarget, PtyLayoutState, ApplyLayoutResult, diff --git a/src/main/runtime/orchestration-mailbox-notification-test-harness.ts b/src/main/runtime/orchestration-mailbox-notification-test-harness.ts index 7bd3ae4665e..b611db3691a 100644 --- a/src/main/runtime/orchestration-mailbox-notification-test-harness.ts +++ b/src/main/runtime/orchestration-mailbox-notification-test-harness.ts @@ -32,8 +32,8 @@ export function createDatabase(prefix: string): OrchestrationDb { return new OrchestrationDb(join(directory, 'orchestration.db')) } -export function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +export function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } export function createBoundRun(db: OrchestrationDb, objective: string) { diff --git a/src/main/runtime/orchestration/context-only-dispatch-release.ts b/src/main/runtime/orchestration/context-only-dispatch-release.ts index ea99d78bf7d..327efd41625 100644 --- a/src/main/runtime/orchestration/context-only-dispatch-release.ts +++ b/src/main/runtime/orchestration/context-only-dispatch-release.ts @@ -24,7 +24,7 @@ export function contextOnlyAbandonWarning(result: { } export function releaseContextOnlyDispatch( - db: Database.Database, + db: Database, dispatch: DispatchContextRow, requestedState: 'abandoned' | 'stopped' ): ContextOnlyDispatchReleaseResult { diff --git a/src/main/runtime/orchestration/db-messages.test.ts b/src/main/runtime/orchestration/db-messages.test.ts index 789797d12f0..4769cae17a2 100644 --- a/src/main/runtime/orchestration/db-messages.test.ts +++ b/src/main/runtime/orchestration/db-messages.test.ts @@ -90,7 +90,7 @@ describe('OrchestrationDb', () => { it('creates the undelivered inbox index used by push delivery', () => { const d = createDb() - const sqlite = (d as unknown as { db: Database.Database }).db + const sqlite = (d as unknown as { db: Database }).db const indexes = sqlite .prepare( diff --git a/src/main/runtime/orchestration/db-task-create-readiness.test.ts b/src/main/runtime/orchestration/db-task-create-readiness.test.ts index 4b661829d02..908d4805ff4 100644 --- a/src/main/runtime/orchestration/db-task-create-readiness.test.ts +++ b/src/main/runtime/orchestration/db-task-create-readiness.test.ts @@ -6,7 +6,7 @@ import type Database from '../../sqlite/sync-database' import { OrchestrationDb } from './db' type OrchestrationDbAccess = { - db: Database.Database + db: Database } describe('task creation dependency readiness', () => { diff --git a/src/main/runtime/orchestration/db-task-dispatch-invariant.test.ts b/src/main/runtime/orchestration/db-task-dispatch-invariant.test.ts index 2b81b2f2897..45be75cd813 100644 --- a/src/main/runtime/orchestration/db-task-dispatch-invariant.test.ts +++ b/src/main/runtime/orchestration/db-task-dispatch-invariant.test.ts @@ -497,6 +497,6 @@ function createDatabase(path?: string): DatabaseHarness { return harness } -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } diff --git a/src/main/runtime/orchestration/db-task-dispatch-lifecycle-guards.test.ts b/src/main/runtime/orchestration/db-task-dispatch-lifecycle-guards.test.ts index f4cdcdf63b8..31146109c9b 100644 --- a/src/main/runtime/orchestration/db-task-dispatch-lifecycle-guards.test.ts +++ b/src/main/runtime/orchestration/db-task-dispatch-lifecycle-guards.test.ts @@ -725,6 +725,6 @@ function expectCapability(database: OrchestrationDb, worker: WorkerFixture, vali ).toBe(valid) } -function sqliteFor(database: OrchestrationDb): Database.Database { - return (database as unknown as { db: Database.Database }).db +function sqliteFor(database: OrchestrationDb): Database { + return (database as unknown as { db: Database }).db } diff --git a/src/main/runtime/orchestration/db-task-dispatch-races.test.ts b/src/main/runtime/orchestration/db-task-dispatch-races.test.ts index c671aa4566b..ad0710f69dc 100644 --- a/src/main/runtime/orchestration/db-task-dispatch-races.test.ts +++ b/src/main/runtime/orchestration/db-task-dispatch-races.test.ts @@ -296,6 +296,6 @@ function createDatabase(path?: string): DatabaseHarness { return harness } -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } diff --git a/src/main/runtime/orchestration/db.test.ts b/src/main/runtime/orchestration/db.test.ts index 33af326f34d..a43c69dd45d 100644 --- a/src/main/runtime/orchestration/db.test.ts +++ b/src/main/runtime/orchestration/db.test.ts @@ -16,7 +16,7 @@ function setDispatchTimes( dispatchedAt: string, heartbeatAt: string | null = null ): void { - const sqlite = (d as unknown as { db: Database.Database }).db + const sqlite = (d as unknown as { db: Database }).db sqlite .prepare('UPDATE dispatch_contexts SET dispatched_at = ?, last_heartbeat_at = ? WHERE id = ?') .run(dispatchedAt, heartbeatAt, id) @@ -561,7 +561,7 @@ describe('OrchestrationDb', () => { // Backdate dispatched_at for a, b, d to long ago so the grace doesn't // shield them. c keeps its default (≈now). - const sqlite = (d as unknown as { db: Database.Database }).db + const sqlite = (d as unknown as { db: Database }).db sqlite .prepare( 'UPDATE dispatch_contexts SET dispatched_at = ?, last_heartbeat_at = ? WHERE id = ?' @@ -816,7 +816,7 @@ describe('OrchestrationDb', () => { expect(d.getTask(task.id)?.display_name).toBe('work') // (c) Indexes still attached to messages post-rebuild. - const sqlite = (d as unknown as { db: Database.Database }).db + const sqlite = (d as unknown as { db: Database }).db const indexes = sqlite .prepare( `SELECT name FROM sqlite_master WHERE type = 'index' AND tbl_name = 'messages' AND name NOT LIKE 'sqlite_%'` diff --git a/src/main/runtime/orchestration/db.ts b/src/main/runtime/orchestration/db.ts index 4af72ff07e5..5a7365ba837 100644 --- a/src/main/runtime/orchestration/db.ts +++ b/src/main/runtime/orchestration/db.ts @@ -13,7 +13,6 @@ export type { AttemptArtifactGitEvidence, AttemptCoordinatorAcknowledgment, AttemptFreshness, - AttemptLivenessObservation, AttemptObservationFact, AttemptObservationFactInput, AttemptOutcomeProjection, diff --git a/src/main/runtime/orchestration/db/attempt-observation-types.ts b/src/main/runtime/orchestration/db/attempt-observation-types.ts index e84fd697619..7bfd362d63f 100644 --- a/src/main/runtime/orchestration/db/attempt-observation-types.ts +++ b/src/main/runtime/orchestration/db/attempt-observation-types.ts @@ -36,8 +36,6 @@ export type AttemptCoordinatorAcknowledgment = { reportId?: string } -export type AttemptLivenessObservation = PtyLivenessVerdict - export type AttemptAdditiveOutcomeFact = { outcome: 'outcome_unknown' | 'finished_unverified' reason: string @@ -48,7 +46,7 @@ export type AttemptObservationPayloadByFacet = { artifact_git: AttemptArtifactGitEvidence worker_report: AttemptWorkerReport coordinator_ack: AttemptCoordinatorAcknowledgment - liveness: AttemptLivenessObservation + liveness: PtyLivenessVerdict outcome: AttemptAdditiveOutcomeFact } @@ -104,6 +102,6 @@ export type AttemptOutcomeProjection = { artifactGit: AttemptArtifactGitEvidence | null workerReport: AttemptWorkerReport | null coordinatorAcknowledgment: AttemptCoordinatorAcknowledgment | null - liveness: AttemptLivenessObservation & { freshness: AttemptFreshness } + liveness: PtyLivenessVerdict & { freshness: AttemptFreshness } } import type { PtyLivenessVerdict } from '../../../../shared/pty-liveness-verdict' diff --git a/src/main/runtime/orchestration/db/attempt-outcome-projection.ts b/src/main/runtime/orchestration/db/attempt-outcome-projection.ts index 787c616dd92..45d22d354c5 100644 --- a/src/main/runtime/orchestration/db/attempt-outcome-projection.ts +++ b/src/main/runtime/orchestration/db/attempt-outcome-projection.ts @@ -1,6 +1,6 @@ +import type { PtyLivenessVerdict } from '../../../../shared/pty-liveness-verdict' import type { AttemptFreshness, - AttemptLivenessObservation, AttemptObservationFact, AttemptObservationFacet, AttemptOutcomeProjection, @@ -59,12 +59,12 @@ function projectLiveness( fact: AttemptObservationFact | undefined, clock: { execution?: number; home: number }, freshAfterMs: number -): AttemptLivenessObservation & { freshness: AttemptFreshness } { +): PtyLivenessVerdict & { freshness: AttemptFreshness } { const freshness = projectFreshness(fact, clock, freshAfterMs) if (!fact) { return { status: 'unverifiable', reason: 'never observed', freshness } } - const observed = fact.payload as AttemptLivenessObservation + const observed = fact.payload as PtyLivenessVerdict if (observed.status === 'exited') { return { status: 'exited', freshness } } diff --git a/src/main/runtime/orchestration/db/dispatch-row-writer.ts b/src/main/runtime/orchestration/db/dispatch-row-writer.ts index 84862ee343d..b8f4ebe5be3 100644 --- a/src/main/runtime/orchestration/db/dispatch-row-writer.ts +++ b/src/main/runtime/orchestration/db/dispatch-row-writer.ts @@ -63,7 +63,7 @@ function assertStampedDepth(depth: number): void { /** Adopts an existing agent terminal, claiming a ready task atomically. */ export function claimDispatchContextRow( - db: Database.Database, + db: Database, params: { id: string contractVersion: number @@ -106,7 +106,7 @@ export function claimDispatchContextRow( /** Supervised `worker-start`, including every retry and the federated home side. */ export function insertStartingDispatchContextRow( - db: Database.Database, + db: Database, params: { id: string runId: string @@ -137,7 +137,7 @@ export function insertStartingDispatchContextRow( /** The worker host's record of a live worker driven by a remote Run home. */ export function insertRemoteDispatchAttachmentRow( - db: Database.Database, + db: Database, params: { dispatchId: string runId: string diff --git a/src/main/runtime/orchestration/db/federation/federated-stub-home-run-backfill.ts b/src/main/runtime/orchestration/db/federation/federated-stub-home-run-backfill.ts index 47c596e260d..c0ea3e07c1a 100644 --- a/src/main/runtime/orchestration/db/federation/federated-stub-home-run-backfill.ts +++ b/src/main/runtime/orchestration/db/federation/federated-stub-home-run-backfill.ts @@ -3,7 +3,7 @@ import { FEDERATED_STUB_HOME_RUN_ID_PREFIX } from '../contract-constants' // Why: a rolled-back v1.4.198 host inserts attachments with home_run_id='' after user_version is // already 40, so this idempotent repair runs on every open, not only inside the v40 migration. -export function backfillFederatedStubHomeRuns(db: Database.Database): void { +export function backfillFederatedStubHomeRuns(db: Database): void { db.exec(` INSERT OR IGNORE INTO runs (id, objective, home_database, consumer_generation, legacy) SELECT '${FEDERATED_STUB_HOME_RUN_ID_PREFIX}' || dispatch_id, diff --git a/src/main/runtime/orchestration/db/lifecycle-transition.ts b/src/main/runtime/orchestration/db/lifecycle-transition.ts index 6fcc40f1913..c8f572a7c8e 100644 --- a/src/main/runtime/orchestration/db/lifecycle-transition.ts +++ b/src/main/runtime/orchestration/db/lifecycle-transition.ts @@ -17,7 +17,7 @@ type LifecycleWriteTransaction = { } export function beginLifecycleWriteTransaction( - db: Database.Database, + db: Database, savepoint: string ): LifecycleWriteTransaction { if (!/^[a-z][a-z0-9_]*$/.test(savepoint)) { @@ -29,14 +29,14 @@ export function beginLifecycleWriteTransaction( } export function commitLifecycleWriteTransaction( - db: Database.Database, + db: Database, transaction: LifecycleWriteTransaction ): void { db.exec(transaction.savepoint ? `RELEASE ${transaction.savepoint}` : 'COMMIT') } export function rollbackLifecycleWriteTransaction( - db: Database.Database, + db: Database, transaction: LifecycleWriteTransaction ): void { if (transaction.savepoint) { @@ -126,7 +126,7 @@ export function transitionLifecycle( /** DB-shaped variant used by low-level writers and tests. */ export function transitionLifecycleWithDb( - db: Database.Database, + db: Database, params: LifecycleTransitionParams ): { changed: boolean } { const entity = ENTITY_TABLE[params.entity] diff --git a/src/main/runtime/orchestration/db/lifecycle-write-transaction-runner.ts b/src/main/runtime/orchestration/db/lifecycle-write-transaction-runner.ts index 3f9211f1d0a..33635c40d42 100644 --- a/src/main/runtime/orchestration/db/lifecycle-write-transaction-runner.ts +++ b/src/main/runtime/orchestration/db/lifecycle-write-transaction-runner.ts @@ -6,7 +6,7 @@ import { } from './lifecycle-transition' export function runLifecycleWriteTransaction( - db: Database.Database, + db: Database, savepoint: string, operation: () => T ): T { diff --git a/src/main/runtime/orchestration/db/orchestration-db.ts b/src/main/runtime/orchestration/db/orchestration-db.ts index 1ce52e96c4a..1005f296911 100644 --- a/src/main/runtime/orchestration/db/orchestration-db.ts +++ b/src/main/runtime/orchestration/db/orchestration-db.ts @@ -11,7 +11,7 @@ import { createTables } from './schema/create-tables' import { migrate } from './schema/migrate' class OrchestrationDbCore { - db: Database.Database + db: Database // Why: the orchestration DB is created lazily for ALL users, but only the // small minority who dispatch work ever have dispatch_contexts rows. The diff --git a/src/main/runtime/orchestration/db/tasks/task-store.ts b/src/main/runtime/orchestration/db/tasks/task-store.ts index af43dc2be12..5faba13b5b6 100644 --- a/src/main/runtime/orchestration/db/tasks/task-store.ts +++ b/src/main/runtime/orchestration/db/tasks/task-store.ts @@ -1,4 +1,4 @@ -import type Database from '../../../../sqlite/sync-database' +import type { SQLInputValue } from 'node:sqlite' import type { TaskStatus, TaskRow } from '../../types' import { buildOrchestrationTaskDisplayMetadata } from '../../../../../shared/orchestration-task-display' import { generateId } from '../generated-id' @@ -134,7 +134,7 @@ export function listTasks( filter?: { status?: TaskStatus; ready?: boolean; runId?: string } ): TaskRow[] { const runWhere = filter?.runId ? 'run_id = ? AND ' : '' - const runParams: Database.BindValue[] = filter?.runId ? [filter.runId] : [] + const runParams: SQLInputValue[] = filter?.runId ? [filter.runId] : [] if (filter?.ready) { return this.db .prepare( @@ -172,7 +172,7 @@ export function listTasksWithDispatch( dispatch_id: string | null })[] { const whereClauses: string[] = [] - const params: Database.BindValue[] = [] + const params: SQLInputValue[] = [] if (filter?.runId) { whereClauses.push('t.run_id = ?') params.push(filter.runId) diff --git a/src/main/runtime/orchestration/dispatch-failure-idempotency.test.ts b/src/main/runtime/orchestration/dispatch-failure-idempotency.test.ts index 5704db1490e..e38a70744b4 100644 --- a/src/main/runtime/orchestration/dispatch-failure-idempotency.test.ts +++ b/src/main/runtime/orchestration/dispatch-failure-idempotency.test.ts @@ -32,7 +32,7 @@ describe('dispatch failure idempotency', () => { it('rolls back the dispatch when the task update fails', () => { const db = new OrchestrationDb(':memory:') - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const task = db.createTask({ runId: 'run_legacy_local', spec: 'work' }) const dispatch = createRootDispatch(db, task.id, 'term_worker') sqlite.exec(` diff --git a/src/main/runtime/orchestration/federation-acknowledgment-integrity.test.ts b/src/main/runtime/orchestration/federation-acknowledgment-integrity.test.ts index e5c452f91a1..a39e2f2a31b 100644 --- a/src/main/runtime/orchestration/federation-acknowledgment-integrity.test.ts +++ b/src/main/runtime/orchestration/federation-acknowledgment-integrity.test.ts @@ -132,7 +132,7 @@ describe('federation acknowledgment integrity', () => { it('accepts an identical answer that wins between classification and the guarded update', () => { const current = createReadyAttachment(3) enqueueQuestion(current.db, current.dispatchId, 'question_race') - const sqlite = (current.db as unknown as { db: Database.Database }).db + const sqlite = (current.db as unknown as { db: Database }).db const originalPrepare = sqlite.prepare.bind(sqlite) let injected = false const prepare = vi.spyOn(sqlite, 'prepare').mockImplementation((sql) => { diff --git a/src/main/runtime/orchestration/federation-acknowledgment-migration.test.ts b/src/main/runtime/orchestration/federation-acknowledgment-migration.test.ts index 9791dbde9b3..49f439a6dca 100644 --- a/src/main/runtime/orchestration/federation-acknowledgment-migration.test.ts +++ b/src/main/runtime/orchestration/federation-acknowledgment-migration.test.ts @@ -42,7 +42,7 @@ describe('federation acknowledgment migration', () => { oldDb.close() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect(sqlite.pragma('user_version', { simple: true })).toBe(SCHEMA_VERSION) expect(db.getFederatedDispatch('ctx_migrated')).toMatchObject({ diff --git a/src/main/runtime/orchestration/message-batch-atomicity.test.ts b/src/main/runtime/orchestration/message-batch-atomicity.test.ts index fda83fad9a9..a08a5fe238a 100644 --- a/src/main/runtime/orchestration/message-batch-atomicity.test.ts +++ b/src/main/runtime/orchestration/message-batch-atomicity.test.ts @@ -13,7 +13,7 @@ const messageIds = Array.from( (_, index) => `m${index.toString().padStart(3, '0')}` ) -function seedMessages(sqlite: Database.Database): void { +function seedMessages(sqlite: Database): void { sqlite.exec(` WITH RECURSIVE ids(value) AS ( VALUES(0) @@ -24,7 +24,7 @@ function seedMessages(sqlite: Database.Database): void { `) } -function rejectLastMessageUpdate(sqlite: Database.Database): void { +function rejectLastMessageUpdate(sqlite: Database): void { sqlite.exec(` CREATE TRIGGER reject_last_message_update BEFORE UPDATE ON messages WHEN OLD.id = 'm500' @@ -61,7 +61,7 @@ describe('message batch atomicity', () => { } ])('rolls back $method when a later batch fails', ({ method, setup, changedCountSql }) => { db = new OrchestrationDb(':memory:') - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db seedMessages(sqlite) if (setup) { sqlite.exec(setup) @@ -76,7 +76,7 @@ describe('message batch atomicity', () => { it('preserves an outer transaction when an inner batch rolls back', () => { db = new OrchestrationDb(':memory:') - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db seedMessages(sqlite) rejectLastMessageUpdate(sqlite) sqlite.exec('BEGIN IMMEDIATE') @@ -94,7 +94,7 @@ describe('message batch atomicity', () => { it('preserves an outer transaction when a message insert batch rolls back', () => { db = new OrchestrationDb(':memory:') - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite.exec(` CREATE TRIGGER reject_second_message_insert BEFORE INSERT ON messages WHEN NEW.id = 'inner_second' @@ -135,7 +135,7 @@ describe('message batch atomicity', () => { it('preserves an outer transaction when a worker_done commit rolls back', () => { db = new OrchestrationDb(':memory:') - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite.exec(` BEGIN IMMEDIATE; INSERT INTO messages (id, from_handle, to_handle, subject) diff --git a/src/main/runtime/orchestration/mutation-receipt-capacity.test.ts b/src/main/runtime/orchestration/mutation-receipt-capacity.test.ts index d45cc9f434e..6c8927837e4 100644 --- a/src/main/runtime/orchestration/mutation-receipt-capacity.test.ts +++ b/src/main/runtime/orchestration/mutation-receipt-capacity.test.ts @@ -7,15 +7,11 @@ import { OrchestrationDb } from './db' import { MUTATION_RECEIPT_MAX_ROWS } from './mutation-receipt-capacity' import { SCHEMA_VERSION } from './db/contract-constants' -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } -function insertReceipts( - sqlite: Database.Database, - count: number, - state: 'pending' | 'completed' -): void { +function insertReceipts(sqlite: Database, count: number, state: 'pending' | 'completed'): void { sqlite .prepare( `WITH RECURSIVE receipt_numbers(value) AS ( diff --git a/src/main/runtime/orchestration/mutation-receipt-capacity.ts b/src/main/runtime/orchestration/mutation-receipt-capacity.ts index 2ba735adad0..81487803377 100644 --- a/src/main/runtime/orchestration/mutation-receipt-capacity.ts +++ b/src/main/runtime/orchestration/mutation-receipt-capacity.ts @@ -5,7 +5,7 @@ export const MUTATION_RECEIPT_MAX_ROWS = 10_000 const MUTATION_RECEIPT_MAX_AGE_DAYS = 30 const MUTATION_RECEIPT_PRUNE_BATCH_SIZE = 64 -export function migrateMutationReceiptCapacity(db: Database.Database): void { +export function migrateMutationReceiptCapacity(db: Database): void { // Why: database triggers keep the count exact for concurrent connections and older binaries. db.exec(` CREATE INDEX IF NOT EXISTS idx_mutation_receipts_completed_updated @@ -40,7 +40,7 @@ export function migrateMutationReceiptCapacity(db: Database.Database): void { `) } -function receiptCount(db: Database.Database): number { +function receiptCount(db: Database): number { const row = db .prepare('SELECT receipt_count FROM mutation_receipt_ledger WHERE singleton = 1') .get() as { receipt_count: number } | undefined @@ -50,7 +50,7 @@ function receiptCount(db: Database.Database): number { return row.receipt_count } -export function ensureMutationReceiptCapacity(db: Database.Database): void { +export function ensureMutationReceiptCapacity(db: Database): void { db.prepare( `DELETE FROM mutation_receipts WHERE state = 'completed' diff --git a/src/main/runtime/orchestration/nested-worker-depth-migration.test.ts b/src/main/runtime/orchestration/nested-worker-depth-migration.test.ts index eb0f53a30d5..dbf06326ad3 100644 --- a/src/main/runtime/orchestration/nested-worker-depth-migration.test.ts +++ b/src/main/runtime/orchestration/nested-worker-depth-migration.test.ts @@ -58,7 +58,7 @@ describe('nested worker depth migration (v30)', () => { it('backfills in-flight rows to depth 1, not 0', () => { const dbPath = createV29Database() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect(sqlite.pragma('user_version', { simple: true })).toBe(SCHEMA_VERSION) expect( @@ -74,7 +74,7 @@ describe('nested worker depth migration (v30)', () => { it('leaves an upgraded in-flight worker unable to spawn at the default cap', () => { const dbPath = createV29Database() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite .prepare( "UPDATE dispatch_contexts SET assignee_handle = 'term_upgraded' WHERE id = 'ctx_inflight'" @@ -105,7 +105,7 @@ describe('nested worker depth migration (v30)', () => { it('widens the attachment pane indexes to the potentially-live states', () => { const dbPath = createV29Database() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const sql = sqlite .prepare( "SELECT sql FROM sqlite_master WHERE type = 'index' AND name = 'idx_remote_dispatch_attachments_active_pane'" diff --git a/src/main/runtime/orchestration/orchestration-adopted-run-binding.test.ts b/src/main/runtime/orchestration/orchestration-adopted-run-binding.test.ts index d667267e0a6..08190c786ce 100644 --- a/src/main/runtime/orchestration/orchestration-adopted-run-binding.test.ts +++ b/src/main/runtime/orchestration/orchestration-adopted-run-binding.test.ts @@ -227,7 +227,7 @@ describe('adopted Run binding without --takeover-legacy', () => { // JS isEquivalentPaneKey filter stays authoritative. These pin both halves of that contract. describe('pane-bound Run lookup', () => { function explain(db: OrchestrationDb, paneKey: string): string { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return ( sqlite .prepare( @@ -349,7 +349,7 @@ describe('pane-bound Run lookup', () => { it('hands the JS filter an O(1) candidate set regardless of bound-Run count', () => { const db = track(new OrchestrationDb(':memory:')) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const leaf = 'bbbbbbbb-bbbb-4bbb-8bbb-bbbbbbbbbbbb' for (let i = 0; i < 500; i++) { db.createRun({ diff --git a/src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts b/src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts index df7f3592355..7642d915c3b 100644 --- a/src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts +++ b/src/main/runtime/orchestration/orchestration-creator-authority-performance.test.ts @@ -6,8 +6,8 @@ import { createRootDispatch } from './db/root-dispatch-test-fixture' const CREATOR_PANE = 'tab-creator:11111111-1111-4111-8111-111111111111' const CREATOR_PROCESS = 'pty-creator:incarnation-a' -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } describe('creator authority lookup performance', () => { diff --git a/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts b/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts index c980b8fe02a..f33204f8a0c 100644 --- a/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts +++ b/src/main/runtime/orchestration/orchestration-db-retention-pagination.test.ts @@ -9,8 +9,8 @@ import { createRootDispatch } from './db/root-dispatch-test-fixture' const MUTATION_RECEIPT_MAX_ROWS = 10_000 -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } function insertMutationReceipts( diff --git a/src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts b/src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts index 2dda528333a..3251557c8f3 100644 --- a/src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts +++ b/src/main/runtime/orchestration/orchestration-legacy-storage-db.test.ts @@ -72,7 +72,7 @@ describe('OrchestrationDb legacy contract storage', () => { db = new OrchestrationDb(fixture.dbPath) const adoption = db.getLegacyAdoption() const adoptedRunId = adoption?.adopted_run_id as string - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect(adoption).toMatchObject({ source_run_id: LEGACY_RUN_ID, @@ -694,7 +694,7 @@ describe('OrchestrationDb legacy contract storage', () => { askerHandle: 'term_legacy_worker', question: 'Inherited?' }) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite .prepare( `UPDATE messages diff --git a/src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts b/src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts index d80fb7a6743..c0ba60c2425 100644 --- a/src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts +++ b/src/main/runtime/orchestration/orchestration-run-list-compatibility.test.ts @@ -3,8 +3,8 @@ import type Database from '../../sqlite/sync-database' import { OrchestrationDb } from './db' import { ORCHESTRATION_RUN_METHODS } from '../rpc/methods/orchestration/runs/runs' -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } describe('orchestration Run list compatibility', () => { diff --git a/src/main/runtime/orchestration/orchestration-schema-version-skew.ts b/src/main/runtime/orchestration/orchestration-schema-version-skew.ts index 85e0a78b3ae..39ad96d3724 100644 --- a/src/main/runtime/orchestration/orchestration-schema-version-skew.ts +++ b/src/main/runtime/orchestration/orchestration-schema-version-skew.ts @@ -75,22 +75,18 @@ const POST_V6_INDEXES = [ 'idx_remote_questions_dispatch_status' ] as const -function hasOrchestrationColumn(db: Database.Database, table: string, column: string): boolean { +function hasOrchestrationColumn(db: Database, table: string, column: string): boolean { const rows = db.pragma(`table_info(${table})`) as { name: string }[] return rows.some((row) => row.name === column) } -function hasNotNullOrchestrationColumn( - db: Database.Database, - table: string, - column: string -): boolean { +function hasNotNullOrchestrationColumn(db: Database, table: string, column: string): boolean { const rows = db.pragma(`table_info(${table})`) as { name: string; notnull: number }[] return rows.some((row) => row.name === column && row.notnull === 1) } function hasOrchestrationColumnDefault( - db: Database.Database, + db: Database, table: string, column: string, defaultValue: string @@ -99,29 +95,25 @@ function hasOrchestrationColumnDefault( return rows.some((row) => row.name === column && row.dflt_value === defaultValue) } -function hasOrchestrationIndex(db: Database.Database, index: string): boolean { +function hasOrchestrationIndex(db: Database, index: string): boolean { return !!db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'index' AND name = ?").get(index) } -function hasOrchestrationIndexPredicate( - db: Database.Database, - index: string, - predicate: string -): boolean { +function hasOrchestrationIndexPredicate(db: Database, index: string, predicate: string): boolean { const row = db .prepare("SELECT sql FROM sqlite_master WHERE type = 'index' AND name = ?") .get(index) as { sql: string | null } | undefined return !!row?.sql?.includes(predicate) } -function messagesAllowQuestions(db: Database.Database): boolean { +function messagesAllowQuestions(db: Database): boolean { const row = db .prepare("SELECT sql FROM sqlite_master WHERE type = 'table' AND name = 'messages'") .get() as { sql: string } | undefined return !!row && row.sql.includes("'question'") } -function hasConsistentLegacyAdoption(db: Database.Database): boolean { +function hasConsistentLegacyAdoption(db: Database): boolean { const sourceRunId = 'run_legacy_local' // Misfiled federated mail is not evidence of a pre-Runs database. const notFederatedMailbox = (handle: string): string => @@ -155,7 +147,7 @@ function hasConsistentLegacyAdoption(db: Database.Database): boolean { return true } -function hasCompletePostV6Schema(db: Database.Database, storedVersion: number): boolean { +function hasCompletePostV6Schema(db: Database, storedVersion: number): boolean { return ( POST_V6_COLUMNS.every(([table, column]) => hasOrchestrationColumn(db, table, column)) && VERSIONED_POST_V6_COLUMNS.every( @@ -178,7 +170,7 @@ function hasCompletePostV6Schema(db: Database.Database, storedVersion: number): } export function resolveOrchestrationMigrationStartVersion( - db: Database.Database, + db: Database, storedVersion: number, schemaVersion: number ): number { diff --git a/src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts b/src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts index f15ad3c316a..f3d5c81e48a 100644 --- a/src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts +++ b/src/main/runtime/orchestration/run-coordinator-handle-migration.test.ts @@ -37,7 +37,7 @@ describe('Run coordinator handle history migration', () => { oldDb.close() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect(sqlite.pragma('user_version', { simple: true })).toBe(SCHEMA_VERSION) expect( sqlite @@ -84,7 +84,7 @@ describe('Run coordinator handle history migration', () => { v28Db.close() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect(sqlite.pragma('user_version', { simple: true })).toBe(SCHEMA_VERSION) expect( sqlite @@ -116,7 +116,7 @@ describe('Run coordinator handle history migration', () => { oldRuntimeDb.close() db = new OrchestrationDb(dbPath) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db expect( sqlite .prepare( diff --git a/src/main/runtime/relay/desktop-relay-service.ts b/src/main/runtime/relay/desktop-relay-service.ts index 74bbd4e7254..01d3a87fd6d 100644 --- a/src/main/runtime/relay/desktop-relay-service.ts +++ b/src/main/runtime/relay/desktop-relay-service.ts @@ -9,7 +9,8 @@ import type { import type { RelayHostCloseReason } from '../../../shared/relay-host-close-reason' import { readRelayAuthContext } from './relay-auth-context' import { RelayAuthCoordinator } from './relay-auth-coordinator' -import { RelaySessionBroker, type RelayBrokerStatus } from './relay-session-broker' +import { RelaySessionBroker } from './relay-session-broker' +import type { MobileRelayStatus } from '../../../shared/mobile-relay-status' import type { PairingRelay } from '../../../shared/mobile-relay-pairing-offer' import type { RelayRevokeOutbox, @@ -26,7 +27,7 @@ type DesktopRelayServiceOptions = { userDataPath: string appVersion: string runtimeRpc: OrcaRuntimeRpcServer - onStatus: (status: RelayBrokerStatus, cellUrl?: string) => void + onStatus: (status: MobileRelayStatus, cellUrl?: string) => void } export function pairingAuthorizationForContext( diff --git a/src/main/runtime/relay/relay-auth-coordinator.ts b/src/main/runtime/relay/relay-auth-coordinator.ts index 21c7f6649c4..a2f0540bf24 100644 --- a/src/main/runtime/relay/relay-auth-coordinator.ts +++ b/src/main/runtime/relay/relay-auth-coordinator.ts @@ -3,7 +3,7 @@ import { type RelayHostCloseReason } from '../../../shared/relay-host-close-reason' import { relayStatusCellUrl } from '../../../shared/mobile-relay-status' -import type { RelayBrokerStatus } from './relay-session-broker' +import type { MobileRelayStatus } from '../../../shared/mobile-relay-status' import { RelayHttpError, shouldRetryRelayConnectionError } from './relay-http-client' export type RelayAuthIdentity = { @@ -32,7 +32,7 @@ type RelayAuthCoordinatorOptions = { isCurrent: () => boolean refreshAccessToken: () => Promise }) => Promise - onStatus: (status: RelayBrokerStatus, cellUrl?: string) => void + onStatus: (status: MobileRelayStatus, cellUrl?: string) => void lingerMs?: number random?: () => number } @@ -100,7 +100,7 @@ export class RelayAuthCoordinator { // Why derived rather than passed in: the coordinator republishes `registered` // after the broker already announced its cell, so a call site that forgot the // cell would silently blank it moments after the broker set it. - private publish(status: RelayBrokerStatus): void { + private publish(status: MobileRelayStatus): void { this.options.onStatus( status, relayStatusCellUrl(status, this.ownership?.broker?.endpoint?.cellUrl) diff --git a/src/main/runtime/relay/relay-origin-pool-options.ts b/src/main/runtime/relay/relay-origin-pool-options.ts index ffb5455d1f4..663e489ef8d 100644 --- a/src/main/runtime/relay/relay-origin-pool-options.ts +++ b/src/main/runtime/relay/relay-origin-pool-options.ts @@ -1,7 +1,8 @@ import type WebSocket from 'ws' import type { E2EEKeypair } from '../e2ee-keypair' import type { MobileSocketWiring } from '../rpc/mobile-socket-wiring' -import type { RelayBrokerStatus, RelayIdentity } from './relay-session-broker-contract' +import type { RelayIdentity } from './relay-session-broker-contract' +import type { MobileRelayStatus } from '../../../shared/mobile-relay-status' import type { RelayRegion } from './relay-region-preference' export type RelayOriginPoolOptions = { @@ -12,7 +13,7 @@ export type RelayOriginPoolOptions = { appVersion: string mobileSocketWiring: MobileSocketWiring isCurrent: () => boolean - onStatus: (status: RelayBrokerStatus) => void + onStatus: (status: MobileRelayStatus) => void resolvePreferredRegion?: () => Promise fetch?: typeof globalThis.fetch createControlSocket?: (url: string, relayJwt: string) => WebSocket diff --git a/src/main/runtime/relay/relay-session-broker-contract.ts b/src/main/runtime/relay/relay-session-broker-contract.ts index 355bed76360..ae1748c2dff 100644 --- a/src/main/runtime/relay/relay-session-broker-contract.ts +++ b/src/main/runtime/relay/relay-session-broker-contract.ts @@ -6,8 +6,6 @@ import type { MobileSocketWiring } from '../rpc/mobile-socket-wiring' import type { RelayRegion } from './relay-region-preference' import type { RelayRegionDecision, RelayRegionWindow } from './relay-region-correction-protocol' -export type RelayBrokerStatus = MobileRelayStatus - export type RelayIdentity = { userId: string profileId: string @@ -27,7 +25,7 @@ export type RelaySessionBrokerOptions = { measureRegionDecision?: (window: RelayRegionWindow) => Promise onAssignedCellActive?: (cellUrl: string) => void /** `cellUrl` is absent whenever the host holds no active assignment. */ - onStatus: (status: RelayBrokerStatus, cellUrl?: string) => void + onStatus: (status: MobileRelayStatus, cellUrl?: string) => void fetch?: typeof globalThis.fetch createControlSocket?: (url: string, relayJwt: string) => WebSocket createDataSocket?: (url: string) => WebSocket diff --git a/src/main/runtime/relay/relay-session-broker.ts b/src/main/runtime/relay/relay-session-broker.ts index f32a077885e..231b6158cd9 100644 --- a/src/main/runtime/relay/relay-session-broker.ts +++ b/src/main/runtime/relay/relay-session-broker.ts @@ -21,9 +21,8 @@ import { import { RelayOriginPool } from './relay-origin-pool' import { RelayRegionRefresh } from './relay-region-refresh' import { relayRenewalDelayMs } from './relay-renewal-jitter' -import type { RelayBrokerStatus, RelaySessionBrokerOptions } from './relay-session-broker-contract' - -export type { RelayBrokerStatus } from './relay-session-broker-contract' +import type { RelaySessionBrokerOptions } from './relay-session-broker-contract' +import type { MobileRelayStatus } from '../../../shared/mobile-relay-status' export class RelaySessionBroker { private readonly options: RelaySessionBrokerOptions @@ -312,7 +311,7 @@ export class RelaySessionBroker { return !this.closed && this.options.isCurrent() } - private publishStatus(status: RelayBrokerStatus): void { + private publishStatus(status: MobileRelayStatus): void { if (!this.isCurrent()) { return } diff --git a/src/main/runtime/rpc/dispatcher.ts b/src/main/runtime/rpc/dispatcher.ts index 2c407207197..16cfe8b92fe 100644 --- a/src/main/runtime/rpc/dispatcher.ts +++ b/src/main/runtime/rpc/dispatcher.ts @@ -29,8 +29,6 @@ export type DispatcherOptions = { methods?: readonly RpcAnyMethodDeclaration[] } -type DispatchCallOptions = RpcDispatchStreamingOptions - export class RpcDispatcher { private readonly runtime: OrcaRuntimeService private readonly registry: RpcRegistry @@ -52,7 +50,7 @@ export class RpcDispatcher { }) } - async dispatch(request: RpcRequest, options?: DispatchCallOptions): Promise { + async dispatch(request: RpcRequest, options?: RpcDispatchStreamingOptions): Promise { const meta = this.meta() const method = this.registry.get(request.method) if (!method) { diff --git a/src/main/runtime/rpc/methods/orchestration/worker/agent-status-producer-census.test.ts b/src/main/runtime/rpc/methods/orchestration/worker/agent-status-producer-census.test.ts index afed92ec8aa..9192d7bb324 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/agent-status-producer-census.test.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/agent-status-producer-census.test.ts @@ -213,7 +213,7 @@ function seedWorker(db: OrchestrationDb): void { coordinatorPaneKey: 'tab-coordinator:aaaaaaaa-aaaa-4aaa-8aaa-aaaaaaaaaaaa' }) const task = db.createTask({ spec: 'census worker', runId: run.id }) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite .prepare( `INSERT INTO dispatch_contexts ( diff --git a/src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-release.test.ts b/src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-release.test.ts index e340e51b01d..64f4fd2ff63 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-release.test.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/manual-dispatch-release.test.ts @@ -192,6 +192,6 @@ function paneKey(handle: string): string { return `tab:${handle}` } -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } diff --git a/src/main/runtime/rpc/methods/orchestration/worker/worker-list-pagination.test.ts b/src/main/runtime/rpc/methods/orchestration/worker/worker-list-pagination.test.ts index a9dbccde53d..de68843a15e 100644 --- a/src/main/runtime/rpc/methods/orchestration/worker/worker-list-pagination.test.ts +++ b/src/main/runtime/rpc/methods/orchestration/worker/worker-list-pagination.test.ts @@ -621,8 +621,8 @@ function insertWorkerInventory( ) } -function sqliteFor(db: OrchestrationDb): Database.Database { - return (db as unknown as { db: Database.Database }).db +function sqliteFor(db: OrchestrationDb): Database { + return (db as unknown as { db: Database }).db } function federatedDispatch(dispatchId: string): FederatedDispatchRow { diff --git a/src/main/runtime/rpc/methods/terminal/terminal-input-delivery.ts b/src/main/runtime/rpc/methods/terminal/terminal-input-delivery.ts index 8c234ce2ba4..4dd91aa0ac2 100644 --- a/src/main/runtime/rpc/methods/terminal/terminal-input-delivery.ts +++ b/src/main/runtime/rpc/methods/terminal/terminal-input-delivery.ts @@ -1,10 +1,7 @@ +import type { RuntimeTerminalDriverState } from '../../../../../shared/runtime-types' import { isAgentSessionPtyWriteRefusedError } from '../../../../../shared/agent-session-pty-write-admission' import { InvalidArgumentError } from '../../core' -import type { - DriverState, - OrcaRuntimeService, - SubscriptionRegistration -} from '../../../orca-runtime' +import type { OrcaRuntimeService, SubscriptionRegistration } from '../../../orca-runtime' import { TERMINAL_INPUT_MAX_BYTES, TERMINAL_INPUT_TOO_LARGE_ERROR, @@ -38,7 +35,7 @@ export async function assertTerminalSendTextWithinLimit(text: string | undefined } export function resolveMobileFloorClientId( - driver: DriverState | null, + driver: RuntimeTerminalDriverState | null, client: TerminalViewportClient | undefined ): string | null { if (client?.type === 'mobile') { diff --git a/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher-test-fixture.ts b/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher-test-fixture.ts index faf934a6d66..88571e2ddde 100644 --- a/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher-test-fixture.ts +++ b/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher-test-fixture.ts @@ -191,7 +191,7 @@ export async function invoke( } export function counts(db: OrchestrationDb): Record { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return Object.fromEntries( [ 'messages', diff --git a/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher.test.ts b/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher.test.ts index 564eaed649a..80b8eae708a 100644 --- a/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher.test.ts +++ b/src/main/runtime/rpc/orchestration-legacy-compatibility-dispatcher.test.ts @@ -192,7 +192,7 @@ describe('legacy compatibility through RpcDispatcher', () => { processIncarnation: 'process-1' }) // Recreate the pre-boundary state where A settled before a current attempt was persisted. - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db sqlite.prepare("UPDATE tasks SET status = 'ready' WHERE id = ?").run(harness.taskId) const currentDispatch = createRootDispatch( harness.db, @@ -263,7 +263,7 @@ describe('legacy compatibility through RpcDispatcher', () => { it('rejects a reused pane whose live process incarnation is not the legacy worker', async () => { const harness = createHarness() - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db sqlite .prepare('UPDATE dispatch_contexts SET process_incarnation = ? WHERE id = ?') .run('different-process', harness.dispatchId) diff --git a/src/main/runtime/rpc/orchestration-legacy-coordinator-race.test.ts b/src/main/runtime/rpc/orchestration-legacy-coordinator-race.test.ts index c57f7876402..894b6124fd5 100644 --- a/src/main/runtime/rpc/orchestration-legacy-coordinator-race.test.ts +++ b/src/main/runtime/rpc/orchestration-legacy-coordinator-race.test.ts @@ -172,14 +172,14 @@ function settleLegacyWorkerAndTakeOver(harness: Harness, result: string): void { } function mutationReceiptCount(db: OrchestrationDb): number { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return ( sqlite.prepare('SELECT COUNT(*) AS count FROM mutation_receipts').get() as { count: number } ).count } function messageCount(db: OrchestrationDb): number { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return (sqlite.prepare('SELECT COUNT(*) AS count FROM messages').get() as { count: number }).count } @@ -501,7 +501,7 @@ describe('legacy coordinator takeover races', () => { mutation: { requestId: 'check-ack-takeover', replayed: false } } }) - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db expect(sqlite.prepare('SELECT state FROM mutation_receipts').get()).toEqual({ state: 'completed' }) diff --git a/src/main/runtime/rpc/orchestration-legacy-question-takeover.test.ts b/src/main/runtime/rpc/orchestration-legacy-question-takeover.test.ts index 82c59f39587..1913a3ed0b3 100644 --- a/src/main/runtime/rpc/orchestration-legacy-question-takeover.test.ts +++ b/src/main/runtime/rpc/orchestration-legacy-question-takeover.test.ts @@ -179,7 +179,7 @@ describe('legacy question takeover compatibility', () => { legacyCompatibility: { replayed: true } } }) - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db expect( ( sqlite @@ -230,7 +230,7 @@ describe('legacy question takeover compatibility', () => { } } }) - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db expect( (sqlite.prepare('SELECT COUNT(*) AS count FROM question_threads').get() as { count: number }) .count @@ -339,7 +339,7 @@ describe('legacy question takeover compatibility', () => { workerEvidence ) ) - const sqlite = (harness.db as unknown as { db: Database.Database }).db + const sqlite = (harness.db as unknown as { db: Database }).db sqlite .prepare('UPDATE messages SET to_handle = ? WHERE id = ?') .run('dispatch:foreign', acknowledgement.answerMessageId) diff --git a/src/main/runtime/rpc/orchestration-legacy-takeover-dispatcher.test.ts b/src/main/runtime/rpc/orchestration-legacy-takeover-dispatcher.test.ts index e5a05fe7d26..68ec6f53f9a 100644 --- a/src/main/runtime/rpc/orchestration-legacy-takeover-dispatcher.test.ts +++ b/src/main/runtime/rpc/orchestration-legacy-takeover-dispatcher.test.ts @@ -148,7 +148,7 @@ function request( } function counts(db: OrchestrationDb): Record { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return Object.fromEntries( [ 'messages', diff --git a/src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts b/src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts index 4172097853d..76654d18e90 100644 --- a/src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts +++ b/src/main/runtime/rpc/orchestration-runtime-update-settlement.test.ts @@ -184,7 +184,7 @@ function request( } function entityCounts(db: OrchestrationDb): Record { - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db return Object.fromEntries( ['tasks', 'dispatch_contexts', 'messages', 'legacy_compatibility_principals'].map((table) => [ table, diff --git a/src/main/runtime/runtime-browser-commands-browser-profile-import-from-browser.ts b/src/main/runtime/runtime-browser-commands-browser-profile-import-from-browser.ts index 585af8cd756..ec2759f456e 100644 --- a/src/main/runtime/runtime-browser-commands-browser-profile-import-from-browser.ts +++ b/src/main/runtime/runtime-browser-commands-browser-profile-import-from-browser.ts @@ -1,9 +1,7 @@ // @ts-nocheck -- mechanically split class members. +import type { BrowserCookieImportResult } from '../../shared/browser-workspace-types' import { RuntimeBrowserCommandsWithBrowserTabSetProfile } from './runtime-browser-commands-browser-tab-set-profile' -import type { - BrowserProfileClearDefaultCookiesResult, - BrowserProfileImportFromBrowserResult -} from '../../shared/runtime-types' +import type { BrowserProfileClearDefaultCookiesResult } from '../../shared/runtime-types' import { browserSessionRegistry } from '../browser/browser-session-registry' import { detectInstalledBrowsers, @@ -17,7 +15,7 @@ export class RuntimeBrowserCommandsWithBrowserProfileImportFromBrowser extends R browserFamily: string browserProfile?: string supportsPartitionSkippedCookies?: true - }): Promise { + }): Promise { const profile = browserSessionRegistry.getProfile(params.profileId) if (!profile) { return { ok: false, reason: 'Session profile not found.' } diff --git a/src/main/runtime/runtime-file-commands-active-runtime-text-searches.ts b/src/main/runtime/runtime-file-commands-active-runtime-text-searches.ts index e4fbfa0b6dd..5805039615d 100644 --- a/src/main/runtime/runtime-file-commands-active-runtime-text-searches.ts +++ b/src/main/runtime/runtime-file-commands-active-runtime-text-searches.ts @@ -1,5 +1,5 @@ // @ts-nocheck -- mechanically split class members. -import type { ChildProcessHandle } from '../../shared/child-process/process-spec' +import type { ChildProcess } from 'node:child_process' import type { TerminalFileGrant } from './runtime-file-commands-mobile-file-list-limit' import { MOBILE_FILE_PATH_SEARCH_CACHE_ENTRIES, @@ -8,7 +8,7 @@ import { import { RuntimeMobileFilePathSearchCache } from './runtime-mobile-file-path-search' export class RuntimeFileCommandsWithActiveRuntimeTextSearches { - protected activeRuntimeTextSearches = new Map() + protected activeRuntimeTextSearches = new Map() protected terminalFileGrants = new Map() diff --git a/src/main/runtime/runtime-file-commands-search-local-runtime-files.ts b/src/main/runtime/runtime-file-commands-search-local-runtime-files.ts index e7f3172f753..e83980009a1 100644 --- a/src/main/runtime/runtime-file-commands-search-local-runtime-files.ts +++ b/src/main/runtime/runtime-file-commands-search-local-runtime-files.ts @@ -20,7 +20,7 @@ import { isRipgrepUnavailableExit, killSpawnedRipgrepProcess } from '../../shared/ripgrep-process-availability' -import type { ChildProcessHandle } from '../../shared/child-process/process-spec' +import type { ChildProcess } from 'node:child_process' import { wslAwareSpawn } from '../git/runner' import type { RuntimeFileExplorerPath } from './runtime-file-command-target' import type { IFilesystemProvider } from '../providers/types' @@ -63,7 +63,7 @@ export class RuntimeFileCommandsWithSearchLocalRuntimeFiles extends RuntimeFileC let resolved = false let processErrorObserved = false let unavailableExitObserved = false - let child: ChildProcessHandle | null = null + let child: ChildProcess | null = null const transformAbsPath = wslInfo ? (p: string): string => toWindowsWslPath(p, wslInfo.distro) : undefined diff --git a/src/main/runtime/runtime-hosted-review-commands.ts b/src/main/runtime/runtime-hosted-review-commands.ts index 00020f9c922..c721da7e27d 100644 --- a/src/main/runtime/runtime-hosted-review-commands.ts +++ b/src/main/runtime/runtime-hosted-review-commands.ts @@ -1,7 +1,6 @@ import type { CreateHostedReviewInput, CreateHostedReviewResult, - CreateStackedHostedReviewInput, CreateStackedHostedReviewResult, HostedReviewCreationEligibility, HostedReviewCreationEligibilityArgs, @@ -184,7 +183,7 @@ export class RuntimeHostedReviewCommands { } async createStackedHostedReview( - args: CreateStackedHostedReviewInput & HostedReviewTargetArgs + args: CreateHostedReviewInput & HostedReviewTargetArgs ): Promise { const { repo, repoPath } = await this.deps.resolveTarget(args) const executionOptions = this.deps.getExecutionOptions(repo, 'interactive') diff --git a/src/main/runtime/runtime-legacy-worker-terminal-recovery-candidate.ts b/src/main/runtime/runtime-legacy-worker-terminal-recovery-candidate.ts index c25ca8deab6..786e5b0fb79 100644 --- a/src/main/runtime/runtime-legacy-worker-terminal-recovery-candidate.ts +++ b/src/main/runtime/runtime-legacy-worker-terminal-recovery-candidate.ts @@ -1,7 +1,7 @@ +import type { PtyControllerInventory } from './runtime-pty-controller-contract' import type { RuntimeLegacyWorkerTerminalRecoveryController } from './runtime-legacy-worker-terminal-recovery-controller' import type { LegacyWorkerRecoveryCandidate, - LegacyWorkerRecoveryInventory, LegacyWorkerRecoveryOptions, LegacyWorkerRecoveryPorts, LegacyWorkerRecoveryResolution, @@ -15,7 +15,7 @@ export async function reconcileLegacyWorkerCandidate(args: { candidate: LegacyWorkerRecoveryCandidate workspace: LegacyWorkerRecoveryWorkspace resolvedWorktrees: LegacyWorkerRecoveryWorkspace['resolved'][] - inventory: LegacyWorkerRecoveryInventory + inventory: PtyControllerInventory deferredDispatchIds: Set pendingResolutions: LegacyWorkerRecoveryResolution[] }): Promise { diff --git a/src/main/runtime/runtime-legacy-worker-terminal-recovery-types.ts b/src/main/runtime/runtime-legacy-worker-terminal-recovery-types.ts index 16ca330647c..c83380afe98 100644 --- a/src/main/runtime/runtime-legacy-worker-terminal-recovery-types.ts +++ b/src/main/runtime/runtime-legacy-worker-terminal-recovery-types.ts @@ -35,8 +35,6 @@ export type LegacyWorkerRecoveryWorkspace = { resolved: ResolvedWorktree } -export type LegacyWorkerRecoveryInventory = PtyControllerInventory - export type LegacyWorkerRecoveryPorts = { preparePlan: () => LegacyWorkerTerminalRecoveryPlan resolveWorkspace: ( @@ -45,7 +43,7 @@ export type LegacyWorkerRecoveryPorts = { refreshInventory: ( worktrees: ResolvedWorktree[], connectionId: string | null - ) => Promise + ) => Promise /** Serializes the pre-adoption liveness probe and the adoption itself against other terminal mutations. */ runMutation: (worktreeId: string, operation: () => Promise) => Promise getActivation: (worktreeId: string) => { activeTabId?: string; activeGroupId?: string } @@ -54,7 +52,7 @@ export type LegacyWorkerRecoveryPorts = { adopt: ( candidate: LegacyWorkerRecoveryCandidate, workspace: TerminalWorkspaceLaunchScope, - inventory: LegacyWorkerRecoveryInventory, + inventory: PtyControllerInventory, activation: { activeTabId?: string; activeGroupId?: string } ) => Promise getRendererEpoch: () => number diff --git a/src/main/runtime/runtime-notifier-contract.ts b/src/main/runtime/runtime-notifier-contract.ts index 9cdc365e1ba..a3ea825bb7b 100644 --- a/src/main/runtime/runtime-notifier-contract.ts +++ b/src/main/runtime/runtime-notifier-contract.ts @@ -18,8 +18,6 @@ import type { RuntimeTerminalPresentation } from '../../shared/runtime-types' -type DriverState = RuntimeTerminalDriverState - export type RuntimeNotifier = { automationsChanged?(payload: { selector?: { kind: 'self' } | { kind: 'ssh'; targetId: string } | { kind: 'orphan' } @@ -140,7 +138,7 @@ export type RuntimeNotifier = { // actor's clientId so the renderer can disambiguate multi-phone scenarios // and so a future write coordinator can use the same signal as scheduling // input. See docs/mobile-presence-lock.md. - terminalDriverChanged(ptyId: string, driver: DriverState): void + terminalDriverChanged(ptyId: string, driver: RuntimeTerminalDriverState): void nativeChatLaunchDraftResolved?( tabId: string, resolution: { text: string; createdAt: number } diff --git a/src/main/runtime/runtime-pty-controller-contract.ts b/src/main/runtime/runtime-pty-controller-contract.ts index 73a75af017e..5521a80c93c 100644 --- a/src/main/runtime/runtime-pty-controller-contract.ts +++ b/src/main/runtime/runtime-pty-controller-contract.ts @@ -10,7 +10,7 @@ import type { PtyIncarnationId } from '../../shared/pty-incarnation' import type { PtyBindingSourceExpectation } from '../persistence' import type { ExecutionHostId } from '../../shared/execution-host' import type { PtyProviderBufferSnapshot, PtyProcessInfo, PtySpawnResult } from '../providers/types' -import type { PtyProcessInspection } from '../providers/pty-process-inspection' +import type { TerminalProcessInspection } from '../../shared/terminal-process-inspection' import type { WriteSettlement } from '../../shared/pty-write-settlement' export type RuntimePtyController = { @@ -112,7 +112,7 @@ export type RuntimePtyController = { inspectProcess?( ptyId: string, options?: { expectedIncarnationId?: PtyIncarnationId; scanChildProcesses?: boolean } - ): Promise + ): Promise confirmForegroundProcess?(ptyId: string): Promise confirmShellForeground?(ptyId: string): Promise hasChildProcesses?(ptyId: string): Promise diff --git a/src/main/runtime/runtime-terminal-driver-controller.ts b/src/main/runtime/runtime-terminal-driver-controller.ts index 44525ad661d..e6a031cd4a2 100644 --- a/src/main/runtime/runtime-terminal-driver-controller.ts +++ b/src/main/runtime/runtime-terminal-driver-controller.ts @@ -1,42 +1,40 @@ import type { RuntimeTerminalDriverState } from '../../shared/runtime-types' import { notifyRuntimeListeners } from './runtime-async-boundaries' -type DriverState = RuntimeTerminalDriverState - type RuntimeTerminalDriverDependencies = { - notifyChanged: (ptyId: string, next: DriverState) => void + notifyChanged: (ptyId: string, next: RuntimeTerminalDriverState) => void canClaimMobileFloor: (ptyId: string, clientId: string) => boolean commitMobileFloor: ( ptyId: string, clientId: string, - previousFloor: DriverState, + previousFloor: RuntimeTerminalDriverState, isCurrent: () => boolean ) => Promise } type MobileInputFloorState = { - base: DriverState + base: RuntimeTerminalDriverState generation: number committedGeneration: number pending: Map } export class RuntimeTerminalDriverController { - private readonly current = new Map() - private readonly listeners = new Map void>>() + private readonly current = new Map() + private readonly listeners = new Map void>>() private readonly inputFloorClaims = new Map() constructor(private readonly deps: RuntimeTerminalDriverDependencies) {} - get(ptyId: string): DriverState { + get(ptyId: string): RuntimeTerminalDriverState { return this.current.get(ptyId) ?? { kind: 'idle' } } - getAll(): Map { + getAll(): Map { return new Map(this.current) } - set(ptyId: string, next: DriverState): void { + set(ptyId: string, next: RuntimeTerminalDriverState): void { const prev = this.get(ptyId) if (prev.kind === next.kind) { if (prev.kind === 'mobile' && next.kind === 'mobile' && prev.clientId === next.clientId) { @@ -66,8 +64,9 @@ export class RuntimeTerminalDriverController { return true } - subscribe(ptyId: string, listener: (driver: DriverState) => void): () => void { - const listeners = this.listeners.get(ptyId) ?? new Set<(driver: DriverState) => void>() + subscribe(ptyId: string, listener: (driver: RuntimeTerminalDriverState) => void): () => void { + const listeners = + this.listeners.get(ptyId) ?? new Set<(driver: RuntimeTerminalDriverState) => void>() listeners.add(listener) this.listeners.set(ptyId, listeners) return () => { diff --git a/src/main/runtime/structured-session-worktree-teardown.ts b/src/main/runtime/structured-session-worktree-teardown.ts index 46265a16111..c5a15e8becc 100644 --- a/src/main/runtime/structured-session-worktree-teardown.ts +++ b/src/main/runtime/structured-session-worktree-teardown.ts @@ -30,9 +30,23 @@ import { getStructuredAgentSessionHost } from '../native-chat/agent-session-wire import { observeStructuredWorker } from './structured-worker-authority' import { closeStructuredAgentSessionChild } from './structured-agent-session-close' import { retireSettledStructuredWorkerTab } from './structured-agent-session-tab-retirement' -import type { WorktreePtyHostFence } from './worktree-pty-host-fence' import type { OrcaRuntimeService } from './orca-runtime' +/** + * `WorktreePtyHostFence` is the two fields every teardown caller already resolves to fence its PTY + * sweeps to one host. + * + * Deliberately the PTY fence's own type rather than a look-alike: these two helpers are written + * against each other, so a widening on one side must not become a silent disagreement on the + * other. `resolvedConnectionId: null` means this machine on both. + * + * They differ in exactly one reading, and only that one: ABSENT. The PTY fence takes it as no + * fence at all and matches every host, which a single-host-id comparison cannot express — and + * closing every host's chats is destructive, not merely noisy. So this side reads absent as local + * too, the narrower half of that pair. Pinned by test, not left to the next reader to rediscover. + */ +import type { WorktreePtyHostFence } from './worktree-pty-host-fence' + export type StructuredSessionInWorkspace = { sessionId: string agent: 'claude' | 'codex' @@ -48,20 +62,6 @@ export type StructuredWorktreeSweepRuntime = Pick< 'forgetStructuredSessionMail' | 'retireStructuredAgentSessionTabFromSnapshot' > -/** - * The two fields every teardown caller already resolves to fence its PTY sweeps to one host. - * - * Deliberately the PTY fence's own type rather than a look-alike: these two helpers are written - * against each other, so a widening on one side must not become a silent disagreement on the - * other. `resolvedConnectionId: null` means this machine on both. - * - * They differ in exactly one reading, and only that one: ABSENT. The PTY fence takes it as no - * fence at all and matches every host, which a single-host-id comparison cannot express — and - * closing every host's chats is destructive, not merely noisy. So this side reads absent as local - * too, the narrower half of that pair. Pinned by test, not left to the next reader to rediscover. - */ -export type StructuredSessionHostFence = WorktreePtyHostFence - /** * The one execution host this teardown may touch. * @@ -70,9 +70,7 @@ export type StructuredSessionHostFence = WorktreePtyHostFence * PTY sweeps fence on exactly these two fields; a structured session records its host directly, so * the comparison is on `location.executionHostId` instead of on a pty-id shape. */ -export function structuredSessionTeardownHostId( - fence: StructuredSessionHostFence -): ExecutionHostId { +export function structuredSessionTeardownHostId(fence: WorktreePtyHostFence): ExecutionHostId { if (fence.resolvedRuntimeEnvironmentId !== undefined) { return toRuntimeExecutionHostId(fence.resolvedRuntimeEnvironmentId) } @@ -113,7 +111,7 @@ export type StructuredSessionsForWorktree = { */ export function listStructuredSessionsForWorktree( worktreeId: string, - fence: StructuredSessionHostFence + fence: WorktreePtyHostFence ): StructuredSessionsForWorktree { const host = getStructuredAgentSessionHost() if (!host) { diff --git a/src/main/runtime/unstopped-pty-verification.ts b/src/main/runtime/unstopped-pty-verification.ts index 7cfd5907797..83d000c75c9 100644 --- a/src/main/runtime/unstopped-pty-verification.ts +++ b/src/main/runtime/unstopped-pty-verification.ts @@ -14,8 +14,6 @@ import { settleBeforeDeadline } from './settle-before-deadline' // Floor for the verification window when the sweep ran on a very short budget. export const WORKTREE_TEARDOWN_VERIFY_GRACE_MS = 2_000 -export type UnstoppedPtyVerdict = PtyLivenessVerdict - /** * Re-lists the provider's processes to decide what a failed stop RPC actually * meant. The three verdicts stay distinct on purpose: "we could not ask" is not @@ -31,7 +29,7 @@ export async function verifyUnstoppedPtys( failedPtyIds: readonly string[], provider: IPtyProvider, sweepBudgetMs: number -): Promise { +): Promise { const verifyBudgetMs = Math.max(WORKTREE_TEARDOWN_VERIFY_GRACE_MS, sweepBudgetMs) const verifyDeadline = Date.now() + verifyBudgetMs let listError: unknown @@ -66,7 +64,7 @@ export async function verifyUnstoppedPtys( export function unverifiableStopVerdict( failedPtyIds: readonly string[], runtime: OrcaRuntimeService | undefined -): UnstoppedPtyVerdict | null { +): PtyLivenessVerdict | null { for (const ptyId of failedPtyIds) { const verdict = runtime?.getPtyLivenessVerdict?.(ptyId) if (verdict?.status === 'unverifiable') { @@ -82,7 +80,7 @@ export async function resolveUnstoppedPtyVerdict( sweepBudgetMs: number, providerObservesOwningHost: boolean, runtime?: OrcaRuntimeService -): Promise { +): Promise { if (failedPtyIds.length === 0) { return { status: 'exited' } } @@ -101,7 +99,7 @@ export async function resolveUnstoppedPtyVerdict( export function describeUnstoppedPtys( worktreeId: string, failedPtyIds: readonly string[], - verdict: Exclude + verdict: Exclude ): string { const detail = verdict.status === 'live' diff --git a/src/main/skills/skill-package-identity.ts b/src/main/skills/skill-package-identity.ts index cbc0fa79e24..9056c00be15 100644 --- a/src/main/skills/skill-package-identity.ts +++ b/src/main/skills/skill-package-identity.ts @@ -9,10 +9,8 @@ import { type SkillGitTreeFileEntry } from './skill-git-tree-identity' -type ObservedSkillFile = SkillBundleFileIdentity - export type ObservedSkillPackage = { - files: ObservedSkillFile[] + files: SkillBundleFileIdentity[] observedDigest: string /** * Git tree sha of every observed file's raw bytes — one of the two values comparable @@ -133,7 +131,7 @@ export function describeObservedSkillFile( path: string, bytes: Buffer, executable: boolean -): ObservedSkillFile { +): SkillBundleFileIdentity { let normalized: Buffer | null = null if (!bytes.includes(0)) { try { @@ -173,7 +171,7 @@ export function skillPackageDigest(files: readonly SkillBundleFileIdentity[]): s } function matchesFileIdentity( - actual: ObservedSkillFile, + actual: SkillBundleFileIdentity, expected: SkillBundleFileIdentity ): boolean { if ( @@ -196,7 +194,7 @@ export async function observeSkillPackage( platform: NodeJS.Platform = process.platform, inferShebangExecutables = false ): Promise { - const files: ObservedSkillFile[] = [] + const files: SkillBundleFileIdentity[] = [] const treeEntries: SkillGitTreeFileEntry[] = [] const caseFoldedPaths = new Map() const normalizedExecutablePaths = diff --git a/src/main/skills/skill-provider-destinations.ts b/src/main/skills/skill-provider-destinations.ts index 361b0932f10..4f7adc9782d 100644 --- a/src/main/skills/skill-provider-destinations.ts +++ b/src/main/skills/skill-provider-destinations.ts @@ -5,15 +5,13 @@ import { type SkillInstallProviderId } from '../../shared/skill-install-providers' -export type OrcaSkillProviderId = SkillInstallProviderId - export type SkillProviderDestination = { - provider: OrcaSkillProviderId + provider: SkillInstallProviderId readsCanonicalRoot: boolean rootPath: string } -export type SkillProviderRootOverrides = Partial> +export type SkillProviderRootOverrides = Partial> function normalizedPath(path: string): string { const normalized = resolve(path) diff --git a/src/main/source-control/stacked-hosted-review-creation.ts b/src/main/source-control/stacked-hosted-review-creation.ts index f11643ee198..3dba1ab57dc 100644 --- a/src/main/source-control/stacked-hosted-review-creation.ts +++ b/src/main/source-control/stacked-hosted-review-creation.ts @@ -1,6 +1,6 @@ import type { ExecutionHostId } from '../../shared/execution-host' import type { - CreateStackedHostedReviewInput, + CreateHostedReviewInput, CreateStackedHostedReviewResult, HostedReviewSummary } from '../../shared/hosted-review' @@ -13,7 +13,7 @@ import type { HostedReviewExecutionOptions } from './hosted-review-git-options' export async function createStackedHostedReview( repoPath: string, - input: CreateStackedHostedReviewInput, + input: CreateHostedReviewInput, executionHostId: ExecutionHostId, options: HostedReviewExecutionOptions = {} ): Promise { diff --git a/src/main/sqlite/sync-database.test.ts b/src/main/sqlite/sync-database.test.ts index 39cb443aeeb..2a6df5f8a36 100644 --- a/src/main/sqlite/sync-database.test.ts +++ b/src/main/sqlite/sync-database.test.ts @@ -6,10 +6,10 @@ import { afterEach, describe, expect, it } from 'vitest' import SyncDatabase from './sync-database' const temporaryDirectories: string[] = [] -const openDatabases: SyncDatabase.Database[] = [] +const openDatabases: SyncDatabase[] = [] const lockHolders: Worker[] = [] -async function createDatabase(): Promise { +async function createDatabase(): Promise { const directory = await mkdtemp(join(tmpdir(), 'orca-sync-database-')) temporaryDirectories.push(directory) const db = new SyncDatabase(join(directory, 'test.db')) diff --git a/src/main/sqlite/sync-database.ts b/src/main/sqlite/sync-database.ts index 0bfa79e43f5..8990539d0b3 100644 --- a/src/main/sqlite/sync-database.ts +++ b/src/main/sqlite/sync-database.ts @@ -1,5 +1,5 @@ import { existsSync } from 'node:fs' -import type { DatabaseSync, StatementSync, SQLInputValue } from 'node:sqlite' +import type { DatabaseSync, StatementSync } from 'node:sqlite' type SqlitePath = ConstructorParameters[0] @@ -13,8 +13,6 @@ type PragmaOptions = { simple?: boolean } -export type SqliteStatement = StatementSync - // Why: dynamic `IN (?,?,…)` clauses mint a new SQL string per arity, so the cache must stay bounded. const STATEMENT_CACHE_LIMIT = 256 const AGGREGATE_STAR = /\(\s*\*\s*\)/g @@ -106,10 +104,4 @@ class SyncDatabase { } } -namespace SyncDatabase { - export type Database = SyncDatabase - export type Statement = SqliteStatement - export type BindValue = SQLInputValue -} - export default SyncDatabase diff --git a/src/main/ssh/ssh-remote-orchestration-compatibility.test.ts b/src/main/ssh/ssh-remote-orchestration-compatibility.test.ts index e84b483b5b1..02b2894d6e1 100644 --- a/src/main/ssh/ssh-remote-orchestration-compatibility.test.ts +++ b/src/main/ssh/ssh-remote-orchestration-compatibility.test.ts @@ -59,7 +59,7 @@ function createLegacyRuntime() { createdByTerminalHandle: COORDINATOR_HANDLE }) const dispatch = createRootDispatch(db, task.id, WORKER_HANDLE, WORKER_PANE) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite .prepare( `UPDATE dispatch_contexts @@ -319,7 +319,7 @@ describe('legacy SSH orchestration fallback', () => { try { const first = await runRemoteOrcaCli(runtime, request, LEGACY_FALLBACK_OPTIONS) const replay = await runRemoteOrcaCli(runtime, request, LEGACY_FALLBACK_OPTIONS) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const firstResult = JSON.parse(first.stdout) as { messageId: string; timedOut: boolean } const replayResult = JSON.parse(replay.stdout) as { messageId: string; timedOut: boolean } @@ -353,7 +353,7 @@ describe('legacy SSH orchestration fallback', () => { consumerGeneration: run.consumer_generation, body: 'yes' }) - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db sqlite .prepare( `UPDATE messages @@ -409,7 +409,7 @@ describe('legacy SSH orchestration fallback', () => { 'refuses a %s --retry-request instead of minting a new send identity', async (_label, retryArgv, expectedMessage) => { const { db, runtime } = createLegacyRuntime() - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const countMessages = (): number => (sqlite.prepare('SELECT COUNT(*) AS count FROM messages').get() as { count: number }).count const before = countMessages() @@ -454,7 +454,7 @@ describe('legacy SSH orchestration fallback', () => { ['ask', ['orchestration', 'ask', '--from', WORKER_HANDLE, '--question', 'continue?']] ])('refuses a valueless --retry-request on orchestration %s', async (_label, commandArgv) => { const { db, runtime } = createLegacyRuntime() - const sqlite = (db as unknown as { db: Database.Database }).db + const sqlite = (db as unknown as { db: Database }).db const countMessages = (): number => (sqlite.prepare('SELECT COUNT(*) AS count FROM messages').get() as { count: number }).count const before = countMessages() diff --git a/src/main/startup/main-process-state.ts b/src/main/startup/main-process-state.ts index 2b9361a6d51..ae33911be4d 100644 --- a/src/main/startup/main-process-state.ts +++ b/src/main/startup/main-process-state.ts @@ -23,7 +23,7 @@ import type { PluginKillListService } from '../plugins/plugin-kill-list-service' import type { PluginMarketplaceService } from '../plugins/plugin-marketplace-service' import type { PluginMarketplaceInstaller } from '../plugins/plugin-marketplace-installer' import type { KeybindingService } from '../keybindings/keybinding-service' -import type { RelayBrokerStatus } from '../runtime/relay/relay-session-broker' +import type { MobileRelayStatus } from '../../shared/mobile-relay-status' import type { AgentBrowserBridge } from '../browser/agent-browser-bridge' import type { EmulatorBridge } from '../emulator/emulator-bridge' import type { GpuFallbackMarker, GpuFallbackEnvironment } from './gpu-fallback-marker' @@ -66,7 +66,7 @@ export const mainProcessState = { serveReadinessPublisher: new ServeReadinessPublisher(), desktopRelayService: null as DesktopRelayService | null, desktopPushService: null as DesktopPushService | null, - desktopRelayStatus: 'offline' as RelayBrokerStatus, + desktopRelayStatus: 'offline' as MobileRelayStatus, desktopRelayCellUrl: undefined as string | undefined, pendingUnpairedDeviceAuthFailure: false, // Why: gates whether headless serve installs the offscreen browser backend (and advertises browser pane support). diff --git a/src/main/startup/main-window-actions.ts b/src/main/startup/main-window-actions.ts index 0acc8d1a074..398a483db10 100644 --- a/src/main/startup/main-window-actions.ts +++ b/src/main/startup/main-window-actions.ts @@ -16,10 +16,8 @@ import { describeInstallDirAclPoison, isBlockingInstallDirAclRepairInFlight } from './windows-install-dir-acl-recovery' -import { - presentRendererRecoveryPrompt, - type RendererRecoveryPromptFailure -} from '../window/renderer-recovery-prompt' +import { presentRendererRecoveryPrompt } from '../window/renderer-recovery-prompt' +import type { RecoveryExhaustionCause } from '../window/renderer-recovery-reload-watchdog' // The window module injects this callback to avoid a cycle between actions and lifecycle code. let openWindow: (options?: { revealOnDidFinishLoad?: boolean }) => BrowserWindow @@ -152,7 +150,7 @@ export function sendOpenCrashReport(targetWindow?: BrowserWindow | null): void { // Why: on renderer crash-loop the breaker stops auto-reloading and the window goes blank, so a main-process dialog is the only retry/quit surface. export async function showRendererRecoveryPrompt( recentRecoveryCount: number, - failure?: RendererRecoveryPromptFailure, + failure?: RecoveryExhaustionCause, retry?: () => void ): Promise { await presentRendererRecoveryPrompt({ diff --git a/src/main/telemetry/client.ts b/src/main/telemetry/client.ts index dc92cf1461d..fad193baafc 100644 --- a/src/main/telemetry/client.ts +++ b/src/main/telemetry/client.ts @@ -13,7 +13,8 @@ import type { CommonProps, EventName, EventProps, OptInVia } from '../../shared/ import type { Store } from '../persistence' import { consumeBurstToken, resetBurstCapsForSession } from './burst-cap' import { getCohortAtEmit } from './cohort-classifier' -import { resolveConsent, type ConsentState } from './consent' +import { resolveConsent } from './consent' +import type { TelemetryConsentState } from '../../shared/telemetry-consent-types' import { commonPropsSchema, validate } from './validator' // Compile-time feature flag, independent of the build-identity gate — both must be satisfied to transmit. @@ -123,7 +124,7 @@ export function initTelemetry(store: Store): void { * `pending_banner`: the direct `telemetry_opted_out` capture in `setOptIn(_, false)` must not drop, * or we'd lose the one signal that the opt-out flow works. */ -export function shouldOptOutSdkAtInit(consent: ConsentState): boolean { +export function shouldOptOutSdkAtInit(consent: TelemetryConsentState): boolean { return consent.effective === 'disabled' } diff --git a/src/main/telemetry/consent.ts b/src/main/telemetry/consent.ts index 20b78526406..485590c823b 100644 --- a/src/main/telemetry/consent.ts +++ b/src/main/telemetry/consent.ts @@ -15,11 +15,6 @@ import type { TelemetryConsentState } from '../../shared/telemetry-consent-types // (`pending_banner`) from "user explicitly opted out" (`disabled`). A boolean // would force the UI to re-derive that, re-introducing the scattered env // checks this module exists to eliminate. -// -// The type itself lives in `shared/telemetry-consent-types.ts` so the -// renderer can import it across the IPC boundary; this re-export keeps -// existing call sites in main working without a rename. -export type ConsentState = TelemetryConsentState // Precedence for the `disabled` branches is documented alongside // `resolveConsent` below. @@ -73,7 +68,7 @@ export function _resetMisconfigWarnCacheForTests(): void { warnedMisconfigured.clear() } -export function resolveConsent(settings: GlobalSettings): ConsentState { +export function resolveConsent(settings: GlobalSettings): TelemetryConsentState { // Precedence 1: community standard kill switch. Always wins. if (isEnvVarTruthy('DO_NOT_TRACK')) { return { effective: 'disabled', reason: 'do_not_track' } diff --git a/src/main/text-generation/commit-message-text-generation.ts b/src/main/text-generation/commit-message-text-generation.ts index 17ead460bf1..ef33714169f 100644 --- a/src/main/text-generation/commit-message-text-generation.ts +++ b/src/main/text-generation/commit-message-text-generation.ts @@ -39,7 +39,6 @@ import type { TextGenerationOperation } from './source-control-text-generation-types' -export type GenerateCommitMessageParams = ResolvedSourceControlAiGenerationParams export type { CommitMessageGenerationTarget, CommitMessageModelDiscoveryLocalOptions, @@ -53,7 +52,7 @@ export type GeneratePullRequestFieldsResult = GenericGeneratePullRequestFieldsResult type ResolveCommitMessageSettingsResult = - | { ok: true; params: GenerateCommitMessageParams } + | { ok: true; params: ResolvedSourceControlAiGenerationParams } | { ok: false; error: string } export function trimGeneratedCommitMessage(message: string): string { @@ -134,7 +133,7 @@ export function cancelGeneratePullRequestFieldsLocal(cwd: string): void { export function generateCommitMessageFromContext( context: CommitMessageDraftContext, - params: GenerateCommitMessageParams, + params: ResolvedSourceControlAiGenerationParams, target: CommitMessageGenerationTarget ): Promise { return generateCommitMessage({ context, params, target, spawnAgent: spawnSourceControlAgent }) @@ -142,7 +141,7 @@ export function generateCommitMessageFromContext( export function generatePullRequestFieldsFromContext( context: PullRequestDraftContext, - params: GenerateCommitMessageParams, + params: ResolvedSourceControlAiGenerationParams, target: CommitMessageGenerationTarget ): Promise { return generatePullRequestFields({ context, params, target, spawnAgent: spawnSourceControlAgent }) @@ -150,7 +149,7 @@ export function generatePullRequestFieldsFromContext( export function generateBranchNameFromContext( context: BranchNameWorkContext, - params: GenerateCommitMessageParams, + params: ResolvedSourceControlAiGenerationParams, target: CommitMessageGenerationTarget ): Promise { return generateBranchName({ context, params, target, spawnAgent: spawnSourceControlAgent }) diff --git a/src/main/text-generation/source-control-text-generation-requests.ts b/src/main/text-generation/source-control-text-generation-requests.ts index 596ebc891cf..e37d47f9679 100644 --- a/src/main/text-generation/source-control-text-generation-requests.ts +++ b/src/main/text-generation/source-control-text-generation-requests.ts @@ -35,8 +35,6 @@ import type { TextGenerationOperation } from './source-control-text-generation-types' -type GenerateParams = ResolvedSourceControlAiGenerationParams - export function trimGeneratedCommitMessage(message: string): string { return message.replace(/\s+$/, '') } @@ -49,7 +47,7 @@ export function commandBackslashMode( } async function executeGenerationPlan(input: { - params: GenerateParams + params: ResolvedSourceControlAiGenerationParams plan: CommitMessagePlan target: CommitMessageGenerationTarget emptyResultName: string @@ -75,7 +73,7 @@ async function executeGenerationPlan(input: { export async function generateCommitMessage(input: { context: CommitMessageDraftContext - params: GenerateParams + params: ResolvedSourceControlAiGenerationParams target: CommitMessageGenerationTarget spawnAgent: SpawnSourceControlAgent }): Promise { @@ -120,7 +118,7 @@ export async function generateCommitMessage(input: { export async function generatePullRequestFields(input: { context: PullRequestDraftContext - params: GenerateParams + params: ResolvedSourceControlAiGenerationParams target: CommitMessageGenerationTarget spawnAgent: SpawnSourceControlAgent }): Promise> { @@ -183,7 +181,7 @@ export async function generatePullRequestFields(input: { export async function generateBranchName(input: { context: BranchNameWorkContext - params: GenerateParams + params: ResolvedSourceControlAiGenerationParams target: CommitMessageGenerationTarget spawnAgent: SpawnSourceControlAgent }): Promise { diff --git a/src/main/window/renderer-recovery-prompt.ts b/src/main/window/renderer-recovery-prompt.ts index 18ab02a8eca..d82d7bc5133 100644 --- a/src/main/window/renderer-recovery-prompt.ts +++ b/src/main/window/renderer-recovery-prompt.ts @@ -3,11 +3,9 @@ import { translateMain } from '../i18n/main-i18n' import type { InstallDirAclPoisonDiagnosis } from '../startup/windows-install-dir-acl-recovery' import type { RecoveryExhaustionCause } from './renderer-recovery-reload-watchdog' -export type RendererRecoveryPromptFailure = RecoveryExhaustionCause - export type RendererRecoveryPromptDeps = { recentRecoveryCount: number - failure?: RendererRecoveryPromptFailure + failure?: RecoveryExhaustionCause isQuitting: () => boolean diagnose: () => InstallDirAclPoisonDiagnosis | null showMessageBox: (options: MessageBoxOptions) => Promise diff --git a/src/main/workspace-cleanup-scan-snapshot.ts b/src/main/workspace-cleanup-scan-snapshot.ts index b87020e1ffe..4b6fb294636 100644 --- a/src/main/workspace-cleanup-scan-snapshot.ts +++ b/src/main/workspace-cleanup-scan-snapshot.ts @@ -23,8 +23,6 @@ import { const SNAPSHOT_FILE_NAME = 'orca-workspace-cleanup-scan.json' const SNAPSHOT_VERSION = 2 -export type WorkspaceCleanupScanSnapshotPruneTarget = WorkspaceSnapshotPruneTarget - const prunedWorkspacesByFile = new Map>() type PersistedWorkspaceCleanupScanSnapshot = { @@ -130,7 +128,7 @@ function candidateSnapshotKey( /** Register anti-resurrection tombstones without scheduling a sidecar rewrite. */ export function registerWorkspaceCleanupScanSnapshotPruneTombstones( snapshotDirectory: string, - targets: readonly WorkspaceCleanupScanSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): void { if (targets.length === 0) { return @@ -243,7 +241,7 @@ export async function persistWorkspaceCleanupScanResult( async function pruneWorkspaceCleanupScanSnapshotsWithRegisteredTombstones( snapshotDirectory: string, - targets: readonly WorkspaceCleanupScanSnapshotPruneTarget[], + targets: readonly WorkspaceSnapshotPruneTarget[], registerTombstones: boolean ): Promise { if (targets.length === 0) { @@ -286,7 +284,7 @@ async function pruneWorkspaceCleanupScanSnapshotsWithRegisteredTombstones( /** Drop removed workspaces in one sidecar transaction. Never throws. */ export async function pruneWorkspaceCleanupScanSnapshots( snapshotDirectory: string, - targets: readonly WorkspaceCleanupScanSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): Promise { await pruneWorkspaceCleanupScanSnapshotsWithRegisteredTombstones(snapshotDirectory, targets, true) } @@ -294,7 +292,7 @@ export async function pruneWorkspaceCleanupScanSnapshots( /** Flush only tombstones still active for this batch, preserving their original prune time. */ export async function finalizeWorkspaceCleanupScanSnapshotPrunes( snapshotDirectory: string, - targets: readonly WorkspaceCleanupScanSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): Promise { await pruneWorkspaceCleanupScanSnapshotsWithRegisteredTombstones( snapshotDirectory, diff --git a/src/main/workspace-space-analysis-snapshot.ts b/src/main/workspace-space-analysis-snapshot.ts index 8118095d396..4aa0428931d 100644 --- a/src/main/workspace-space-analysis-snapshot.ts +++ b/src/main/workspace-space-analysis-snapshot.ts @@ -21,8 +21,6 @@ import { const SNAPSHOT_FILE_NAME = 'orca-workspace-space-analysis.json' const SNAPSHOT_VERSION = 2 -export type WorkspaceSpaceAnalysisSnapshotPruneTarget = WorkspaceSnapshotPruneTarget - const prunedWorkspacesByFile = new Map>() type PersistedWorkspaceSpaceAnalysisSnapshot = { @@ -209,7 +207,7 @@ function analysisRepoKey(entry: { repoId: string; executionHostId?: ExecutionHos /** Register anti-resurrection tombstones without scheduling a sidecar rewrite. */ export function registerWorkspaceSpaceAnalysisSnapshotPruneTombstones( snapshotDirectory: string, - targets: readonly WorkspaceSpaceAnalysisSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): void { if (targets.length === 0) { return @@ -254,7 +252,7 @@ function clearSupersededPrunes(file: string, analysis: WorkspaceSpaceAnalysis): async function pruneWorkspaceSpaceAnalysisSnapshotsWithRegisteredTombstones( snapshotDirectory: string, - targets: readonly WorkspaceSpaceAnalysisSnapshotPruneTarget[], + targets: readonly WorkspaceSnapshotPruneTarget[], registerTombstones: boolean ): Promise { if (targets.length === 0) { @@ -297,7 +295,7 @@ async function pruneWorkspaceSpaceAnalysisSnapshotsWithRegisteredTombstones( /** Drop removed workspace rows and rebalance their totals in one sidecar transaction. Never throws. */ export async function pruneWorkspaceSpaceAnalysisSnapshots( snapshotDirectory: string, - targets: readonly WorkspaceSpaceAnalysisSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): Promise { await pruneWorkspaceSpaceAnalysisSnapshotsWithRegisteredTombstones( snapshotDirectory, @@ -309,7 +307,7 @@ export async function pruneWorkspaceSpaceAnalysisSnapshots( /** Flush only tombstones still active for this batch, preserving their original prune time. */ export async function finalizeWorkspaceSpaceAnalysisSnapshotPrunes( snapshotDirectory: string, - targets: readonly WorkspaceSpaceAnalysisSnapshotPruneTarget[] + targets: readonly WorkspaceSnapshotPruneTarget[] ): Promise { await pruneWorkspaceSpaceAnalysisSnapshotsWithRegisteredTombstones( snapshotDirectory, diff --git a/src/relay/dispatcher-capacity-degradation.test.ts b/src/relay/dispatcher-capacity-degradation.test.ts index 2c746168ecc..ea607aed028 100644 --- a/src/relay/dispatcher-capacity-degradation.test.ts +++ b/src/relay/dispatcher-capacity-degradation.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' import { RelayDispatcher, type RelayClientSinkOptions, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { DISPATCHER_CONTROL_QUEUE_MAX_FRAMES } from './dispatcher-writer-admission' import { LEGACY_CLIENT_RETAINED_BYTES_LOW } from './legacy-relay-publication-ledger' @@ -396,7 +396,7 @@ describe('RelayDispatcher bounded-capacity degradation', () => { const primary = makeBoundedClient(65536) const bounded = new RelayDispatcher(primary.write, primary.options) try { - const settlements: SinkWriteSettlement[] = [] + const settlements: DispatcherWriterSettlement[] = [] bounded.onRequest('fs.listFiles', async (_params, context) => { context.onResponseSettled?.((result) => settlements.push(result)) return { paths: 'x'.repeat(3 * 1024 * 1024) } @@ -537,7 +537,7 @@ describe('RelayDispatcher bounded-capacity degradation', () => { const bounded = new RelayDispatcher(primary.write, primary.options) try { const clientId = bounded.activeClientIds()[0] - const settlements: SinkWriteSettlement[] = [] + const settlements: DispatcherWriterSettlement[] = [] bounded.onRequest('workspace.get', async (_params, context) => { context.onResponseSettled?.((result) => settlements.push(result)) return { name: 'workspace' } diff --git a/src/relay/dispatcher-client-close-cause.test.ts b/src/relay/dispatcher-client-close-cause.test.ts index 2eb926900d5..f647c049c0e 100644 --- a/src/relay/dispatcher-client-close-cause.test.ts +++ b/src/relay/dispatcher-client-close-cause.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi } from 'vitest' -import { RelayDispatcher, type SinkWriteSettlement } from './dispatcher' +import { RelayDispatcher, type DispatcherWriterSettlement } from './dispatcher' import { DISPATCHER_CONTROL_QUEUE_MAX_FRAMES } from './dispatcher-writer-admission' // A detach carries the reason the client went away, because the PTY owner grace is only safe to @@ -11,7 +11,7 @@ describe('RelayDispatcher client close cause', () => { // but slow to drain looks like, and a consumer that read this as a peer close would shorten that // owner's grace and hand its session to someone else while it is still there. const detachListener = vi.fn() - const settlements: ((result: SinkWriteSettlement) => void)[] = [] + const settlements: ((result: DispatcherWriterSettlement) => void)[] = [] const dispatcher = new RelayDispatcher( (_data, onSettled) => { settlements.push(onSettled) diff --git a/src/relay/dispatcher-client-state.ts b/src/relay/dispatcher-client-state.ts index 710a704e4d8..8913380ac37 100644 --- a/src/relay/dispatcher-client-state.ts +++ b/src/relay/dispatcher-client-state.ts @@ -9,7 +9,7 @@ import type { DispatcherClientWriter, RelayClientSinkOptions, RelayClientWrite, - SinkWriteSettlement + DispatcherWriterSettlement } from './dispatcher-client-writer' import type { MethodHandler, @@ -118,6 +118,6 @@ export abstract class RelayDispatcherClientState { frame: PreparedRelayFrame, lane: 'interactive' | 'ordinary' | 'fixed-bulk' | 'bulk', lease: LegacyPublicationLease, - onSettled?: (result: SinkWriteSettlement) => void + onSettled?: (result: DispatcherWriterSettlement) => void ): boolean } diff --git a/src/relay/dispatcher-client-writer.test.ts b/src/relay/dispatcher-client-writer.test.ts index 83b5e7960b7..1bb2c218f34 100644 --- a/src/relay/dispatcher-client-writer.test.ts +++ b/src/relay/dispatcher-client-writer.test.ts @@ -2,12 +2,12 @@ import { describe, expect, it, vi } from 'vitest' import { DispatcherClientWriter, type DispatcherWriterLane, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher-client-writer' type AcceptedWrite = { data: string - settle: (result: SinkWriteSettlement) => void + settle: (result: DispatcherWriterSettlement) => void } class FakeSink { @@ -18,7 +18,7 @@ class FakeSink { saturateNext = false closed = false - write = (data: Buffer, settle: (result: SinkWriteSettlement) => void): boolean | void => { + write = (data: Buffer, settle: (result: DispatcherWriterSettlement) => void): boolean | void => { this.accepted.push({ data: data.toString(), settle }) this.writableLength += data.length if (this.saturateNext) { diff --git a/src/relay/dispatcher-client-writer.ts b/src/relay/dispatcher-client-writer.ts index 514ee47b8da..456e2499940 100644 --- a/src/relay/dispatcher-client-writer.ts +++ b/src/relay/dispatcher-client-writer.ts @@ -10,21 +10,20 @@ import { DispatcherWriterLaneScheduler } from './dispatcher-writer-lane-schedule import { DispatcherWriterSink, type RelayClientSinkOptions, - type RelayClientWrite, - type SinkWriteSettlement + type RelayClientWrite } from './dispatcher-writer-sink' +import type { DispatcherWriterSettlement } from './dispatcher-writer-admission' export { DEFAULT_PRODUCER_QUEUE_MAX_BYTES, DISPATCHER_CONTROL_QUEUE_MAX_BYTES, relayWriterControlReserve } from './dispatcher-writer-admission' +export type { RelayClientSinkOptions, RelayClientWrite } from './dispatcher-writer-sink' export type { - RelayClientSinkOptions, - RelayClientWrite, - SinkWriteSettlement -} from './dispatcher-writer-sink' -export type { DispatcherWriterLane } from './dispatcher-writer-admission' + DispatcherWriterLane, + DispatcherWriterSettlement +} from './dispatcher-writer-admission' export class DispatcherClientWriter { private readonly admission: DispatcherWriterAdmission @@ -88,7 +87,7 @@ export class DispatcherClientWriter { lane: DispatcherWriterLane, encode: () => Buffer, estimatedBytes: number, - onSettled: (result: SinkWriteSettlement) => void = () => {}, + onSettled: (result: DispatcherWriterSettlement) => void = () => {}, overflowIsNonFatal = false, isStillAdmitted?: () => boolean ): boolean { @@ -212,9 +211,9 @@ export class DispatcherClientWriter { } this.laneScheduler.recordWrite(entry.lane) this.inFlight.add(entry) - let callbackResult: SinkWriteSettlement | undefined + let callbackResult: DispatcherWriterSettlement | undefined let writeReturned = false - const onWriteSettled = (result: SinkWriteSettlement): void => { + const onWriteSettled = (result: DispatcherWriterSettlement): void => { if (!writeReturned) { callbackResult = result return @@ -245,7 +244,10 @@ export class DispatcherClientWriter { } } - private handleWriteSettlement(entry: DispatcherWriterEntry, result: SinkWriteSettlement): void { + private handleWriteSettlement( + entry: DispatcherWriterEntry, + result: DispatcherWriterSettlement + ): void { if (!result.ok) { this.releaseEntry(entry, result) this.close(result.error) @@ -285,7 +287,7 @@ export class DispatcherClientWriter { this.pump() } - private releaseEntry(entry: DispatcherWriterEntry, result: SinkWriteSettlement): void { + private releaseEntry(entry: DispatcherWriterEntry, result: DispatcherWriterSettlement): void { if (entry.settled) { return } diff --git a/src/relay/dispatcher-contract.ts b/src/relay/dispatcher-contract.ts index 23c62431d2c..b478a29dda0 100644 --- a/src/relay/dispatcher-contract.ts +++ b/src/relay/dispatcher-contract.ts @@ -5,14 +5,14 @@ import type { JsonRpcResponse, PreparedJsonRpcPayload } from './protocol' -import type { DispatcherClientWriter, SinkWriteSettlement } from './dispatcher-client-writer' +import type { DispatcherClientWriter, DispatcherWriterSettlement } from './dispatcher-client-writer' export type RequestContext = { clientId: number isStale: () => boolean signal?: AbortSignal sessionIdentity?: RelayClientSessionIdentity - onResponseSettled?: (handler: (result: SinkWriteSettlement) => void) => void + onResponseSettled?: (handler: (result: DispatcherWriterSettlement) => void) => void } export type RelayClientSessionIdentity = { diff --git a/src/relay/dispatcher-frame-codec.ts b/src/relay/dispatcher-frame-codec.ts index 5a25fa1100f..d38ea6be4fb 100644 --- a/src/relay/dispatcher-frame-codec.ts +++ b/src/relay/dispatcher-frame-codec.ts @@ -9,7 +9,7 @@ import { type JsonRpcRequest, type JsonRpcResponse } from './protocol' -import type { DispatcherWriterLane, SinkWriteSettlement } from './dispatcher-client-writer' +import type { DispatcherWriterLane, DispatcherWriterSettlement } from './dispatcher-client-writer' import type { OutgoingJsonRpcMessage, PreparedRelayFrame, RelayClient } from './dispatcher-contract' import { RelayDispatcherCapacitySignals } from './dispatcher-capacity-signals' @@ -65,7 +65,7 @@ export abstract class RelayDispatcherFrameCodec extends RelayDispatcherCapacityS client: RelayClient, msg: OutgoingJsonRpcMessage, lane: DispatcherWriterLane, - onSettled: (result: SinkWriteSettlement) => void = () => {}, + onSettled: (result: DispatcherWriterSettlement) => void = () => {}, controlOverflow: 'close-client' | 'reject' = 'close-client' ): boolean { if (this.disposed || client.closed) { @@ -84,7 +84,7 @@ export abstract class RelayDispatcherFrameCodec extends RelayDispatcherCapacityS client: RelayClient, frame: PreparedRelayFrame, lane: DispatcherWriterLane, - onSettled: (result: SinkWriteSettlement) => void = () => {}, + onSettled: (result: DispatcherWriterSettlement) => void = () => {}, controlOverflow: 'close-client' | 'reject' = 'close-client' ): boolean { if (this.disposed || client.closed) { diff --git a/src/relay/dispatcher-json-payload.test.ts b/src/relay/dispatcher-json-payload.test.ts index 4965fe6898a..6c9f7382c02 100644 --- a/src/relay/dispatcher-json-payload.test.ts +++ b/src/relay/dispatcher-json-payload.test.ts @@ -3,7 +3,7 @@ import type * as ProtocolModule from './protocol' import { RelayDispatcher, type RelayClientSinkOptions, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeKeepAliveFrame, type JsonRpcNotification } from './protocol' @@ -184,7 +184,7 @@ describe('RelayDispatcher prepared JSON payloads', () => { return admissionParams.deliveryToken === 'token-before' }) dispatcher.notify('test.blocker') - const settled = vi.fn<(result: SinkWriteSettlement) => void>() + const settled = vi.fn<(result: DispatcherWriterSettlement) => void>() expect(dispatcher.tryNotifyPtyDataToClient(1, params, settled)).toBe(true) admissions.length = 0 params.data = 'after' @@ -219,7 +219,7 @@ describe('RelayDispatcher prepared JSON payloads', () => { dispatcher.registerPtyDataPublicationAdmission(() => admitted) try { dispatcher.notify('test.blocker') - const retired = vi.fn<(result: SinkWriteSettlement) => void>() + const retired = vi.fn<(result: DispatcherWriterSettlement) => void>() expect(dispatcher.tryNotifyPtyDataToClient(1, { id: 'pty-1', data: 'retire' }, retired)).toBe( true ) diff --git a/src/relay/dispatcher-notification-publication.ts b/src/relay/dispatcher-notification-publication.ts index ab0851b4203..b5228deb985 100644 --- a/src/relay/dispatcher-notification-publication.ts +++ b/src/relay/dispatcher-notification-publication.ts @@ -1,4 +1,4 @@ -import type { SinkWriteSettlement } from './dispatcher-client-writer' +import type { DispatcherWriterSettlement } from './dispatcher-client-writer' import type { JsonRpcNotification } from './protocol' import { DROPPED_NOTIFICATION_LOG_KEY_LIMIT, @@ -86,7 +86,7 @@ export abstract class RelayDispatcherNotificationPublication extends RelayDispat clientId: number, method: string, params?: Record, - onSettled: (result: SinkWriteSettlement) => void = () => {}, + onSettled: (result: DispatcherWriterSettlement) => void = () => {}, options: { controlOverflow?: 'close-client' | 'reject' } = {} diff --git a/src/relay/dispatcher-producer-transport.ts b/src/relay/dispatcher-producer-transport.ts index 089a69577d1..9243718a3e5 100644 --- a/src/relay/dispatcher-producer-transport.ts +++ b/src/relay/dispatcher-producer-transport.ts @@ -1,6 +1,6 @@ import { DEFAULT_PRODUCER_QUEUE_MAX_BYTES, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher-client-writer' import type { LegacyPublicationLease } from './legacy-relay-publication-ledger' import type { JsonRpcNotification } from './protocol' @@ -71,7 +71,7 @@ export abstract class RelayDispatcherProducerTransport extends RelayDispatcherRp client: RelayClient, msg: JsonRpcNotification, lane: 'interactive' | 'ordinary' | 'fixed-bulk' | 'bulk', - onSettled: (result: SinkWriteSettlement) => void = () => {} + onSettled: (result: DispatcherWriterSettlement) => void = () => {} ): boolean { if (this.disposed || client.closed) { return false @@ -83,7 +83,7 @@ export abstract class RelayDispatcherProducerTransport extends RelayDispatcherRp client: RelayClient, frame: PreparedRelayFrame, lane: 'interactive' | 'ordinary' | 'fixed-bulk' | 'bulk', - onSettled: (result: SinkWriteSettlement) => void = () => {} + onSettled: (result: DispatcherWriterSettlement) => void = () => {} ): boolean { const bytes = frame.frameBytes const fixedBlocked = @@ -148,7 +148,7 @@ export abstract class RelayDispatcherProducerTransport extends RelayDispatcherRp frame: PreparedRelayFrame, lane: 'interactive' | 'ordinary' | 'fixed-bulk' | 'bulk', lease: LegacyPublicationLease, - onSettled: (result: SinkWriteSettlement) => void = () => {} + onSettled: (result: DispatcherWriterSettlement) => void = () => {} ): boolean { const accepted = this.enqueuePreparedFrame(client, frame, lane, (result) => { lease.release() diff --git a/src/relay/dispatcher-pty-publication.ts b/src/relay/dispatcher-pty-publication.ts index 40eb149f0f2..f623e009a32 100644 --- a/src/relay/dispatcher-pty-publication.ts +++ b/src/relay/dispatcher-pty-publication.ts @@ -1,4 +1,4 @@ -import type { SinkWriteSettlement } from './dispatcher-client-writer' +import type { DispatcherWriterSettlement } from './dispatcher-client-writer' import type { JsonRpcNotification } from './protocol' import { RelayDispatcherProducerCapacity } from './dispatcher-producer-capacity' @@ -59,7 +59,7 @@ export abstract class RelayDispatcherPtyPublication extends RelayDispatcherProdu tryNotifyPtyDataToClient( clientId: number, params: Record, - onSettled: (result: SinkWriteSettlement) => void + onSettled: (result: DispatcherWriterSettlement) => void ): boolean { if (this.disposed) { onSettled({ ok: false, error: new Error('Relay dispatcher is disposed') }) @@ -128,7 +128,7 @@ export abstract class RelayDispatcherPtyPublication extends RelayDispatcherProdu tryNotifyPtyExitToClient( clientId: number, params: Record, - onSettled: (result: SinkWriteSettlement) => void + onSettled: (result: DispatcherWriterSettlement) => void ): boolean { if (this.disposed) { onSettled({ ok: false, error: new Error('Relay dispatcher is disposed') }) diff --git a/src/relay/dispatcher-rpc-routing.ts b/src/relay/dispatcher-rpc-routing.ts index a6e6c24190c..e4a25bd1b2f 100644 --- a/src/relay/dispatcher-rpc-routing.ts +++ b/src/relay/dispatcher-rpc-routing.ts @@ -15,7 +15,7 @@ import { } from './protocol' import { DISPATCHER_CONTROL_QUEUE_MAX_BYTES, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher-client-writer' import { RELAY_TO_CLIENT_REQUEST_TIMEOUT_MS, @@ -83,9 +83,9 @@ export abstract class RelayDispatcherRpcRouting extends RelayDispatcherFrameCode client.id, req.id ) - const responseSettledHandlers = new Set<(result: SinkWriteSettlement) => void>() + const responseSettledHandlers = new Set<(result: DispatcherWriterSettlement) => void>() let responseSettled = false - const settleResponse = (result: SinkWriteSettlement): void => { + const settleResponse = (result: DispatcherWriterSettlement): void => { if (responseSettled) { return } @@ -193,7 +193,7 @@ export abstract class RelayDispatcherRpcRouting extends RelayDispatcherFrameCode id: number, result?: unknown, error?: { code: number; message: string; data?: unknown }, - onSettled: (result: SinkWriteSettlement) => void = () => {} + onSettled: (result: DispatcherWriterSettlement) => void = () => {} ): boolean { const msg: JsonRpcResponse = { jsonrpc: '2.0', diff --git a/src/relay/dispatcher-writer-sink.ts b/src/relay/dispatcher-writer-sink.ts index ef6c1349134..724ad5bde3f 100644 --- a/src/relay/dispatcher-writer-sink.ts +++ b/src/relay/dispatcher-writer-sink.ts @@ -4,11 +4,9 @@ import { type DispatcherWriterSettlement } from './dispatcher-writer-admission' -export type SinkWriteSettlement = DispatcherWriterSettlement - export type RelayClientWrite = ( data: Buffer, - onSettled: (result: SinkWriteSettlement) => void + onSettled: (result: DispatcherWriterSettlement) => void ) => boolean | void export type RelayClientSinkOptions = { diff --git a/src/relay/dispatcher.test.ts b/src/relay/dispatcher.test.ts index 280f9351200..ed74bcaa27f 100644 --- a/src/relay/dispatcher.test.ts +++ b/src/relay/dispatcher.test.ts @@ -1,5 +1,5 @@ import { describe, expect, it, vi, beforeEach, afterEach } from 'vitest' -import { RelayDispatcher, type SinkWriteSettlement } from './dispatcher' +import { RelayDispatcher, type DispatcherWriterSettlement } from './dispatcher' import { relayWriterControlReserve } from './dispatcher-writer-admission' import { encodeJsonRpcFrame, @@ -547,7 +547,7 @@ describe('RelayDispatcher', () => { }) it('keeps a saturated legacy primary as required backpressure', () => { - const callbacks: ((result: SinkWriteSettlement) => void)[] = [] + const callbacks: ((result: DispatcherWriterSettlement) => void)[] = [] const legacyDispatcher = new RelayDispatcher( (_data, settle) => { callbacks.push(settle) @@ -730,13 +730,13 @@ describe('RelayDispatcher', () => { client: object, msg: JsonRpcNotification, lane: string, - onSettled?: (result: SinkWriteSettlement) => void + onSettled?: (result: DispatcherWriterSettlement) => void ) => boolean enqueuePreparedFrame: ( client: object, frame: object, lane: string, - onSettled?: (result: SinkWriteSettlement) => void + onSettled?: (result: DispatcherWriterSettlement) => void ) => boolean } diff --git a/src/relay/dispatcher.ts b/src/relay/dispatcher.ts index dbf70abd906..e5b8d786c47 100644 --- a/src/relay/dispatcher.ts +++ b/src/relay/dispatcher.ts @@ -3,7 +3,7 @@ import { RelayDispatcherNotificationPublication } from './dispatcher-notificatio export type { RelayClientSinkOptions, RelayClientWrite, - SinkWriteSettlement + DispatcherWriterSettlement } from './dispatcher-client-writer' export type { MethodHandler, diff --git a/src/relay/fs-handler-git-fallback.ts b/src/relay/fs-handler-git-fallback.ts index 6e1144cdf4f..973322fad6d 100644 --- a/src/relay/fs-handler-git-fallback.ts +++ b/src/relay/fs-handler-git-fallback.ts @@ -9,7 +9,8 @@ import { SearchSubprocessLineAccumulator } from '../shared/search-subprocess-lines' import { spawn } from 'node:child_process' import { fileListingCancellationError } from '../shared/file-listing-cancellation' -import type { SearchOptions, SearchResult } from './fs-handler-utils' +import type { SearchResult } from '../shared/code-search-types' +import type { SearchOptions } from './fs-handler-utils' import { buildGitLsFilesArgsForQuickOpen, shouldExcludeQuickOpenRelPath, diff --git a/src/relay/fs-handler-utils.ts b/src/relay/fs-handler-utils.ts index 7d501e56d66..30ac10ebcf3 100644 --- a/src/relay/fs-handler-utils.ts +++ b/src/relay/fs-handler-utils.ts @@ -16,7 +16,7 @@ import { SEARCH_TIMEOUT_MS as SHARED_SEARCH_TIMEOUT_MS } from '../shared/text-search' import { IMAGE_FILE_MIME_TYPES } from '../shared/image-file-extensions' -import type { SearchResult as SharedSearchResult } from '../shared/code-search-types' +import type { SearchResult } from '../shared/code-search-types' import { absorbPendingRipgrepSpawnError, isRipgrepUnavailableAfterLaunchFailure, @@ -78,8 +78,6 @@ export type SearchOptions = { maxResults: number } -export type SearchResult = SharedSearchResult - // ─── rg-based search ───────────────────────────────────────────────── /** diff --git a/src/relay/fs-stream-pty-echo-backpressure.integration.test.ts b/src/relay/fs-stream-pty-echo-backpressure.integration.test.ts index 98d95fd7f8a..af1422d4499 100644 --- a/src/relay/fs-stream-pty-echo-backpressure.integration.test.ts +++ b/src/relay/fs-stream-pty-echo-backpressure.integration.test.ts @@ -28,7 +28,7 @@ import { import { readFileViaStream } from '../main/ssh/ssh-filesystem-stream-reader' import { RelayDispatcher } from './dispatcher' -import type { SinkWriteSettlement } from './dispatcher' +import type { DispatcherWriterSettlement } from './dispatcher' import { RelayContext } from './context' import { FsHandler } from './fs-handler' import { STREAM_CHUNK_SIZE } from './protocol' @@ -98,7 +98,7 @@ function createHarness(opts: { congested: boolean }): Harness { const outQueue: { data: Buffer - settle: (result: SinkWriteSettlement) => void + settle: (result: DispatcherWriterSettlement) => void }[] = [] let queuedBytes = 0 const drainWaiters = new Set<() => void>() diff --git a/src/relay/git-response-pty-echo-backpressure.integration.test.ts b/src/relay/git-response-pty-echo-backpressure.integration.test.ts index 4f3eee4a87a..a070a3b166d 100644 --- a/src/relay/git-response-pty-echo-backpressure.integration.test.ts +++ b/src/relay/git-response-pty-echo-backpressure.integration.test.ts @@ -28,7 +28,7 @@ import { import { requestGitStreamable } from '../main/ssh/ssh-git-response-stream-reader' import { RelayDispatcher } from './dispatcher' -import type { SinkWriteSettlement } from './dispatcher' +import type { DispatcherWriterSettlement } from './dispatcher' import { RelayContext } from './context' import { GitHandler } from './git-handler' import { GIT_RESPONSE_CHUNK_SIZE } from './protocol' @@ -94,7 +94,7 @@ function createHarness(opts: { congested: boolean }): Harness { const outQueue: { data: Buffer - settle: (result: SinkWriteSettlement) => void + settle: (result: DispatcherWriterSettlement) => void }[] = [] let queuedBytes = 0 const drainWaiters = new Set<() => void>() diff --git a/src/relay/pty-handler-source-publication.test.ts b/src/relay/pty-handler-source-publication.test.ts index 71e8c12b48f..bddd80119b3 100644 --- a/src/relay/pty-handler-source-publication.test.ts +++ b/src/relay/pty-handler-source-publication.test.ts @@ -3,7 +3,7 @@ import { PTY_STARTUP_INGRESS_VERSION } from '../shared/pty-startup-ingress' import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { PtyHandler } from './pty-handler' @@ -56,15 +56,15 @@ describe('PtyHandler negotiated source publication', () => { let originalPlatform: PropertyDescriptor | undefined let writes: Buffer[] let heldResponseId: number | null - let heldResponseSettlements: ((result: SinkWriteSettlement) => void)[] + let heldResponseSettlements: ((result: DispatcherWriterSettlement) => void)[] let adapter: SshPtyConsumerSessionAdapter let pausePty: ReturnType let exitCallback: ((event: { exitCode: number }) => void) | undefined let destroyPty: ReturnType let holdDataSettlements: boolean - let heldDataSettlements: ((result: SinkWriteSettlement) => void)[] + let heldDataSettlements: ((result: DispatcherWriterSettlement) => void)[] let holdExitSettlements: boolean - let heldExitSettlements: ((result: SinkWriteSettlement) => void)[] + let heldExitSettlements: ((result: DispatcherWriterSettlement) => void)[] let highWaterMark: number | undefined beforeEach(async () => { @@ -225,7 +225,7 @@ describe('PtyHandler negotiated source publication', () => { }) async function attachSubscriber( - holdDataSettlement?: (settle: (result: SinkWriteSettlement) => void) => boolean + holdDataSettlement?: (settle: (result: DispatcherWriterSettlement) => void) => boolean ): Promise { const subscriberWrites: Buffer[] = [] const clientId = dispatcher.attachClient( diff --git a/src/relay/pty-source-credit-retention.ts b/src/relay/pty-source-credit-retention.ts index 742a660e374..4fbd415d8fa 100644 --- a/src/relay/pty-source-credit-retention.ts +++ b/src/relay/pty-source-credit-retention.ts @@ -6,8 +6,6 @@ export type PtySourceCreditRetentionSnapshot = Readonly<{ spans: number }> -type RecordRetention = PtySourceCreditRetentionSnapshot - export class PtySourceCreditRetention { private sourceSuTotal = 0 private dataBytesTotal = 0 @@ -53,14 +51,14 @@ export class PtySourceCreditRetention { }) } - private apply(retention: RecordRetention, direction: 1 | -1): void { + private apply(retention: PtySourceCreditRetentionSnapshot, direction: 1 | -1): void { this.sourceSuTotal += direction * retention.sourceSu this.dataBytesTotal += direction * retention.dataBytes this.spansTotal += direction * retention.spans } } -function recordRetention(record: DeliveryRecord): RecordRetention { +function recordRetention(record: DeliveryRecord): PtySourceCreditRetentionSnapshot { return { sourceSu: record.receivedEndSu - record.creditedEndSu, dataBytes: record.retainedDataBytes, diff --git a/src/relay/relay-pty-consumer-owner-displacement.test.ts b/src/relay/relay-pty-consumer-owner-displacement.test.ts index 155abc542f0..21017dceb4a 100644 --- a/src/relay/relay-pty-consumer-owner-displacement.test.ts +++ b/src/relay/relay-pty-consumer-owner-displacement.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { SshPtyConsumerSessionAdapter } from './ssh-pty-consumer-session-adapter' @@ -160,7 +160,7 @@ describe('relay PTY consumer owner displacement', () => { }) const reconnectWrites: Buffer[] = [] - let grantSettlement: ((result: SinkWriteSettlement) => void) | undefined + let grantSettlement: ((result: DispatcherWriterSettlement) => void) | undefined const reconnectClientId = dispatcher.attachClient( (data, settle) => { reconnectWrites.push(Buffer.from(data)) @@ -215,7 +215,7 @@ describe('relay PTY consumer owner displacement', () => { const incumbentGrant = response(incumbentWrites, 1)!.result as Record const reconnectWrites: Buffer[] = [] - let grantSettlement: ((result: SinkWriteSettlement) => void) | undefined + let grantSettlement: ((result: DispatcherWriterSettlement) => void) | undefined const reconnectClientId = dispatcher.attachClient( (data, settle) => { reconnectWrites.push(Buffer.from(data)) @@ -300,7 +300,7 @@ describe('relay PTY consumer owner displacement', () => { ownerLease: incumbentGrant.ownerLease } - let firstReconnectSettlement: ((result: SinkWriteSettlement) => void) | undefined + let firstReconnectSettlement: ((result: DispatcherWriterSettlement) => void) | undefined const firstReconnectWrites: Buffer[] = [] const firstReconnectClientId = dispatcher.attachClient( (data, settle) => { diff --git a/src/relay/relay-pty-publication-admission.test.ts b/src/relay/relay-pty-publication-admission.test.ts index d1cb9551cfe..6bb1ac68815 100644 --- a/src/relay/relay-pty-publication-admission.test.ts +++ b/src/relay/relay-pty-publication-admission.test.ts @@ -3,7 +3,7 @@ import { RelayDispatcher, type RelayClientSessionIdentity, type RelayClientSinkOptions, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourcePublication } from './relay-pty-source-publication' @@ -79,7 +79,7 @@ class SaturatedSink { } } - write = (data: Buffer, onSettled: (result: SinkWriteSettlement) => void): boolean => { + write = (data: Buffer, onSettled: (result: DispatcherWriterSettlement) => void): boolean => { this.writes.push(Buffer.from(data)) this.writableBytes += data.length onSettled({ ok: true }) @@ -111,7 +111,7 @@ describe('relay PTY publication admission', () => { dispatcher = new RelayDispatcher(sink.write, sink.options, endpointIdentity) sink.saturateNext = true dispatcher.notify('test.blocker') - const settled = vi.fn<(result: SinkWriteSettlement) => void>() + const settled = vi.fn<(result: DispatcherWriterSettlement) => void>() const legacyData = 'x'.repeat(1024 * 1024 + 128) expect(dispatcher.tryNotifyPtyDataToClient(1, { id: 'pty-1', data: legacyData }, settled)).toBe( @@ -170,7 +170,7 @@ describe('relay PTY publication admission', () => { const publication = new RelayPtySourcePublication(dispatcher, adapter, () => {}) dispatcher.feed(openFrame(1, 'session-owner', true)) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { diff --git a/src/relay/relay-pty-source-cancellation-exit.test.ts b/src/relay/relay-pty-source-cancellation-exit.test.ts index 8a1f5d39ebb..a4b822ab3ef 100644 --- a/src/relay/relay-pty-source-cancellation-exit.test.ts +++ b/src/relay/relay-pty-source-cancellation-exit.test.ts @@ -3,7 +3,7 @@ import { PTY_CONSUMER_OWNER_GRACE_MS } from '../shared/pty-consumer-session' import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourcePublication } from './relay-pty-source-publication' @@ -58,7 +58,7 @@ describe('RelayPtySourcePublication cancellation and exit', () => { async function createHarness(windowSu = 8, holdExitSettlement = false) { const writes: Buffer[] = [] - const exitSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const exitSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const capacityIds: string[] = [] dispatcher = new RelayDispatcher( (data, onSettled) => { @@ -93,7 +93,7 @@ describe('RelayPtySourcePublication cancellation and exit', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: 1, @@ -116,7 +116,7 @@ describe('RelayPtySourcePublication cancellation and exit', () => { } function activateOwner(publication: RelayPtySourcePublication): void { - const settlements: ((result: SinkWriteSettlement) => void)[] = [] + const settlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: 1, diff --git a/src/relay/relay-pty-source-publication.test.ts b/src/relay/relay-pty-source-publication.test.ts index af75613debd..e5c1833beac 100644 --- a/src/relay/relay-pty-source-publication.test.ts +++ b/src/relay/relay-pty-source-publication.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourcePublication } from './relay-pty-source-publication' @@ -58,8 +58,8 @@ describe('RelayPtySourcePublication', () => { holdExitSettlement = false ) { const writes: Buffer[] = [] - const sourceSettlements: ((result: SinkWriteSettlement) => void)[] = [] - const exitSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const sourceSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] + const exitSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const capacityIds: string[] = [] dispatcher = new RelayDispatcher( (data, onSettled) => { @@ -103,7 +103,7 @@ describe('RelayPtySourcePublication', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: 1, @@ -298,7 +298,7 @@ describe('RelayPtySourcePublication', () => { const detached: number[] = [] const saturatedWrites: Buffer[] = [] const healthyWrites: Buffer[] = [] - const heldSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const heldSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] let saturateSubscriber = false dispatcher!.onClientDetached((clientId) => detached.push(clientId)) const saturatedId = dispatcher!.attachClient( @@ -501,7 +501,7 @@ describe('RelayPtySourcePublication', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( harness.publication.activate( 'pty-1', @@ -578,7 +578,7 @@ describe('RelayPtySourcePublication', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const activation = harness.publication.activate( 'pty-1', 'incarnation-1', @@ -654,8 +654,8 @@ describe('RelayPtySourcePublication', () => { dispatcher!.invalidateClient() const recoveredWrites: Buffer[] = [] - const recoverySettlements: ((result: SinkWriteSettlement) => void)[] = [] - const completionSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const recoverySettlements: ((result: DispatcherWriterSettlement) => void)[] = [] + const completionSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const recoveredClientId = dispatcher!.attachClient( (data, onSettled) => { recoveredWrites.push(Buffer.from(data)) @@ -686,7 +686,7 @@ describe('RelayPtySourcePublication', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const activation = harness.publication.activate( 'pty-1', 'incarnation-1', diff --git a/src/relay/relay-pty-source-recovery-completion.test.ts b/src/relay/relay-pty-source-recovery-completion.test.ts index ba5f8d6787f..b884fbd88a7 100644 --- a/src/relay/relay-pty-source-recovery-completion.test.ts +++ b/src/relay/relay-pty-source-recovery-completion.test.ts @@ -1,5 +1,5 @@ import { expect, it } from 'vitest' -import type { RelayDispatcher, SinkWriteSettlement } from './dispatcher' +import type { RelayDispatcher, DispatcherWriterSettlement } from './dispatcher' import type { SshPtyConsumerSessionAdapter } from './ssh-pty-consumer-session-adapter' import { RelayPtySourceSendScheduler, @@ -10,7 +10,7 @@ it('retries an unadmitted recovery completion once capacity returns', () => { let capacityListener = () => {} let listenerRemoved = false let admissions = 0 - const completionSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const completionSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const dispatcher = { onLegacyPtyCapacity(listener: () => void) { capacityListener = listener @@ -22,7 +22,7 @@ it('retries an unadmitted recovery completion once capacity returns', () => { _clientId: number, _method: string, _params: Record, - onSettled: (result: SinkWriteSettlement) => void + onSettled: (result: DispatcherWriterSettlement) => void ) { admissions++ if (admissions === 1) { diff --git a/src/relay/relay-pty-source-recovery-completion.ts b/src/relay/relay-pty-source-recovery-completion.ts index bc4c79e1be6..2bc7b888ce1 100644 --- a/src/relay/relay-pty-source-recovery-completion.ts +++ b/src/relay/relay-pty-source-recovery-completion.ts @@ -1,10 +1,10 @@ -import type { RelayDispatcher, SinkWriteSettlement } from './dispatcher' +import type { RelayDispatcher, DispatcherWriterSettlement } from './dispatcher' import type { RelayPtySourceDeliveryRecord } from './relay-pty-source-send-scheduler' import type { SshPtyConsumerSessionAdapter } from './ssh-pty-consumer-session-adapter' function onceSettlement( - callback: (result: SinkWriteSettlement) => void -): (result: SinkWriteSettlement) => void { + callback: (result: DispatcherWriterSettlement) => void +): (result: DispatcherWriterSettlement) => void { let settled = false return (result) => { if (settled) { diff --git a/src/relay/relay-pty-source-recovery-interleavings.test.ts b/src/relay/relay-pty-source-recovery-interleavings.test.ts index 0c35b25c1a7..06ee2efd63a 100644 --- a/src/relay/relay-pty-source-recovery-interleavings.test.ts +++ b/src/relay/relay-pty-source-recovery-interleavings.test.ts @@ -4,7 +4,7 @@ import type { PtySourceRecoveryRequest } from '../shared/pty-source-recovery-con import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourceCreditLedger } from './pty-source-credit-ledger' @@ -115,7 +115,7 @@ describe('relay PTY source recovery interleavings', () => { it('retries a failed exit publication after exact owner recovery', async () => { const primaryWrites: Buffer[] = [] - let exitSettlement: ((result: SinkWriteSettlement) => void) | undefined + let exitSettlement: ((result: DispatcherWriterSettlement) => void) | undefined dispatcher = new RelayDispatcher( (data, settle) => { primaryWrites.push(Buffer.from(data)) @@ -143,7 +143,7 @@ describe('relay PTY source recovery interleavings', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] publication.activate('pty-1', 'incarnation-1', { clientId: 1, isStale: () => false, @@ -197,7 +197,7 @@ describe('relay PTY source recovery interleavings', () => { }) ) await flushRequests() - const recoveredActivationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const recoveredActivationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const recovered = publication.activate( 'pty-1', 'incarnation-1', @@ -261,7 +261,7 @@ describe('relay PTY source recovery interleavings', () => { }) ) await flushRequests() - const activationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const activationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: 1, @@ -309,7 +309,7 @@ describe('relay PTY source recovery interleavings', () => { ptyIncarnation: 'incarnation-1', acceptedSourceEndSu: 4 } - const firstActivationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const firstActivationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const firstRecovery = publication.activate( 'pty-1', 'incarnation-1', @@ -333,7 +333,7 @@ describe('relay PTY source recovery interleavings', () => { publication.onCreditAvailable('pty-1') expect(recoveredWrites).toHaveLength(writesBeforeRetry) - const retryActivationSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const retryActivationSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const retriedRecovery = publication.activate( 'pty-1', 'incarnation-1', @@ -376,7 +376,7 @@ describe('relay PTY source recovery interleavings', () => { }) ) await flushRequests() - const firstActivation: ((result: SinkWriteSettlement) => void)[] = [] + const firstActivation: ((result: DispatcherWriterSettlement) => void)[] = [] publication.activate('pty-1', 'incarnation-1', { clientId: 1, isStale: () => false, @@ -391,8 +391,8 @@ describe('relay PTY source recovery interleavings', () => { dispatcher.invalidateClient() const recoveredWrites: Buffer[] = [] - let recoveryDataSettlement: ((result: SinkWriteSettlement) => void) | undefined - let failedCompletionSettlement: ((result: SinkWriteSettlement) => void) | undefined + let recoveryDataSettlement: ((result: DispatcherWriterSettlement) => void) | undefined + let failedCompletionSettlement: ((result: DispatcherWriterSettlement) => void) | undefined const recoveredClientId = dispatcher.attachClient( (data, settle) => { recoveredWrites.push(Buffer.from(data)) @@ -424,7 +424,7 @@ describe('relay PTY source recovery interleavings', () => { ) await flushRequests() const recoveredGrant = responseResult(recoveredWrites, 2)! - const recoveredActivation: ((result: SinkWriteSettlement) => void)[] = [] + const recoveredActivation: ((result: DispatcherWriterSettlement) => void)[] = [] publication.activate( 'pty-1', 'incarnation-1', @@ -453,7 +453,7 @@ describe('relay PTY source recovery interleavings', () => { failedCompletionSettlement!({ ok: false, error: new Error('completion write failed') }) const replacementWrites: Buffer[] = [] - let replacementCompletionSettlement: ((result: SinkWriteSettlement) => void) | undefined + let replacementCompletionSettlement: ((result: DispatcherWriterSettlement) => void) | undefined const replacementClientId = dispatcher.attachClient( (data, settle) => { replacementWrites.push(Buffer.from(data)) @@ -481,7 +481,7 @@ describe('relay PTY source recovery interleavings', () => { }) ) await flushRequests() - const replacementActivation: ((result: SinkWriteSettlement) => void)[] = [] + const replacementActivation: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate( 'pty-1', diff --git a/src/relay/relay-pty-source-restore-retry.test.ts b/src/relay/relay-pty-source-restore-retry.test.ts index 0eaf29d61c0..2c785343725 100644 --- a/src/relay/relay-pty-source-restore-retry.test.ts +++ b/src/relay/relay-pty-source-restore-retry.test.ts @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from 'vitest' import { RelayDispatcher, type RelayClientSessionIdentity, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourcePublication } from './relay-pty-source-publication' @@ -72,7 +72,7 @@ describe('relay PTY source restore retry', () => { }) ) await flushRequests() - const initialSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const initialSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: 1, @@ -112,7 +112,7 @@ describe('relay PTY source restore retry', () => { }) ) await flushRequests() - const restoreSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const restoreSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate( 'pty-1', @@ -139,7 +139,7 @@ describe('relay PTY source restore retry', () => { } expect(publication.accepts('pty-1')).toBe(false) - const retrySettlements: ((result: SinkWriteSettlement) => void)[] = [] + const retrySettlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect( publication.activate('pty-1', 'incarnation-1', { clientId: recoveredClientId, diff --git a/src/relay/relay-pty-source-send-scheduler.ts b/src/relay/relay-pty-source-send-scheduler.ts index bbec1a824d3..d2f7ad68a38 100644 --- a/src/relay/relay-pty-source-send-scheduler.ts +++ b/src/relay/relay-pty-source-send-scheduler.ts @@ -4,7 +4,7 @@ import type { } from '../shared/pty-source-credit-contract' import type { PtySourceRecoveryCheckpoint } from '../shared/pty-source-recovery-contract' import type { PtySourceReceivingActivation } from '../shared/pty-source-receiving-activation' -import type { RelayDispatcher, SinkWriteSettlement } from './dispatcher' +import type { RelayDispatcher, DispatcherWriterSettlement } from './dispatcher' import { PTY_SOURCE_SCHEDULER_MAX_FRAMES, PTY_SOURCE_SCHEDULER_MAX_SU @@ -47,8 +47,8 @@ export type RelayPtySourcePublicationCounters = { const PTY_SOURCE_FRAME_MAX_SU = 16 * 1024 export function onceSinkSettlement( - callback: (result: SinkWriteSettlement) => void -): (result: SinkWriteSettlement) => void { + callback: (result: DispatcherWriterSettlement) => void +): (result: DispatcherWriterSettlement) => void { let settled = false return (result) => { if (settled) { diff --git a/src/relay/relay-pty-source-superseded-activation.test.ts b/src/relay/relay-pty-source-superseded-activation.test.ts index 759bbbef1f5..522b80a5272 100644 --- a/src/relay/relay-pty-source-superseded-activation.test.ts +++ b/src/relay/relay-pty-source-superseded-activation.test.ts @@ -3,7 +3,7 @@ import { RelayDispatcher, type RelayClientSessionIdentity, type RequestContext, - type SinkWriteSettlement + type DispatcherWriterSettlement } from './dispatcher' import { encodeJsonRpcFrame, MessageType } from './protocol' import { RelayPtySourcePublication } from './relay-pty-source-publication' @@ -71,7 +71,7 @@ describe('PTY source activation from a superseded owner', () => { function contextFor( clientId: number, - settlements: ((result: SinkWriteSettlement) => void)[] + settlements: ((result: DispatcherWriterSettlement) => void)[] ): RequestContext { return { clientId, @@ -88,7 +88,7 @@ describe('PTY source activation from a superseded owner', () => { */ it('leaves the replacement delivery intact when the superseded owner re-activates', async () => { const { publication, adapter, writes } = await createHarness() - const settlements: ((result: SinkWriteSettlement) => void)[] = [] + const settlements: ((result: DispatcherWriterSettlement) => void)[] = [] expect(publication.activate('pty-1', 'incarnation-1', contextFor(1, settlements))).toBe( 'opened' ) @@ -124,7 +124,7 @@ describe('PTY source activation from a superseded owner', () => { ) await flushRequests() - const replacementSettlements: ((result: SinkWriteSettlement) => void)[] = [] + const replacementSettlements: ((result: DispatcherWriterSettlement) => void)[] = [] const recovery = { status: 'checkpoint' as const, clientGeneration: activation.clientGeneration, diff --git a/src/relay/workspace-space-scan.ts b/src/relay/workspace-space-scan.ts index 73a68454833..26beb7d9f6c 100644 --- a/src/relay/workspace-space-scan.ts +++ b/src/relay/workspace-space-scan.ts @@ -26,8 +26,6 @@ const DU_TIMEOUT_MS = 120_000 const DU_MAX_BUFFER_BYTES = 16 * 1024 * 1024 const execFileAsync = promisify(execFile) -type ScanStats = WorkspaceSpaceEntryScan - class RelayWorkspaceSpaceScanCancelledError extends Error { constructor() { super('Workspace space scan cancelled') @@ -77,7 +75,7 @@ async function readDuDepthOne( return parseDuDepthOneOutput(stdout) } -function toWorkspaceSpaceItem(stats: ScanStats): WorkspaceSpaceItem { +function toWorkspaceSpaceItem(stats: WorkspaceSpaceEntryScan): WorkspaceSpaceItem { return { name: stats.name, path: stats.path, @@ -91,7 +89,7 @@ async function scanTopLevelEntryWithDu( name: string, duSizes: Map, context: RequestContext -): Promise { +): Promise { throwIfCancelled(context) const stats = await lstat(entryPath) throwIfCancelled(context) @@ -129,7 +127,7 @@ async function scanEntryAggregate( entryPath: string, name: string, context: RequestContext -): Promise { +): Promise { return scanWorkspaceSpaceEntryTree({ rootPath: entryPath, rootName: name, @@ -182,7 +180,7 @@ async function scanDirectoryWithDu( const childStats = await mapWithConcurrency( entries, RELAY_FS_CONCURRENCY, - async (entry): Promise => { + async (entry): Promise => { try { return await scanTopLevelEntryWithDu( join(rootPath, entry.name), @@ -198,7 +196,7 @@ async function scanDirectoryWithDu( } } ) - const children = childStats.filter((child): child is ScanStats => child !== null) + const children = childStats.filter((child): child is WorkspaceSpaceEntryScan => child !== null) const compact = compactWorkspaceSpaceItems(children.map(toWorkspaceSpaceItem)) return { diff --git a/src/renderer/src/components/GitHubItemDialog.tsx b/src/renderer/src/components/GitHubItemDialog.tsx index fdea4edf9ed..4b5891239f2 100644 --- a/src/renderer/src/components/GitHubItemDialog.tsx +++ b/src/renderer/src/components/GitHubItemDialog.tsx @@ -1,7 +1,7 @@ import GitHubItemDialog from './github-item-dialog/open-dialog/github-item-dialog' export type { ItemDialogTab } from './github/github-work-item-identity' -export type { GitHubItemDialogProjectOrigin } from './github-item-dialog/load-item-details/github-item-dialog-types' +export type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' export { invalidateWorkItemDetailsCacheForKey } from './github-item-dialog/load-item-details/work-item-details-cache' export default GitHubItemDialog diff --git a/src/renderer/src/components/NewWorkspaceComposerCard.tsx b/src/renderer/src/components/NewWorkspaceComposerCard.tsx index 7145bed732c..99ac9459821 100644 --- a/src/renderer/src/components/NewWorkspaceComposerCard.tsx +++ b/src/renderer/src/components/NewWorkspaceComposerCard.tsx @@ -1,3 +1,4 @@ +import type { NeedsSetupProjectHostOption } from '@/lib/project-host-setup-options' import React from 'react' import { useTranslation } from 'react-i18next' import { toast } from 'sonner' @@ -32,7 +33,6 @@ import { EMPTY_EPHEMERAL_VM_RECIPES, EMPTY_PROJECT_HOST_SETUP_OPTIONS, EMPTY_PROJECT_OPTIONS, - type NeedsProjectHostOption, type NewWorkspaceComposerCardProps } from './new-workspace/new-workspace-composer-card-props' import { getSshStatusLabel } from './new-workspace/new-workspace-composer-ssh-status' @@ -95,9 +95,8 @@ export default function NewWorkspaceComposerCard( const branchNameInputId = React.useId() const projectDescriptionId = React.useId() const [addRemoteHostMode, setAddRemoteHostMode] = React.useState(null) - const [setLocationOption, setSetLocationOption] = React.useState( - null - ) + const [setLocationOption, setSetLocationOption] = + React.useState(null) // Why sticky: the dialog animates itself closed off its own `option` prop, so unmounting it // when the option clears would cut that animation short. const [setLocationDialogMounted, setSetLocationDialogMounted] = React.useState(false) @@ -204,7 +203,7 @@ export default function NewWorkspaceComposerCard( openModal('add-repo') }, [onAddProjectOverride, openModal]) const handleSetLocation = React.useCallback( - (option: NeedsProjectHostOption): void => { + (option: NeedsSetupProjectHostOption): void => { setSetLocationDialogMounted(true) setSetLocationOption(option) onNestedDialogOpenChange?.(true) @@ -223,7 +222,7 @@ export default function NewWorkspaceComposerCard( [handleSetLocationClose, onProjectHostSetupChange] ) const handleConnectRunTargetHost = React.useCallback( - async (option: NeedsProjectHostOption): Promise => { + async (option: NeedsSetupProjectHostOption): Promise => { const action = option.connectAction if (!action) { return diff --git a/src/renderer/src/components/PullRequestPage.tsx b/src/renderer/src/components/PullRequestPage.tsx index 12db9c71fd9..49024b48d5a 100644 --- a/src/renderer/src/components/PullRequestPage.tsx +++ b/src/renderer/src/components/PullRequestPage.tsx @@ -1,4 +1,4 @@ export type { ItemDialogTab } from '@/components/github/github-work-item-identity' -export type { PullRequestPageProjectOrigin } from '@/components/pull-request-page/page-types' +export type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' export { invalidateWorkItemDetailsCacheForKey } from '@/components/pull-request-page/cache/work-item-details' export { default } from '@/components/pull-request-page/page/surface' diff --git a/src/renderer/src/components/TerminalWorkbenchContainer.test.tsx b/src/renderer/src/components/TerminalWorkbenchContainer.test.tsx index d944b8537c9..ac2c55da974 100644 --- a/src/renderer/src/components/TerminalWorkbenchContainer.test.tsx +++ b/src/renderer/src/components/TerminalWorkbenchContainer.test.tsx @@ -1,7 +1,7 @@ // @vitest-environment happy-dom import { cleanup, render } from '@testing-library/react' import { afterEach, describe, expect, it, vi } from 'vitest' -import type { BrowserTab as BrowserTabState } from '../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../shared/browser-workspace-types' type MockAppState = { browserTabsByWorktree: Record } diff --git a/src/renderer/src/components/activity/activity-event-state.ts b/src/renderer/src/components/activity/activity-event-state.ts index 85f6af9c828..60bde818632 100644 --- a/src/renderer/src/components/activity/activity-event-state.ts +++ b/src/renderer/src/components/activity/activity-event-state.ts @@ -4,11 +4,7 @@ import { type AgentStatusEntry, type AgentStatusState } from '../../../../shared/agent-status-types' -import type { - ActivityEventState, - ActivityHookLiveAgentState, - ActivityLiveAgentState -} from './activity-thread-types' +import type { ActivityHookLiveAgentState, ActivityLiveAgentState } from './activity-thread-types' function isActivityHookLiveAgentState( state: AgentStatusState @@ -33,6 +29,6 @@ export function freshActivityLiveAgentState( export function isHistoricalActivityState( state: string -): state is Extract { +): state is Extract { return state === 'done' || state === 'blocked' || state === 'waiting' } diff --git a/src/renderer/src/components/activity/activity-pane-events.ts b/src/renderer/src/components/activity/activity-pane-events.ts index 9170bcf8898..39a09b5cf96 100644 --- a/src/renderer/src/components/activity/activity-pane-events.ts +++ b/src/renderer/src/components/activity/activity-pane-events.ts @@ -1,16 +1,13 @@ import { isHistoricalActivityState } from './activity-event-state' import type { AgentStateHistoryEntry, - AgentStatusEntry + AgentStatusEntry, + AgentStatusState } from '../../../../shared/agent-status-types' import type { Repo } from '../../../../shared/repo-types' import type { TerminalTab } from '../../../../shared/terminal-tab-types' import type { Worktree } from '../../../../shared/worktree/types' -import type { - ActivityEvent, - ActivityEventState, - ActivityLiveAgentState -} from './activity-thread-types' +import type { ActivityEvent, ActivityLiveAgentState } from './activity-thread-types' import { EVENTS_PER_PANE_CAP } from './activity-event-cap' function historyEntrySnapshot( @@ -62,7 +59,7 @@ type PaneEventInputs = { export function buildPaneActivityEvents(args: PaneEventInputs): ActivityEvent[] { const events: ActivityEvent[] = [] const seenIds = new Set() - const append = (state: ActivityEventState, timestamp: number, entry: AgentStatusEntry): void => { + const append = (state: AgentStatusState, timestamp: number, entry: AgentStatusEntry): void => { const id = `agent:${entry.paneKey}:${state}:${timestamp}` if (seenIds.has(id)) { return @@ -91,7 +88,7 @@ export function buildPaneActivityEvents(args: PaneEventInputs): ActivityEvent[] continue } append( - history.state as ActivityEventState, + history.state as AgentStatusState, history.startedAt, historyEntrySnapshot(args.entry, history) ) diff --git a/src/renderer/src/components/activity/activity-thread-grouping.ts b/src/renderer/src/components/activity/activity-thread-grouping.ts index cab4fbcf8c3..89a0ffe573b 100644 --- a/src/renderer/src/components/activity/activity-thread-grouping.ts +++ b/src/renderer/src/components/activity/activity-thread-grouping.ts @@ -10,14 +10,13 @@ import { agentSummary, agentTitle, threadAgentState, - threadAgentStateLabel, - type ActivityThreadStatusId + threadAgentStateLabel } from './activity-thread-presentation' import type { ActivityGroupBy, ActivityThreadGroup, AgentPaneThread } from './activity-thread-types' // Attention-first. Exhaustive Record so an unranked dot state is a type error; ranks are // unique so header order never falls back to thread recency. -const ACTIVITY_STATUS_GROUP_RANK: Record = { +const ACTIVITY_STATUS_GROUP_RANK: Record = { waiting: 0, blocked: 1, permission: 2, diff --git a/src/renderer/src/components/activity/activity-thread-presentation.ts b/src/renderer/src/components/activity/activity-thread-presentation.ts index 1aa27d321dc..0569d03ff54 100644 --- a/src/renderer/src/components/activity/activity-thread-presentation.ts +++ b/src/renderer/src/components/activity/activity-thread-presentation.ts @@ -112,11 +112,9 @@ export function statusPreviewForEntry( return resolveActivityThreadStatusPreview(entry, agentState, previousPreview) } -export type ActivityThreadStatusId = AgentDotState - /** Single classifier behind grouping, labels, and clear-completed; the only place the * interrupted predicate is spelled. */ -export function activityThreadStatusId(thread: AgentPaneThread): ActivityThreadStatusId { +export function activityThreadStatusId(thread: AgentPaneThread): AgentDotState { const state = thread.currentAgentState ?? thread.latestEvent?.state ?? 'done' if (!thread.currentAgentState && state === 'done' && thread.latestEvent?.entry.interrupted) { return 'interrupted' diff --git a/src/renderer/src/components/activity/activity-thread-types.ts b/src/renderer/src/components/activity/activity-thread-types.ts index 7d32f1a2b39..8ddbddb9c0f 100644 --- a/src/renderer/src/components/activity/activity-thread-types.ts +++ b/src/renderer/src/components/activity/activity-thread-types.ts @@ -11,7 +11,6 @@ import type { ActivityPortalReadinessStatus } from './activity-portal-readiness- export type { ActivityGroupBy, ThreadReadFilter } from '../../../../shared/ui-chrome-types' -export type ActivityEventState = AgentStatusState export type ActivityHookLiveAgentState = Extract< AgentStatusState, 'working' | 'blocked' | 'waiting' @@ -19,7 +18,7 @@ export type ActivityHookLiveAgentState = Extract< export type ActivityLiveAgentState = ActivityHookLiveAgentState | 'monitoring' export type ActivityEvent = { id: string - state: ActivityEventState + state: AgentStatusState timestamp: number worktree: Worktree repo: Repo | null diff --git a/src/renderer/src/components/automations/automation-host-scheduler.ts b/src/renderer/src/components/automations/automation-host-scheduler.ts index 310e2bd0c8b..c9d94d05ff5 100644 --- a/src/renderer/src/components/automations/automation-host-scheduler.ts +++ b/src/renderer/src/components/automations/automation-host-scheduler.ts @@ -100,8 +100,6 @@ export type AutomationHostScheduler = { dispose: () => void } -type PlannedTarget = PlannedAutomationHostTarget - function scopeSelectorFor(target: AutomationHostFetchTarget): AutomationListScopeSelector | null { const selector = target.ref.selector if (selector.kind === 'self' || selector.kind === 'orphan') { @@ -148,7 +146,7 @@ export function createAutomationHostScheduler( retry: (target) => void refresh([target], { force: true }) }) - const stillCurrent = (target: PlannedTarget): boolean => + const stillCurrent = (target: PlannedAutomationHostTarget): boolean => !disposed && cache.getByKey(target.stableKey)?.requestGeneration === target.fence.requestGeneration @@ -160,10 +158,10 @@ export function createAutomationHostScheduler( * response; it is one response and one stale entry. */ const settleGroup = ( - live: readonly PlannedTarget[], + live: readonly PlannedAutomationHostTarget[], stableKey: string | null, outcome: 'commit' | 'failure', - apply: (target: PlannedTarget) => boolean + apply: (target: PlannedAutomationHostTarget) => boolean ): void => { let landed = 0 for (const target of live) { @@ -178,7 +176,7 @@ export function createAutomationHostScheduler( } } - const runScoped = async (target: PlannedTarget): Promise => { + const runScoped = async (target: PlannedAutomationHostTarget): Promise => { if (!stillCurrent(target)) { return } @@ -205,7 +203,9 @@ export function createAutomationHostScheduler( } } - const runLegacyAuthority = async (targets: readonly PlannedTarget[]): Promise => { + const runLegacyAuthority = async ( + targets: readonly PlannedAutomationHostTarget[] + ): Promise => { const live = targets.filter(stillCurrent) if (live.length === 0) { return @@ -223,7 +223,7 @@ export function createAutomationHostScheduler( options.legacyPartitionContext(live[0].ref.authority), hostStableKey ) - const rowsFor = (target: PlannedTarget): readonly AutomationHostRow[] => + const rowsFor = (target: PlannedAutomationHostTarget): readonly AutomationHostRow[] => partition.rowsByStableKey.get(target.stableKey) ?? [] // The call belongs to the authority, but the rows belong to the hosts — // without this, per-host row counts are missing exactly where payload is worst. @@ -243,7 +243,7 @@ export function createAutomationHostScheduler( } /** Hands back the markers of queued work the pool dropped before sending it. */ - const abandon = (targets: readonly PlannedTarget[]): void => { + const abandon = (targets: readonly PlannedAutomationHostTarget[]): void => { for (const target of targets) { cache.abandonRequest(target.fence) } @@ -291,7 +291,7 @@ export function createAutomationHostScheduler( ) } const fetchable = planned.filter((target) => target.querySupport !== 'incompatible') - const legacyByAuthority = new Map() + const legacyByAuthority = new Map() const submitted: Promise[] = [...joined] for (const target of fetchable) { if (target.querySupport === 'legacy-unscoped') { diff --git a/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.ts b/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.ts index 790065bf60a..769cf121b2d 100644 --- a/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.ts +++ b/src/renderer/src/components/browser-pane/annotate/markup-drawing-model.ts @@ -3,11 +3,9 @@ // live canvas overlay and the final composited PNG. Keeping this canvas-free // makes the undo/redo and geometry logic unit-testable. -export type MarkupToolKind = 'pen' | 'highlight' | 'arrow' | 'rect' | 'ellipse' | 'text' - // Toolbar selection. Draw-only: markup is a throwaway scribble the user copies // once, so there is no select/move/restyle cursor. -export type MarkupTool = MarkupToolKind +export type MarkupTool = 'pen' | 'highlight' | 'arrow' | 'rect' | 'ellipse' | 'text' export type MarkupPoint = { x: number; y: number } diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx index a1931284202..f75cf45e9e5 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserMobileDriverOverlay.tsx @@ -1,11 +1,11 @@ import { useCallback, useRef, useState, type ReactElement } from 'react' import { Button } from '@/components/ui/button' import { cn } from '@/lib/utils' -import type { BrowserDriverState } from '@/lib/pane-manager/browser-mobile-driver-state' +import type { RuntimeBrowserDriverState } from '../../../../../shared/runtime-types' import { translate } from '@/i18n/i18n' type Props = { - driver: BrowserDriverState + driver: RuntimeBrowserDriverState onTakeBack: () => void | Promise } diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.test.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.test.tsx index 8ab52d952b8..825e41ae455 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.test.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.test.tsx @@ -3,7 +3,7 @@ import { cleanup, render } from '@testing-library/react' import { Suspense } from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { BrowserTab as BrowserTabState } from '../../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../../shared/browser-workspace-types' import type { Tab, TabGroup } from '../../../../../shared/tab-types' type MockAppState = { diff --git a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.tsx b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.tsx index b15c8420ff0..e144d6b0850 100644 --- a/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.tsx +++ b/src/renderer/src/components/browser-pane/assemble-chrome/BrowserPaneOverlayLayer.tsx @@ -2,7 +2,7 @@ import { memo, useCallback, useMemo } from 'react' import { registerBrowserOverlaySlotViewport } from '../host-guest/browser-page-viewport' import { useShallow } from 'zustand/react/shallow' import { useAppStore } from '../../../store' -import type { BrowserTab as BrowserTabState } from '../../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../../shared/browser-workspace-types' import type { Tab, TabGroup } from '../../../../../shared/tab-types' import BrowserPane from './browser-workspace-pane' import { DeferredBrowserContent } from './DeferredBrowserContent' diff --git a/src/renderer/src/components/dashboard-popout/AgentDashboardMapView.tsx b/src/renderer/src/components/dashboard-popout/AgentDashboardMapView.tsx index 0f861279438..db143c86202 100644 --- a/src/renderer/src/components/dashboard-popout/AgentDashboardMapView.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentDashboardMapView.tsx @@ -1,6 +1,7 @@ import { Suspense, useMemo, useState, type RefObject } from 'react' import type { DashboardCard, + DashboardRevealAgentArgs, DashboardSleepWorkspaceArgs, DashboardSnapshot, DashboardSpawnAgentArgs @@ -8,7 +9,7 @@ import type { import { cn } from '@/lib/utils' import { lazyWithRetry } from '@/lib/lazy-with-retry' import { AgentDashboardToolbar } from './AgentDashboardToolbar' -import { AgentTerminalPanel, type AgentRevealArgs } from './AgentTerminalDialog' +import { AgentTerminalPanel } from './AgentTerminalDialog' import { EMPTY_DASHBOARD_FILTERS, filterDashboardWorkspaces, @@ -36,7 +37,7 @@ type AgentDashboardMapViewProps = { now: number dialogCard: DashboardCard | null onDialogOpenChange: (open: boolean) => void - onRevealAgent: (args: AgentRevealArgs) => void + onRevealAgent: (args: DashboardRevealAgentArgs) => void onOpenTerminal: (card: DashboardCard) => void onSpawnAgent?: (args: DashboardSpawnAgentArgs) => void onSleepWorkspace?: (args: DashboardSleepWorkspaceArgs) => void diff --git a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx index a0648ff6c88..d070af11903 100644 --- a/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentKanbanBoard.tsx @@ -4,6 +4,7 @@ import { DASHBOARD_BUCKET_ORDER, type DashboardBucket, type DashboardCard, + type DashboardRevealAgentArgs, type DashboardSnapshot } from '../../../../shared/dashboard-snapshot' import type { RepoIcon } from '../../../../shared/repo-icon' @@ -12,7 +13,7 @@ import { TooltipProvider } from '@/components/ui/tooltip' import { installWindowVisibilityInterval } from '@/lib/window-visibility-interval' import { AgentKanbanCard } from './AgentKanbanCard' import { AgentDashboardToolbar } from './AgentDashboardToolbar' -import { AgentTerminalDialog, type AgentRevealArgs } from './AgentTerminalDialog' +import { AgentTerminalDialog } from './AgentTerminalDialog' import { EMPTY_DASHBOARD_FILTERS, filterDashboardCards, @@ -31,7 +32,7 @@ function ackAgentViaPopoutRelay(paneKey: string): void { /** Reveal an agent from the pop-out window: raise the main window and route it * to the agent's pane via IPC. Same `?.` HMR-skew guard as the ack relay — * both channels ship together, so a stale preload lacks both. */ -function revealAgentViaPopoutRelay(args: AgentRevealArgs): void { +function revealAgentViaPopoutRelay(args: DashboardRevealAgentArgs): void { void window.api.dashboard.revealAgent?.(args) } @@ -122,7 +123,7 @@ type AgentKanbanBoardProps = { onAckAgent?: (paneKey: string) => void /** Focuses the agent's pane. Defaults to the pop-out IPC relay; the in-window * host activates the worktree/pane locally and closes the overlay. */ - onRevealAgent?: (args: AgentRevealArgs) => void + onRevealAgent?: (args: DashboardRevealAgentArgs) => void /** When provided, renders a close control in the header (in-window mode). The * pop-out relies on its native window controls, so it omits this. */ onClose?: () => void diff --git a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx index 66bbd580e70..667395c9ee0 100644 --- a/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx +++ b/src/renderer/src/components/dashboard-popout/AgentTerminalDialog.tsx @@ -15,16 +15,13 @@ import { terminalPreviewUnavailableMessage } from './terminal-preview-unavailabl import { translate } from '@/i18n/i18n' import { cn } from '@/lib/utils' -/** Routing payload for focusing an agent's pane in the main window. */ -export type AgentRevealArgs = DashboardRevealAgentArgs - type AgentTerminalDialogProps = { /** The agent shown in the dialog; null renders the dialog closed. */ card: DashboardCard | null onOpenChange: (open: boolean) => void /** Focus the agent's pane. The pop-out relays over IPC; the in-window host * activates the worktree/pane locally. */ - onReveal: (args: AgentRevealArgs) => void + onReveal: (args: DashboardRevealAgentArgs) => void } type AgentTerminalFrameProps = Omit & { diff --git a/src/renderer/src/components/dashboard-popout/agent-map-worktree-packing.ts b/src/renderer/src/components/dashboard-popout/agent-map-worktree-packing.ts index 4dab5ac2346..2c9faabdc4a 100644 --- a/src/renderer/src/components/dashboard-popout/agent-map-worktree-packing.ts +++ b/src/renderer/src/components/dashboard-popout/agent-map-worktree-packing.ts @@ -26,8 +26,6 @@ const CENTER_DIRECTIONS = [ [1, 1] ] as const -type PackableWorktree = AgentMapPackableCircle - type PackingCandidate = { x: number y: number @@ -55,8 +53,8 @@ function hashFraction(value: string): number { } function placedWorktreesOverlap( - candidate: Pick, - placed: PackableWorktree[] + candidate: Pick, + placed: AgentMapPackableCircle[] ): boolean { return placed.some( (worktree) => @@ -68,7 +66,7 @@ function placedWorktreesOverlap( function comparePackingScores( a: PackingCandidate, b: PackingCandidate, - placed: PackableWorktree[] + placed: AgentMapPackableCircle[] ): number { for (const key of ['enclosingRadius', 'distanceFromCenter'] as const) { if (Math.abs(a[key] - b[key]) > SCORE_TOLERANCE) { @@ -88,15 +86,15 @@ function comparePackingScores( : 0 } -function compareBoundaryAnchors(a: PackableWorktree, b: PackableWorktree): number { +function compareBoundaryAnchors(a: AgentMapPackableCircle, b: AgentMapPackableCircle): number { return ( Math.hypot(b.x, b.y) + b.radius - (Math.hypot(a.x, a.y) + a.radius) || compareStable(a.id, b.id) ) } function addBoundaryAnchor( - boundaryAnchors: PackableWorktree[], - worktree: PackableWorktree, + boundaryAnchors: AgentMapPackableCircle[], + worktree: AgentMapPackableCircle, maxAnchors: number ): void { let low = 0 @@ -116,9 +114,9 @@ function addBoundaryAnchor( } function placePackedWorktree( - worktree: PackableWorktree, - placed: PackableWorktree[], - boundaryAnchors: PackableWorktree[], + worktree: AgentMapPackableCircle, + placed: AgentMapPackableCircle[], + boundaryAnchors: AgentMapPackableCircle[], spatialIndex: AgentMapPackingSpatialIndex | null, currentRadius: number, searchBudget: PackingSearchBudget @@ -169,7 +167,7 @@ function placePackedWorktree( worktree.y = 0 } -function enclosingRadius(worktrees: PackableWorktree[], x: number, y: number): number { +function enclosingRadius(worktrees: AgentMapPackableCircle[], x: number, y: number): number { let radius = 0 for (const worktree of worktrees) { radius = Math.max(radius, Math.hypot(worktree.x - x, worktree.y - y) + worktree.radius) @@ -191,7 +189,7 @@ function packingSearchBudget(count: number): PackingSearchBudget { } function findEnclosingCenter( - worktrees: PackableWorktree[], + worktrees: AgentMapPackableCircle[], bounds: { left: number; right: number; top: number; bottom: number } ): { x: number; y: number } { let x = (bounds.left + bounds.right) / 2 @@ -219,10 +217,10 @@ function findEnclosingCenter( return { x, y } } -export function packAgentMapWorktrees(worktrees: T[]): T[] { +export function packAgentMapWorktrees(worktrees: T[]): T[] { const packed = [...worktrees].sort((a, b) => b.radius - a.radius || compareStable(a.id, b.id)) - const placed: PackableWorktree[] = [] - const boundaryAnchors: PackableWorktree[] = [] + const placed: AgentMapPackableCircle[] = [] + const boundaryAnchors: AgentMapPackableCircle[] = [] const searchBudget = packingSearchBudget(packed.length) const spatialIndex: AgentMapPackingSpatialIndex | null = packed.length > MAX_DIRECT_OVERLAP_WORKTREES ? new Map() : null diff --git a/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx b/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx index 347255324fd..8c0a926355b 100644 --- a/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx +++ b/src/renderer/src/components/dashboard/AgentDashboardDrawer.tsx @@ -3,7 +3,7 @@ import { useAppStore } from '@/store' import { Sheet, SheetContent, SheetTitle } from '@/components/ui/sheet' import { revealDashboardAgent } from './reveal-dashboard-agent' import { AgentKanbanBoard } from '../dashboard-popout/AgentKanbanBoard' -import type { AgentRevealArgs } from '../dashboard-popout/AgentTerminalDialog' +import type { DashboardRevealAgentArgs } from '../../../../shared/dashboard-snapshot' import { isWorkspaceBoardKeepOpenTarget, useWorkspaceKanbanOutsideDismiss @@ -46,7 +46,7 @@ function AgentDashboardDrawerBody({ useAppStore.getState().acknowledgeAgents([paneKey]) }, []) const handleRevealAgent = useCallback( - (args: AgentRevealArgs) => { + (args: DashboardRevealAgentArgs) => { revealDashboardAgent(args) onClose() }, diff --git a/src/renderer/src/components/editor/editor-restart-save-handlers.ts b/src/renderer/src/components/editor/editor-restart-save-handlers.ts index ef950b35884..f4be5337b0f 100644 --- a/src/renderer/src/components/editor/editor-restart-save-handlers.ts +++ b/src/renderer/src/components/editor/editor-restart-save-handlers.ts @@ -4,10 +4,7 @@ import { canAutoSaveOpenFile } from './editor-autosave' import { flushPendingEditorChange } from './editor-pending-flush' import { getDuplicateDirtySavePaths } from './editor-autosave-state-projections' import type { AppStoreApi, EditorSaveQueue } from './editor-save-queue' -import type { - EditorPrepareHotExitDetail, - EditorSaveDirtyFilesDetail -} from '../../../../shared/editor-save-events' +import type { EditorSaveDirtyFilesDetail } from '../../../../shared/editor-save-events' type EditorRestartSaveHandlerOptions = { store: AppStoreApi @@ -73,7 +70,7 @@ export function createEditorRestartSaveHandlers({ } const handlePrepareHotExit = async (event: Event): Promise => { - const detail = (event as CustomEvent).detail + const detail = (event as CustomEvent).detail if (!detail) { return } diff --git a/src/renderer/src/components/editor/migrate-restored-editor-file-owner.ts b/src/renderer/src/components/editor/migrate-restored-editor-file-owner.ts index 86fbbfa63f1..6436e43cd79 100644 --- a/src/renderer/src/components/editor/migrate-restored-editor-file-owner.ts +++ b/src/renderer/src/components/editor/migrate-restored-editor-file-owner.ts @@ -11,13 +11,11 @@ import { import { requestEditorSaveQuiesce } from './editor-autosave' import type { RestoredEditorOwnerResult } from '@/store/slices/editor' -export type RestoredEditorOwnerMigrationResult = RestoredEditorOwnerResult - export async function migrateRestoredEditorFileOwner( fileId: string, route: RuntimeWorkspaceFileRoute, runtimeEnvironmentId: string | null -): Promise { +): Promise { const state = useAppStore.getState() const source = state.openFiles.find((file) => file.id === fileId) const initialRoute = source diff --git a/src/renderer/src/components/emulator-pane/emulator-screen-gesture.ts b/src/renderer/src/components/emulator-pane/emulator-screen-gesture.ts index 21fb139c477..440760ab8aa 100644 --- a/src/renderer/src/components/emulator-pane/emulator-screen-gesture.ts +++ b/src/renderer/src/components/emulator-pane/emulator-screen-gesture.ts @@ -35,8 +35,6 @@ export type EmulatorPointerAction = | { kind: 'tap'; point: EmulatorScreenPoint } | { kind: 'gesture'; points: EmulatorGesturePoint[] } -type ContentRect = RectLike - const DOM_DELTA_LINE = 1 const DOM_DELTA_PAGE = 2 export const HID_EDGE_BOTTOM = 3 @@ -62,7 +60,7 @@ export function buildEmulatorGesturePoint( return edge === undefined ? { ...point, type } : { ...point, type, edge } } -function resolveSimulatorScreenContentRect(rect: RectLike, streamSize: StreamSize): ContentRect { +function resolveSimulatorScreenContentRect(rect: RectLike, streamSize: StreamSize): RectLike { let contentLeft = rect.left let contentTop = rect.top let contentWidth = rect.width diff --git a/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx b/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx index d00d2640cf2..c25e1332490 100644 --- a/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.test.tsx @@ -5,7 +5,7 @@ import { createRoot, type Root } from 'react-dom/client' import { afterEach, describe, expect, it, vi } from 'vitest' import { FloatingBrowserSlot } from './FloatingBrowserSlot' import { getBrowserOverlaySlotViewport } from '@/components/browser-pane/host-guest/browser-page-viewport' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' // Why: BrowserPane mounts a real Electron we can't run in jsdom; stub // it so the test isolates the slot-root registration that BrowserPane depends on. @@ -13,7 +13,7 @@ vi.mock('@/components/browser-pane/BrowserPane', () => ({ default: () => null })) -function makeBrowserTab(id: string): BrowserTab { +function makeBrowserTab(id: string): BrowserWorkspace { return { id, worktreeId: 'global-floating-terminal', diff --git a/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx b/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx index 3af3493db0b..8af588b0f87 100644 --- a/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingBrowserSlot.tsx @@ -1,7 +1,7 @@ import { useCallback } from 'react' import BrowserPane from '@/components/browser-pane/BrowserPane' import { registerBrowserOverlaySlotViewport } from '@/components/browser-pane/host-guest/browser-page-viewport' -import type { BrowserTab as BrowserTabState } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../shared/browser-workspace-types' // Why: BrowserPane mounts its persistent Electron into a slot viewport // root keyed by the browser tab id. The main workspace registers that root via diff --git a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx index 3e4de4227e7..0cc8257a121 100644 --- a/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx +++ b/src/renderer/src/components/floating-terminal/FloatingTerminalPanel.shortcuts.test.tsx @@ -1,7 +1,7 @@ import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' import type { KeybindingOverrides } from '../../../../shared/keybindings' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' import type { Tab } from '../../../../shared/tab-types' import { getMaximizedFloatingTerminalBounds } from './floating-terminal-panel-bounds' import { @@ -495,7 +495,7 @@ describe('FloatingTerminalPanel close behavior', () => { sortOrder: 1, createdAt: 1 } - const browserTab: BrowserTab = { + const browserTab: BrowserWorkspace = { id: 'browser-tab', worktreeId: FLOATING_TERMINAL_WORKTREE_ID, url: '', diff --git a/src/renderer/src/components/floating-terminal/floating-terminal-panel-test-fixtures.ts b/src/renderer/src/components/floating-terminal/floating-terminal-panel-test-fixtures.ts index b51cb9a6d78..5883bab4bfa 100644 --- a/src/renderer/src/components/floating-terminal/floating-terminal-panel-test-fixtures.ts +++ b/src/renderer/src/components/floating-terminal/floating-terminal-panel-test-fixtures.ts @@ -1,13 +1,13 @@ import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' import type { KeybindingOverrides, TerminalShortcutPolicy } from '../../../../shared/keybindings' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' import type { Tab, TabGroup } from '../../../../shared/tab-types' import type { TerminalTab } from '../../../../shared/terminal-tab-types' import type { OpenFile } from '@/store/slices/editor' export type FloatingPanelStoreState = { tabsByWorktree: Record - browserTabsByWorktree: Record + browserTabsByWorktree: Record browserPagesByWorkspace: Record groupsByWorktree: Record unifiedTabsByWorktree: Record @@ -32,7 +32,7 @@ export type FloatingPanelStoreState = { title?: string targetGroupId?: string } - ) => BrowserTab + ) => BrowserWorkspace closeTab: (tabId: string) => void closeBrowserTab: (tabId: string) => void closeFile: (fileId: string) => void diff --git a/src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts b/src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts index 392317f2d37..d6b8089d388 100644 --- a/src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts +++ b/src/renderer/src/components/floating-terminal/use-floating-terminal-panel-items.ts @@ -2,7 +2,7 @@ import { useMemo } from 'react' import { resolveGroupTabFromVisibleId } from '@/components/tab-group/tab-group-visible-id' import { useTerminalTabColdParking } from '@/components/terminal-pane/use-terminal-tab-cold-parking' import type { OpenFile } from '@/store/slices/editor' -import type { BrowserTab as BrowserTabState } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../shared/browser-workspace-types' import type { TerminalTab } from '../../../../shared/terminal-tab-types' import { resolveUnifiedTabLabel } from '../../../../shared/tab-title-resolution' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../../shared/constants' diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx index 3cbb1535a16..bf3625c1b0b 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab-pr-sidebar.tsx @@ -7,7 +7,7 @@ import type { } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' import { PRAssigneesPanel } from '@/components/github/PRAssigneesPanel' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { ChecksTab } from '../inspect-pull-request/checks-tab' import { PRActionsPanel } from '../land-pull-request/pr-actions-panel' import { PRReviewersPanel } from '../land-pull-request/pr-reviewers-panel' @@ -30,7 +30,7 @@ export function ConversationTabPRSidebar({ item: GitHubWorkItem repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: GitHubItemDialogProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined localState: GitHubWorkItem['state'] onStateChange: (state: GitHubWorkItem['state']) => void onMutated: () => void diff --git a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx index 435ab07d6ac..5ab7626d639 100644 --- a/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx +++ b/src/renderer/src/components/github-item-dialog/discuss-item/conversation-tab.tsx @@ -44,7 +44,7 @@ import { addPRReviewCommentReplyForRepo } from '@/components/github/github-work-item-comment-mutations' import { runWorkItemBodyUpdate } from '@/components/github/github-work-item-edit-mutations' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { EMPTY_GITHUB_ISSUE_TIMELINE_ITEMS, getIssueConversationEntries @@ -92,7 +92,7 @@ export function ConversationTab({ checks: GitHubWorkItemDetails['checks'] localState: GitHubWorkItem['state'] onStateChange: (state: GitHubWorkItem['state']) => void - projectOrigin: GitHubItemDialogProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined onMutated: () => void onChecksUpdated: (checks: PRCheckDetail[]) => void onBodyUpdated: (body: string) => void diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts index 42fba184a2e..cf66574c88d 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section-mutations.ts @@ -12,7 +12,7 @@ import { runIssueUpdate } from '@/components/github/github-work-item-edit-mutati import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' import { translate } from '@/i18n/i18n' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' export type GHEditProjectRowPatch = { state?: GitHubWorkItem['state'] @@ -27,7 +27,7 @@ type GHEditMutationBase = { itemRepoId: GitHubWorkItem['repoId'] repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: GitHubItemDialogProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined run: GHEditMutationRun patchProjectRowIfNeeded: (patch: GHEditProjectRowPatch) => void onMutated: () => void diff --git a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx index f92c99b2a09..16b520c9019 100644 --- a/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx +++ b/src/renderer/src/components/github-item-dialog/edit-item-fields/gh-edit-section.tsx @@ -17,7 +17,7 @@ import { } from '@/components/task-page-github-status-actions' import { parseOwnerRepoFromItemUrl } from '@/components/github/github-work-item-identity' import { translate } from '@/i18n/i18n' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { getGitHubRepositoryLabelsUrl } from './repository-labels-url' import { closeGHEditAsDuplicate, @@ -50,7 +50,7 @@ export function GHEditSection({ repoPath: string | null repoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: GitHubItemDialogProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined localState: GitHubWorkItem['state'] localLabels: string[] onStateChange: (state: GitHubWorkItem['state']) => void diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx index 487411e75e8..f910df00b26 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-actions-panel.tsx @@ -24,7 +24,7 @@ import { assertTaskPageGitHubDialogStateAuthority } from '@/components/task-page import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { notifyWorkItemDetailsMutation } from '@/components/github/github-work-item-comment-mutations' import { runPullRequestStateUpdate } from '@/components/github/github-work-item-edit-mutations' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { PRActionsMergeMenu } from './pr-actions-merge-menu' import { WorkItemStateBadge } from '../load-item-details/work-item-state-badge' @@ -42,7 +42,7 @@ export function PRActionsPanel({ repoPath: string | null repoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: GitHubItemDialogProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined localState: GitHubWorkItem['state'] onStateChange: (state: GitHubWorkItem['state']) => void onMutated: () => void diff --git a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx index 4a6db255e19..c814950dfc9 100644 --- a/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx +++ b/src/renderer/src/components/github-item-dialog/land-pull-request/pr-reviewers-panel.tsx @@ -20,7 +20,7 @@ import { translate } from '@/i18n/i18n' import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner' import { resolvePullRequestRepo } from '@/components/github/github-work-item-identity' import { mergeReviewerSuggestions } from '@/components/github/work-item-state-presentation' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { PRReviewersPickerList } from './pr-reviewers-picker-row' import { PRReviewersRequestedList } from './pr-reviewers-requested-list' import { removePRReviewers, requestPRReviewers } from './pr-reviewers-request-actions' @@ -37,7 +37,7 @@ export function PRReviewersPanel({ loading: boolean repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin onReviewersRequested: (reviewRequests: GitHubAssignableUser[]) => void }): React.JSX.Element { const [open, setOpen] = useState(false) diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts b/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts index c0a6fc7a3b8..07bbf1113cd 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/github-item-dialog-types.ts @@ -6,9 +6,6 @@ import type { ItemDialogTab } from '@/components/github/github-work-item-identity' -/** Re-exported so Project-view callers keep a stable import path. */ -export type GitHubItemDialogProjectOrigin = GitHubWorkItemProjectOrigin - export type GitHubItemDialogProps = { workItem: GitHubWorkItem | null repoPath: string | null @@ -24,5 +21,5 @@ export type GitHubItemDialogProps = { ) => void onClose: () => void /** Optional Project-origin context; when set, edits route via slug-addressed IPCs against the row's repo (slug routing wins for writes). */ - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin } diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts index b49877225f1..df371e5a462 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/pr-file-viewed-change.ts @@ -5,7 +5,7 @@ import { translate } from '@/i18n/i18n' import type { GitHubPRFileViewedState } from '../../../../../shared/github/pull-request-types' import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { GitHubItemDialogProjectOrigin } from './github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { patchCachedPRFileViewedState } from './work-item-details-cache' export async function syncPRFileViewedState(args: { @@ -15,7 +15,7 @@ export async function syncPRFileViewedState(args: { detailsCacheKey: string | null repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin path: string viewed: boolean setPendingViewedPaths: (updater: (prev: Set) => Set) => void diff --git a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.ts b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.ts index 0d685da6020..655dfaf3cf5 100644 --- a/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.ts +++ b/src/renderer/src/components/github-item-dialog/load-item-details/use-github-item-dialog-details.ts @@ -16,7 +16,7 @@ import { getTaskSourceCacheScope, type TaskSourceContext } from '../../../../../shared/task-source-context' -import type { GitHubItemDialogProjectOrigin } from './github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { WORK_ITEM_DETAILS_FRESH_MS, getWorkItemDetailsCacheKey, @@ -44,7 +44,7 @@ export function useGitHubItemDialogDetails({ effectiveRepoId: string | null sourceContext?: TaskSourceContext | null initialTab?: ItemDialogTab - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin onReviewRequestsChange?: ( itemKey: { id: string; repoId: string }, reviewRequests: GitHubAssignableUser[] diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx index 97004ef8937..872a798921f 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-issue-body.tsx @@ -8,7 +8,7 @@ import type { import type { TaskSourceContext } from '../../../../../shared/task-source-context' import { ConversationTab } from '../discuss-item/conversation-tab' import { GHEditSection } from '../edit-item-fields/gh-edit-section' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { patchCachedPRChecks, patchCachedPRReviewRequests, @@ -43,7 +43,7 @@ export function GitHubItemDialogIssueBody({ repoPath: string | null effectiveRepoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin details: GitHubWorkItemDetails | null detailsCacheKey: string | null loading: boolean diff --git a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx index 79de68a8924..d92f68b4dce 100644 --- a/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx +++ b/src/renderer/src/components/github-item-dialog/open-dialog/github-item-dialog-pr-tabs.tsx @@ -17,7 +17,7 @@ import type { import type { TaskSourceContext } from '../../../../../shared/task-source-context' import { ChecksTab } from '../inspect-pull-request/checks-tab' import { ConversationTab } from '../discuss-item/conversation-tab' -import type { GitHubItemDialogProjectOrigin } from '../load-item-details/github-item-dialog-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { PRFilesCombinedDiffViewer } from '../inspect-pull-request/pr-files-combined-diff-viewer' import { patchCachedPRChecks, @@ -51,7 +51,7 @@ export function GitHubItemDialogPRTabs({ repoPath: string | null effectiveRepoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin?: GitHubItemDialogProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin details: GitHubWorkItemDetails | null detailsCacheKey: string | null loading: boolean diff --git a/src/renderer/src/components/github-project/ProjectItemSlugDialog.tsx b/src/renderer/src/components/github-project/ProjectItemSlugDialog.tsx index bf176e802c3..26a20f8a7cd 100644 --- a/src/renderer/src/components/github-project/ProjectItemSlugDialog.tsx +++ b/src/renderer/src/components/github-project/ProjectItemSlugDialog.tsx @@ -10,13 +10,13 @@ import React from 'react' import { VisuallyHidden } from 'radix-ui' import { Sheet, SheetContent, SheetDescription, SheetTitle } from '@/components/ui/sheet' -import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' +import type { GitHubWorkItemProjectOrigin } from '@/components/GitHubItemDialog' import { SlugDialogBody } from './slug-dialog/SlugDialogBody' import type { GlobalSettings } from '../../../../shared/global-settings-types' import { translate } from '@/i18n/i18n' type Props = { - projectOrigin: GitHubItemDialogProjectOrigin | null + projectOrigin: GitHubWorkItemProjectOrigin | null sourceSettings: Pick | null | undefined onClose: () => void } diff --git a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx index e04c3129958..86e1635f21a 100644 --- a/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx +++ b/src/renderer/src/components/github-project/slug-dialog/SlugDialogBody.tsx @@ -9,7 +9,7 @@ import { useAppStore } from '@/store' import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' import type { GitHubWorkItemDetails } from '../../../../../shared/github/work-item-types' import type { GlobalSettings } from '../../../../../shared/global-settings-types' -import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' +import type { GitHubWorkItemProjectOrigin } from '@/components/GitHubItemDialog' import { LabelsEditor } from './LabelsEditor' import { AssigneesEditor } from './AssigneesEditor' import { CommentsList, NewCommentForm } from './Comments' @@ -20,7 +20,7 @@ export function SlugDialogBody({ sourceSettings, onClose }: { - projectOrigin: GitHubItemDialogProjectOrigin + projectOrigin: GitHubWorkItemProjectOrigin sourceSettings: Pick | null | undefined onClose: () => void }): React.JSX.Element { diff --git a/src/renderer/src/components/github-project/useProjectRowActions.ts b/src/renderer/src/components/github-project/useProjectRowActions.ts index eb0b576c999..fe38e9081f0 100644 --- a/src/renderer/src/components/github-project/useProjectRowActions.ts +++ b/src/renderer/src/components/github-project/useProjectRowActions.ts @@ -8,7 +8,7 @@ import { buildTaskSourceContextFromRepo } from '../../../../shared/task-source-c import { githubProjectHost } from '../../../../shared/github/project-identity' import type { GitHubProjectRow, GitHubProjectTable } from '../../../../shared/github/project-types' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' -import type { GitHubItemDialogProjectOrigin } from '@/components/GitHubItemDialog' +import type { GitHubWorkItemProjectOrigin } from '@/components/GitHubItemDialog' import { resolveMissingRepoProjectDialogState, resolveRepoBackedProjectDialogState @@ -21,7 +21,7 @@ type DialogRepoItem = { workItem: GitHubWorkItem repoPath: string repoId: string - origin: GitHubItemDialogProjectOrigin + origin: GitHubWorkItemProjectOrigin } export function useProjectRowActions({ @@ -37,9 +37,7 @@ export function useProjectRowActions({ const rowMutations = useProjectRowMutations(currentCacheKey) const { lookupSlug, lookupSlugMatches, ready: slugIndexReady } = useRepoSlugIndex() const [dialogRepoItem, setDialogRepoItem] = useState(null) - const [slugDialog, setSlugDialog] = useState<{ origin: GitHubItemDialogProjectOrigin } | null>( - null - ) + const [slugDialog, setSlugDialog] = useState<{ origin: GitHubWorkItemProjectOrigin } | null>(null) const [repoNotInOrca, setRepoNotInOrca] = useState<{ owner: string repo: string @@ -80,7 +78,7 @@ export function useProjectRowActions({ } const buildOrigin = useCallback( - (row: GitHubProjectRow): GitHubItemDialogProjectOrigin | null => { + (row: GitHubProjectRow): GitHubWorkItemProjectOrigin | null => { if (!table || !currentCacheKey) { return null } diff --git a/src/renderer/src/components/github-project/useProjectViewTable.ts b/src/renderer/src/components/github-project/useProjectViewTable.ts index 2649dbe1996..9c05f51af9a 100644 --- a/src/renderer/src/components/github-project/useProjectViewTable.ts +++ b/src/renderer/src/components/github-project/useProjectViewTable.ts @@ -7,7 +7,6 @@ import { useAppStore } from '@/store' import { projectViewCacheKey } from '@/store/github/cache-identity' import type { GitHubProjectSettings, - GitHubProjectTable, GitHubProjectViewSummary } from '../../../../shared/github/project-types' import type { @@ -276,4 +275,3 @@ const EMPTY_PROJECT_SETTINGS: GitHubProjectSettings = { } export type ProjectViewTableState = ReturnType -export type ProjectViewTable = GitHubProjectTable diff --git a/src/renderer/src/components/link-actions/LinkActionPopover.tsx b/src/renderer/src/components/link-actions/LinkActionPopover.tsx index 4bde5fdbae1..de489e705c8 100644 --- a/src/renderer/src/components/link-actions/LinkActionPopover.tsx +++ b/src/renderer/src/components/link-actions/LinkActionPopover.tsx @@ -9,7 +9,8 @@ import { useClipboardTextCopyFeedback } from '@/hooks/use-clipboard-text-copy-fe import { translate } from '@/i18n/i18n' import { BROWSER_TERMINAL_LINK_ACTIONS_SETTINGS_TARGET_ID } from '@/lib/settings-navigation-types' import { useAppStore } from '@/store' -import type { LinkAction, LinkActionRequest } from './link-action-request' +import type { LinkActionRequest } from './link-action-request' +import type { HttpLinkAction } from '@/lib/http-link-destinations' type LinkActionPopoverProps = { request: TRequest | null @@ -21,7 +22,7 @@ function ActionRow({ alternate, onRun }: { - action: LinkAction + action: HttpLinkAction alternate: boolean onRun: () => void }): React.JSX.Element { @@ -65,7 +66,7 @@ export function LinkActionPopover({ [request?.anchorX, request?.anchorY] ) - const runAction = (action: LinkAction): void => { + const runAction = (action: HttpLinkAction): void => { onClose() request?.restoreFocus() void action.run() diff --git a/src/renderer/src/components/link-actions/link-action-request.ts b/src/renderer/src/components/link-actions/link-action-request.ts index f79f22aa7d0..4fe4c4e5850 100644 --- a/src/renderer/src/components/link-actions/link-action-request.ts +++ b/src/renderer/src/components/link-actions/link-action-request.ts @@ -2,16 +2,14 @@ import type { HttpLinkAction } from '@/lib/http-link-destinations' export type LinkActionKind = 'url' | 'file' | 'workspace' | 'terminal' | 'task' -export type LinkAction = HttpLinkAction - /** A pending destination choice for one clicked link, anchored at the pointer. */ export type LinkActionRequest = { anchorX: number anchorY: number destination: string kind: LinkActionKind - primary: LinkAction - alternate?: LinkAction + primary: HttpLinkAction + alternate?: HttpLinkAction /** Hands focus back to the surface that owned the click (terminal, chat transcript). */ restoreFocus: () => void } diff --git a/src/renderer/src/components/mobile/MobileHero.tsx b/src/renderer/src/components/mobile/MobileHero.tsx index 4f683088e3a..f90f52d32bf 100644 --- a/src/renderer/src/components/mobile/MobileHero.tsx +++ b/src/renderer/src/components/mobile/MobileHero.tsx @@ -9,7 +9,8 @@ import type { MobileRelayMintFailure } from '../../../../shared/mobile-relay-min import { MobileHeroPairingStep } from './MobileHeroPairingStep' import { MobileAndroidInstallHelp } from './MobileAndroidInstallHelp' export { HeroIntro } from './MobileHeroIntro' -export { HeroPaired, type PairedDevice } from './MobileHeroPairedDevices' +export { HeroPaired } from './MobileHeroPairedDevices' +export type { PairedMobileDevice } from './paired-mobile-devices' import { translate } from '@/i18n/i18n' export type Platform = 'ios' | 'android' diff --git a/src/renderer/src/components/mobile/MobileHeroPairedDevices.tsx b/src/renderer/src/components/mobile/MobileHeroPairedDevices.tsx index 76eccb832c1..1b34e0bd694 100644 --- a/src/renderer/src/components/mobile/MobileHeroPairedDevices.tsx +++ b/src/renderer/src/components/mobile/MobileHeroPairedDevices.tsx @@ -2,10 +2,8 @@ import { Smartphone, Trash2 } from 'lucide-react' import { translate } from '@/i18n/i18n' import type { PairedMobileDevice } from './paired-mobile-devices' -export type PairedDevice = PairedMobileDevice - type HeroPairedProps = { - devices: readonly PairedDevice[] + devices: readonly PairedMobileDevice[] onPairAnother: () => void onRevoke: (deviceId: string) => void revokingDeviceIds: readonly string[] diff --git a/src/renderer/src/components/mobile/MobilePageContent.tsx b/src/renderer/src/components/mobile/MobilePageContent.tsx index 8df42633c74..4694cbbdaaa 100644 --- a/src/renderer/src/components/mobile/MobilePageContent.tsx +++ b/src/renderer/src/components/mobile/MobilePageContent.tsx @@ -4,7 +4,7 @@ import { HeroFlow, HeroIntro, HeroPaired, - type PairedDevice, + type PairedMobileDevice, type Platform, type StepIndex } from './MobileHero' @@ -19,7 +19,7 @@ type MobilePageContentProps = { closeMobilePage: () => void copyInstallUrl: () => void copyPairingCode: () => void - devices: readonly PairedDevice[] + devices: readonly PairedMobileDevice[] enterFlow: () => void generatePairing: (rotate: boolean) => void canGeneratePairing: boolean diff --git a/src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts b/src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts index a13c38bb87c..9c96dc8d1f0 100644 --- a/src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts +++ b/src/renderer/src/components/mobile/use-mobile-page-paired-devices.ts @@ -2,7 +2,7 @@ import { useCallback, useEffect, useRef, useState, type Dispatch, type SetStateA import { toast } from 'sonner' import { useMountedRef } from '@/hooks/useMountedRef' import { useMobilePairingDevicePolling } from '../settings/mobile-pairing-device-polling' -import type { PairedDevice, StepIndex } from './MobileHero' +import type { PairedMobileDevice, StepIndex } from './MobileHero' import { shouldShowPairedAfterDeviceRefresh, type MobilePageStage as FlowStage @@ -21,7 +21,7 @@ export function useMobilePagePairedDevices({ stepIdx: StepIndex setStepIdx: Dispatch> }): { - devices: readonly PairedDevice[] + devices: readonly PairedMobileDevice[] stage: FlowStage | null revokingDeviceIds: readonly string[] enterFlow: () => void @@ -75,7 +75,7 @@ export function useMobilePagePairedDevices({ opts: { force?: boolean } = {} - ): Promise => { + ): Promise => { try { const nextDevices = await refreshDevices(opts) if (mountedRef.current) { @@ -148,7 +148,7 @@ export function useMobilePagePairedDevices({ // revoked device from the last-known list (not loadDevices' bogus [] from // a failed reload), keeping success + intro-routing correct. Mirrors // MobilePane's revoke fallback. - let remaining: readonly PairedDevice[] + let remaining: readonly PairedMobileDevice[] try { remaining = await refreshDevices({ force: true }) } catch (err) { diff --git a/src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx b/src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx index 345ad5bbef2..e8ab88f1757 100644 --- a/src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx +++ b/src/renderer/src/components/native-chat/NativeChatStructuredSession.test-harness.tsx @@ -6,9 +6,6 @@ import type { NativeChatApprovalCardProps } from './NativeChatApprovalCard' import type { NativeChatQuestionCardProps } from './NativeChatQuestionCard' import type { NativeChatLaunchSeed } from './native-chat-composer-types' -// Why: a named spy type keeps the harness's inferred return type portable across the test files. -type StructuredSessionSpy = Mock - type StructuredSessionMessageListProps = { allowFileUriLinks?: boolean onLinkClick?: (...args: unknown[]) => void @@ -28,8 +25,8 @@ const initialApprovalCardProps: NativeChatApprovalCardProps | null = null */ export function createStructuredSessionMocks() { const mocks = { - call: vi.fn() as StructuredSessionSpy, - fileLinkClick: vi.fn() as StructuredSessionSpy, + call: vi.fn() as Mock, + fileLinkClick: vi.fn() as Mock, mode: 'static' as 'static' | 'outbox', status: 'ready' as 'idle' | 'loading' | 'ready' | 'error', messages: null as null | unknown[], @@ -42,10 +39,10 @@ export function createStructuredSessionMocks() { approvalCardProps: initialApprovalCardProps, questionCardProps: null as NativeChatQuestionCardProps | null, promptItems: [] as AgentJournalRenderItem[], - respond: vi.fn() as StructuredSessionSpy, - cancel: vi.fn() as StructuredSessionSpy, - handlePasteEvent: vi.fn() as StructuredSessionSpy, - pasteFromClipboard: vi.fn() as StructuredSessionSpy, + respond: vi.fn() as Mock, + cancel: vi.fn() as Mock, + handlePasteEvent: vi.fn() as Mock, + pasteFromClipboard: vi.fn() as Mock, submissions: [] as unknown[], monitoringBackgroundTasks: false, showBackgroundTasks: false, @@ -55,7 +52,7 @@ export function createStructuredSessionMocks() { supportsBackgroundTaskStopAll: true, backgroundTasks: [] as AgentSessionBackgroundTask[], settledBackgroundTasks: [] as AgentSessionBackgroundTask[], - stopBackgroundTask: vi.fn() as StructuredSessionSpy + stopBackgroundTask: vi.fn() as Mock } const moduleFactories = { @@ -99,7 +96,7 @@ export function createStructuredSessionMocks() { error: outbox.error, hasOlder: false, loadingOlder: false, - loadOlder: vi.fn() as StructuredSessionSpy, + loadOlder: vi.fn() as Mock, prompts: mocks.promptItems, outbox: outbox.outbox, blockedClientMessageId: outbox.blockedClientMessageId, @@ -135,11 +132,11 @@ export function createStructuredSessionMocks() { ], optionSurface: { getSnapshot: () => [], - setOption: vi.fn() as StructuredSessionSpy, - invokeAction: vi.fn() as StructuredSessionSpy, + setOption: vi.fn() as Mock, + invokeAction: vi.fn() as Mock, subscribe: () => () => {} }, - setStructuredOption: vi.fn() as StructuredSessionSpy + setStructuredOption: vi.fn() as Mock } } } diff --git a/src/renderer/src/components/native-chat/background-task-header-content.ts b/src/renderer/src/components/native-chat/background-task-header-content.ts index 0ec1c765e1f..50aef47ad20 100644 --- a/src/renderer/src/components/native-chat/background-task-header-content.ts +++ b/src/renderer/src/components/native-chat/background-task-header-content.ts @@ -15,8 +15,6 @@ import { } from './background-task-roster' type TaskKind = AgentSessionBackgroundTask['kind'] -type RunState = AgentSessionBackgroundTaskRunState - function kindCountLabel(kind: TaskKind, count: number): string { const value = { value0: count } switch (kind) { @@ -69,7 +67,7 @@ const HEADER_SEGMENT_CAP = 3 /** Done comes last but must be present: the headline counts settled rows too, * so omitting it made the breakdown contradict its own count. */ -const HEADER_STATE_ORDER: readonly RunState[] = [ +const HEADER_STATE_ORDER: readonly AgentSessionBackgroundTaskRunState[] = [ 'working', 'monitoring', 'waiting', @@ -79,7 +77,11 @@ const HEADER_STATE_ORDER: readonly RunState[] = [ 'done' ] -const ATTENTION_STATES: ReadonlySet = new Set(['waiting', 'unverifiable', 'blocked']) +const ATTENTION_STATES: ReadonlySet = new Set([ + 'waiting', + 'unverifiable', + 'blocked' +]) export type BackgroundTasksHeaderSegment = { text: string diff --git a/src/renderer/src/components/native-chat/background-task-roster.ts b/src/renderer/src/components/native-chat/background-task-roster.ts index 4b1c290e944..1fee03bb3d3 100644 --- a/src/renderer/src/components/native-chat/background-task-roster.ts +++ b/src/renderer/src/components/native-chat/background-task-roster.ts @@ -10,12 +10,11 @@ import { formatNativeChatDuration } from '../../../../shared/native-chat-turn-st import { translate } from '@/i18n/i18n' type TaskKind = AgentSessionBackgroundTask['kind'] -type RunState = AgentSessionBackgroundTaskRunState export type BackgroundRosterTask = { task: AgentSessionBackgroundTask settled: boolean - state: RunState + state: AgentSessionBackgroundTaskRunState name: string } @@ -60,7 +59,10 @@ export function resolveBackgroundTaskName(task: AgentSessionBackgroundTask): str ) } -function effectiveState(task: AgentSessionBackgroundTask, settled: boolean): RunState { +function effectiveState( + task: AgentSessionBackgroundTask, + settled: boolean +): AgentSessionBackgroundTaskRunState { if (task.state) { return task.state } @@ -102,7 +104,7 @@ export function buildBackgroundTaskGroups( })).filter((group) => group.tasks.length > 0) } -export function backgroundTaskStateWord(state: RunState): string { +export function backgroundTaskStateWord(state: AgentSessionBackgroundTaskRunState): string { switch (state) { case 'working': return translate('components.native-chat.backgroundTasks.stateWorking', 'working') @@ -122,7 +124,9 @@ export function backgroundTaskStateWord(state: RunState): string { } /** The reason line for an attention state, per the signed-off mock. */ -export function backgroundTaskStateReason(state: RunState): string | null { +export function backgroundTaskStateReason( + state: AgentSessionBackgroundTaskRunState +): string | null { switch (state) { case 'waiting': return translate('components.native-chat.backgroundTasks.reasonWaiting', 'needs approval') diff --git a/src/renderer/src/components/native-chat/native-chat-send-eligibility.ts b/src/renderer/src/components/native-chat/native-chat-send-eligibility.ts index 326b6c436d5..132e2dc6cf1 100644 --- a/src/renderer/src/components/native-chat/native-chat-send-eligibility.ts +++ b/src/renderer/src/components/native-chat/native-chat-send-eligibility.ts @@ -1,4 +1,4 @@ -import type { DriverState } from '@/lib/pane-manager/mobile-driver-state' +import type { RuntimeTerminalDriverState } from '../../../../shared/runtime-types' /** * Pure derivation of the composer's `canSend` (R8). A pty held by a mobile @@ -9,7 +9,9 @@ import type { DriverState } from '@/lib/pane-manager/mobile-driver-state' * is treated as unlocked so the composer stays usable while the lock state loads; * the actual send still no-ops without a ptyId. */ -export function deriveNativeChatCanSend(driver: DriverState | null | undefined): boolean { +export function deriveNativeChatCanSend( + driver: RuntimeTerminalDriverState | null | undefined +): boolean { return driver?.kind !== 'mobile' } diff --git a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx index 1928b205ff6..0a043fffdf1 100644 --- a/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx +++ b/src/renderer/src/components/new-workspace/NewWorkspaceComposerProjectSection.tsx @@ -1,3 +1,4 @@ +import type { NeedsSetupProjectHostOption } from '@/lib/project-host-setup-options' import React from 'react' import { FolderPlus, LoaderCircle, PlugZap } from 'lucide-react' import { Button } from '@/components/ui/button' @@ -7,7 +8,6 @@ import RunTargetCombobox from '@/components/new-workspace/RunTargetCombobox' import { translate } from '@/i18n/i18n' import type { EphemeralVmRecipeOption, - NeedsProjectHostOption, NewWorkspaceComposerCardProps } from './new-workspace-composer-card-props' import { EMPTY_PROJECT_OPTIONS } from './new-workspace-composer-card-props' @@ -40,8 +40,8 @@ type NewWorkspaceComposerProjectSectionProps = Pick< handleProjectHostSetupChange: (setupId: string) => void handleAddSshHost: () => void handleAddRemoteServer: () => void - handleConnectRunTargetHost: (option: NeedsProjectHostOption) => Promise - handleSetLocation: (option: NeedsProjectHostOption) => void + handleConnectRunTargetHost: (option: NeedsSetupProjectHostOption) => Promise + handleSetLocation: (option: NeedsSetupProjectHostOption) => void sshStatusLabel: string connectButtonLabel: string selectedProjectName: string diff --git a/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts b/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts index bcff4c96f63..1072b8be9a2 100644 --- a/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts +++ b/src/renderer/src/components/new-workspace/new-workspace-composer-card-props.ts @@ -1,9 +1,6 @@ import type RepoCombobox from '@/components/repo/RepoCombobox' import type { NewWorkspaceProjectOption } from '@/lib/new-workspace-project-options' -import type { - NeedsSetupProjectHostOption, - ProjectHostSetupOption -} from '@/lib/project-host-setup-options' +import type { ProjectHostSetupOption } from '@/lib/project-host-setup-options' import type { SetupConfig } from '@/lib/new-workspace' import type { WorkspaceCreateErrorDisplay } from '@/lib/workspace-create-error-format' import type { SmartNameMode } from '@/components/new-workspace/smart-workspace-source-results' @@ -120,6 +117,5 @@ export type NewWorkspaceComposerCardProps = { onNestedDialogOpenChange?: (open: boolean) => void } -export type NeedsProjectHostOption = NeedsSetupProjectHostOption export type ReadyProjectHostOption = Extract export type SmartWorkspaceNameFieldProps = React.ComponentProps diff --git a/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts b/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts index c4a85dd9527..7b6d2b2d3f5 100644 --- a/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts +++ b/src/renderer/src/components/new-workspace/smart-workspace-name-field-model.ts @@ -1,6 +1,6 @@ import type React from 'react' import type { useAppStore } from '@/store' -import type { parseGitHubIssueOrPRLink, RepoSlug } from '@/lib/github-links' +import type { parseGitHubIssueOrPRLink } from '@/lib/github-links' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' import type { GitLabWorkItem } from '../../../../shared/gitlab-types' import type { JiraIssue, JiraSite } from '../../../../shared/jira-types' @@ -98,8 +98,6 @@ export type SmartWorkspaceNameFieldSearchState = { jiraIssues: JiraIssue[] } -export type CachedRepoSlug = RepoSlug - export const EMPTY_REPO_SEARCH_REPOS: readonly RepoOption[] = [] export const SEARCH_DEBOUNCE_MS = 200 export const RESULT_LIMIT = 12 diff --git a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts index f54cb6f965b..70cd55c2ba6 100644 --- a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts +++ b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-actions.ts @@ -1,3 +1,4 @@ +import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' import { useCallback } from 'react' import type React from 'react' import { toast } from 'sonner' @@ -5,8 +6,7 @@ import { translate } from '@/i18n/i18n' import { lookupGitHubWorkItemByOwnerRepoForSource } from '@/lib/github-work-item-source-lookup' import { applyWorkspaceEmojiSuggestion, - type WorkspaceEmojiReplacement, - type WorkspaceEmojiSuggestion + type WorkspaceEmojiReplacement } from '@/lib/workspace-emoji-shortcodes' import type { GitHubWorkItem } from '../../../../shared/github/work-item-types' import { buildTaskSourceContextFromRepo } from '../../../../shared/task-source-context' @@ -139,7 +139,7 @@ export function useSmartWorkspaceNameFieldActions( ] ) const handleEmojiSelect = useCallback( - (suggestion: WorkspaceEmojiSuggestion): void => { + (suggestion: StandardEmojiShortcodeEntry): void => { if (!activeEmojiShortcode) { return } diff --git a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-presentation.ts b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-presentation.ts index 5665e60a0af..7ebc2b5ac40 100644 --- a/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-presentation.ts +++ b/src/renderer/src/components/new-workspace/use-smart-workspace-name-field-presentation.ts @@ -1,3 +1,4 @@ +import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' import { useEffect, useMemo } from 'react' import { CaseSensitive, LoaderCircle, Search } from 'lucide-react' import { parseGitHubIssueOrPRLink } from '@/lib/github-links' @@ -10,8 +11,7 @@ import { } from '../../../../shared/new-workspace/smart-workspace-linear-intent' import { getActiveWorkspaceEmojiShortcode, - searchWorkspaceEmojiShortcodes, - type WorkspaceEmojiSuggestion + searchWorkspaceEmojiShortcodes } from '@/lib/workspace-emoji-shortcodes' import { resolveSmartWorkspaceCommandValue } from './smart-workspace-command-value' import { @@ -243,7 +243,7 @@ export function useSmartWorkspaceNameFieldPresentation( () => activeEmojiShortcode ? searchWorkspaceEmojiShortcodes(activeEmojiShortcode.query) - : ([] as WorkspaceEmojiSuggestion[]), + : ([] as StandardEmojiShortcodeEntry[]), [activeEmojiShortcode] ) const emojiMenuOpen = diff --git a/src/renderer/src/components/pull-request-page/actions/merge-actions.ts b/src/renderer/src/components/pull-request-page/actions/merge-actions.ts index 27bd98d963c..e11ac22d916 100644 --- a/src/renderer/src/components/pull-request-page/actions/merge-actions.ts +++ b/src/renderer/src/components/pull-request-page/actions/merge-actions.ts @@ -12,7 +12,7 @@ import type { } from '../../../../../shared/github/pull-request-types' import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { translate } from '@/i18n/i18n' import type { GitHubPRMergeStatePresentation } from '@/components/github-pr-merge-state' @@ -25,7 +25,7 @@ export async function changePullRequestState(args: { repoPath: string | null repoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined prRepo: GitHubOwnerRepo | null confirm: (options: { title: string diff --git a/src/renderer/src/components/pull-request-page/actions/panel.tsx b/src/renderer/src/components/pull-request-page/actions/panel.tsx index da43e08f7f3..5aad5783ef2 100644 --- a/src/renderer/src/components/pull-request-page/actions/panel.tsx +++ b/src/renderer/src/components/pull-request-page/actions/panel.tsx @@ -29,7 +29,7 @@ import { resolvePullRequestRepo } from '@/components/github/github-work-item-ide import { translate } from '@/i18n/i18n' import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { WorkItemStateBadge } from '../presentation/state-badge' import { changePullRequestState, mergePullRequest, setPullRequestAutoMerge } from './merge-actions' @@ -47,7 +47,7 @@ export function PRActionsPanel({ repoPath: string | null repoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined localState: GitHubWorkItem['state'] onStateChange: (state: GitHubWorkItem['state']) => void onMutated: () => void diff --git a/src/renderer/src/components/pull-request-page/conversation/tab.tsx b/src/renderer/src/components/pull-request-page/conversation/tab.tsx index 2a7d9e1ba6f..289dc9fd8b9 100644 --- a/src/renderer/src/components/pull-request-page/conversation/tab.tsx +++ b/src/renderer/src/components/pull-request-page/conversation/tab.tsx @@ -37,7 +37,7 @@ import type { PRCheckDetail } from '../../../../../shared/github/check-types' import type { PRComment } from '../../../../../shared/github/comment-types' import { cn } from '@/lib/utils' import { translate } from '@/i18n/i18n' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { buildMentionOptions } from '../mentions/options' import { PRActionsPanel } from '../actions/panel' import { PRAssigneesPanel } from '@/components/github/PRAssigneesPanel' @@ -86,7 +86,7 @@ export function ConversationTab({ participants: GitHubAssignableUser[] localState: GitHubWorkItem['state'] onStateChange: (state: GitHubWorkItem['state']) => void - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined onMutated: () => void onChecksUpdated: (checks: PRCheckDetail[]) => void onBodyUpdated: (body: string) => void diff --git a/src/renderer/src/components/pull-request-page/edit/issue-updates.ts b/src/renderer/src/components/pull-request-page/edit/issue-updates.ts index 044b1e6f23c..e6fbf00c7d5 100644 --- a/src/renderer/src/components/pull-request-page/edit/issue-updates.ts +++ b/src/renderer/src/components/pull-request-page/edit/issue-updates.ts @@ -2,7 +2,7 @@ import { toast } from 'sonner' import { runIssueUpdate } from '@/components/github/github-work-item-edit-mutations' import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' type IssueUpdateRun = ( key: string, @@ -21,7 +21,7 @@ export function changeIssueState(args: { item: GitHubWorkItem repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined run: IssueUpdateRun onStateChange: (state: GitHubWorkItem['state']) => void patchWorkItem: ( @@ -78,7 +78,7 @@ export function toggleIssueLabel(args: { item: GitHubWorkItem repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined run: IssueUpdateRun onLabelsChange: (labels: string[]) => void patchWorkItem: ( @@ -131,7 +131,7 @@ export function toggleIssueAssignee(args: { item: GitHubWorkItem repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined run: IssueUpdateRun setLocalAssignees: (value: string[]) => void patchProjectRowIfNeeded: (patch: { assignees: string[] }) => void diff --git a/src/renderer/src/components/pull-request-page/edit/section.tsx b/src/renderer/src/components/pull-request-page/edit/section.tsx index 7a2257e6c8e..c5722cb213f 100644 --- a/src/renderer/src/components/pull-request-page/edit/section.tsx +++ b/src/renderer/src/components/pull-request-page/edit/section.tsx @@ -13,7 +13,7 @@ import type { GitHubWorkItem } from '../../../../../shared/github/work-item-type import { getSettingsForRepoRuntimeOwner } from '@/lib/repo-runtime-owner' import { getStateLabel } from '@/components/github/work-item-state-presentation' import { translate } from '@/i18n/i18n' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { getStateTone } from '../presentation/state-badge' import { changeIssueState, toggleIssueAssignee, toggleIssueLabel } from './issue-updates' @@ -35,7 +35,7 @@ export function GHEditSection({ repoPath: string | null repoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined localState: GitHubWorkItem['state'] localLabels: string[] onStateChange: (state: GitHubWorkItem['state']) => void diff --git a/src/renderer/src/components/pull-request-page/page-types.ts b/src/renderer/src/components/pull-request-page/page-types.ts index ef129e886db..0659707108a 100644 --- a/src/renderer/src/components/pull-request-page/page-types.ts +++ b/src/renderer/src/components/pull-request-page/page-types.ts @@ -6,8 +6,6 @@ import type { ItemDialogTab } from '@/components/github/github-work-item-identity' -export type PullRequestPageProjectOrigin = GitHubWorkItemProjectOrigin - export type MentionOption = { login: string name?: string | null @@ -35,5 +33,5 @@ export type PullRequestPageProps = { ) => void onClose: () => void /** Optional Project-origin context; when set, slug-addressed IPCs route writes to the row's repo instead of `repoPath` (both may be set — slug wins for writes). */ - projectOrigin?: PullRequestPageProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin } diff --git a/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx b/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx index 36e5d204338..0466fb4b789 100644 --- a/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx +++ b/src/renderer/src/components/pull-request-page/page/tabs-shell.tsx @@ -18,7 +18,7 @@ import type { import type { PRCheckDetail } from '../../../../../shared/github/check-types' import type { PRComment } from '../../../../../shared/github/comment-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { patchCachedPRChecks, patchCachedPRReviewRequests, @@ -61,7 +61,7 @@ export function PullRequestPageTabs({ repoPath: string | null effectiveRepoId: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined body: string comments: PRComment[] files: GitHubPRFile[] diff --git a/src/renderer/src/components/pull-request-page/page/viewed-sync.ts b/src/renderer/src/components/pull-request-page/page/viewed-sync.ts index ed0b0d10f64..41f9ece33f8 100644 --- a/src/renderer/src/components/pull-request-page/page/viewed-sync.ts +++ b/src/renderer/src/components/pull-request-page/page/viewed-sync.ts @@ -5,7 +5,7 @@ import { translate } from '@/i18n/i18n' import type { GitHubPRFileViewedState } from '../../../../../shared/github/pull-request-types' import type { GitHubWorkItem } from '../../../../../shared/github/work-item-types' import type { TaskSourceContext } from '../../../../../shared/task-source-context' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { patchCachedPRFileViewedState } from '../cache/work-item-details' export async function syncPullRequestFileViewed(args: { @@ -18,7 +18,7 @@ export async function syncPullRequestFileViewed(args: { detailsCacheKey: string | null repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin: PullRequestPageProjectOrigin | undefined + projectOrigin: GitHubWorkItemProjectOrigin | undefined setPendingViewedPaths: (updater: (prev: Set) => Set) => void }): Promise { if ( diff --git a/src/renderer/src/components/pull-request-page/reviewers/panel.tsx b/src/renderer/src/components/pull-request-page/reviewers/panel.tsx index 0f9bbec7b45..99b9a3014fa 100644 --- a/src/renderer/src/components/pull-request-page/reviewers/panel.tsx +++ b/src/renderer/src/components/pull-request-page/reviewers/panel.tsx @@ -17,7 +17,7 @@ import { getGitHubPRReviewerQueryState } from '@/components/github/github-pr-reviewer-candidate-filter' import { translate } from '@/i18n/i18n' -import type { PullRequestPageProjectOrigin } from '../page-types' +import type { GitHubWorkItemProjectOrigin } from '@/components/github/github-work-item-identity' import { createReviewerRequestActions } from './request-actions' import { ReviewerPicker } from './picker' import { ReviewerRequestedList } from './requested-list' @@ -34,7 +34,7 @@ export function PRReviewersPanel({ loading: boolean repoPath: string | null sourceContext?: TaskSourceContext | null - projectOrigin?: PullRequestPageProjectOrigin + projectOrigin?: GitHubWorkItemProjectOrigin onReviewersRequested: (reviewRequests: GitHubAssignableUser[]) => void }): React.JSX.Element { const [open, setOpen] = useState(false) diff --git a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx index e7e58cbc9b9..628aa8b6402 100644 --- a/src/renderer/src/components/right-sidebar/ChecksPanel.tsx +++ b/src/renderer/src/components/right-sidebar/ChecksPanel.tsx @@ -1,3 +1,4 @@ +import type { HostedReviewInfo } from '../../../../shared/hosted-review' import React from 'react' import { Ellipsis, GitMerge, Link, RefreshCw } from 'lucide-react' import { Button } from '@/components/ui/button' @@ -12,7 +13,6 @@ import { getTerminalUrlOrcaBrowserHint, getTerminalUrlSystemBrowserHint } from '../terminal-pane/terminal-link-open-hints' -import type { ChecksPanelReview } from './checks-panel-review' import type { ChecksPanelHostedReviewModifierDestination } from './checks-panel-hosted-review-click-routing' import { translate } from '@/i18n/i18n' import { PullRequestIcon, prStateColor } from './checks-panel/check-presentation' @@ -40,7 +40,7 @@ import { ChecksPanelActiveContent } from './checks-panel/active-content' import { HostedReviewUnlinkMenuItem } from '@/components/HostedReviewUnlinkMenuItem' type ChecksPanelReviewHeaderProps = { - review: ChecksPanelReview + review: HostedReviewInfo isRefreshing: boolean canUnlinkReview: boolean modifierHintDestination: ChecksPanelHostedReviewModifierDestination diff --git a/src/renderer/src/components/right-sidebar/checks-panel-review.ts b/src/renderer/src/components/right-sidebar/checks-panel-review.ts index 5b6ab41613b..8c122185929 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel-review.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel-review.ts @@ -3,8 +3,6 @@ import type { HostedReviewInfo } from '../../../../shared/hosted-review' import { hostedReviewInfoFromGitHubPRInfo } from '../../../../shared/hosted-review-github' import { isGitHubPRSuppressed } from '../../../../shared/worktree/github-pr-suppression' -export type ChecksPanelReview = HostedReviewInfo - export type ChecksPanelReviewSelectionInput = { hostedReview: HostedReviewInfo | null | undefined pr: PRInfo | null | undefined @@ -16,7 +14,7 @@ export type ChecksPanelReviewSelectionInput = { linkedGiteaPR: number | null } -export function gitHubPRToChecksPanelReview(pr: PRInfo): ChecksPanelReview { +export function gitHubPRToChecksPanelReview(pr: PRInfo): HostedReviewInfo { // Why: the checks panel must not maintain a second GitHub PR metadata mapper; // merge-state fields drifting here regressed the right-sidebar action label. return hostedReviewInfoFromGitHubPRInfo(pr) @@ -31,7 +29,7 @@ export function selectChecksPanelReview({ linkedBitbucketPR, linkedAzureDevOpsPR, linkedGiteaPR -}: ChecksPanelReviewSelectionInput): ChecksPanelReview | null { +}: ChecksPanelReviewSelectionInput): HostedReviewInfo | null { const gitLabHostedReview = hostedReview?.provider === 'gitlab' ? hostedReview : null if (gitLabHostedReview) { return gitLabHostedReview diff --git a/src/renderer/src/components/right-sidebar/checks-panel/active-content.tsx b/src/renderer/src/components/right-sidebar/checks-panel/active-content.tsx index caf3c074ea3..77f25963d33 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/active-content.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/active-content.tsx @@ -1,3 +1,4 @@ +import type { HostedReviewInfo } from '../../../../../shared/hosted-review' import React from 'react' import { Check, LoaderCircle, Pencil, X } from 'lucide-react' import { toast } from 'sonner' @@ -17,11 +18,10 @@ import { ConflictingFilesSection, MergeConflictNotice } from './conflict-summary import { ChecksList } from './checks-list' import { PRCommentsList } from './comments-list' import { translate } from '@/i18n/i18n' -import type { ChecksPanelReview } from '../checks-panel-review' import type { ChecksPanelHostedReviewModifierDestination } from '../checks-panel-hosted-review-click-routing' import type { ChecksPanelActiveContentModel } from './active-content-props' type ReviewHeaderComponentProps = { - review: ChecksPanelReview + review: HostedReviewInfo isRefreshing: boolean canUnlinkReview: boolean modifierHintDestination: ChecksPanelHostedReviewModifierDestination diff --git a/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts b/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts index 6c98eff79d6..5b29d049c04 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts +++ b/src/renderer/src/components/right-sidebar/checks-panel/gitlab-review-client.ts @@ -1,14 +1,14 @@ +import type { HostedReviewInfo } from '../../../../../shared/hosted-review' import type { PRComment } from '../../../../../shared/github/comment-types' import type { GitLabDiscussionResolveResult, GitLabWorkItemDetails } from '../../../../../shared/gitlab-types' import { callRuntimeRpc, getActiveRuntimeTarget } from '@/runtime/runtime-rpc-client' -import type { ChecksPanelReview } from '../checks-panel-review' export function isGitLabChecksPanelReview( - review: ChecksPanelReview | null -): review is ChecksPanelReview & { provider: 'gitlab' } { + review: HostedReviewInfo | null +): review is HostedReviewInfo & { provider: 'gitlab' } { return review?.provider === 'gitlab' } diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-ai-acknowledgement.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-ai-acknowledgement.tsx index d1441f09ff4..cb03e857b98 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-ai-acknowledgement.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-ai-acknowledgement.tsx @@ -1,3 +1,4 @@ +import type { HostedReviewInfo } from '../../../../../shared/hosted-review' import { useCallback, useEffect, useRef } from 'react' import { toast } from 'sonner' import { mergePRCommentIntoList } from '@/store/github/pr-comment-cache' @@ -20,7 +21,6 @@ import { resolveGitLabMRDiscussionForChecks } from './gitlab-review-client' import { clearPRCommentsListSelection } from '../pr-comments-list-selection' import { translate } from '@/i18n/i18n' import type { ChecksAgentComposerState } from './panel-state-types' -import type { ChecksPanelReview } from '../checks-panel-review' type ChecksPanelAiAcknowledgementInput = Pick< ChecksPanelControllerState, @@ -72,7 +72,7 @@ export function useChecksPanelAiAcknowledgement(model: ChecksPanelAiAcknowledgem ) const refreshCommentsAfterBulkResolve = useCallback( - async (provider: ChecksPanelReview['provider']): Promise => { + async (provider: HostedReviewInfo['provider']): Promise => { if (provider === 'gitlab') { await fetchGitLabDetails({ commitAsCurrent: true }) return diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-check-and-review-actions.test.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-check-and-review-actions.test.tsx index a1af6dd90fb..df15848410c 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-check-and-review-actions.test.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-check-and-review-actions.test.tsx @@ -5,8 +5,6 @@ import { afterEach, describe, expect, it, vi } from 'vitest' import type { ChecksPanelCheckAndReviewActionsInput } from './check-and-review-action-dependencies' import { useChecksPanelCheckAndReviewActions } from './use-checks-panel-check-and-review-actions' -type Input = ChecksPanelCheckAndReviewActionsInput - afterEach(cleanup) const mocks = vi.hoisted(() => ({ toastError: vi.fn() })) @@ -113,7 +111,9 @@ describe('useChecksPanelCheckAndReviewActions', () => { }) }) -function makeInput(overrides: Partial = {}): Input { +function makeInput( + overrides: Partial = {} +): ChecksPanelCheckAndReviewActionsInput { const worktree = { id: 'repo-1::/workspace/repo', repoId: 'repo-1', @@ -137,7 +137,7 @@ function makeInput(overrides: Partial = {}): Input { updatedAt: null, mergeable: 'UNKNOWN' }, - activeWorktree: worktree as Input['activeWorktree'], + activeWorktree: worktree as ChecksPanelCheckAndReviewActionsInput['activeWorktree'], activeWorktreeId: worktree.id, asyncResultKeyRef: { current: '' }, branch: 'feature/mr', @@ -166,7 +166,7 @@ function makeInput(overrides: Partial = {}): Input { id: 'repo-1', path: '/workspace/repo', connectionId: 'ssh-1' - } as NonNullable, + } as NonNullable, repoConnectionId: 'ssh-1', runtimeEnvironmentId: null, settings: null, @@ -179,7 +179,7 @@ function makeInput(overrides: Partial = {}): Input { stateRequestKey: 'state', updateWorktreeMeta: vi.fn(), ...overrides - } as Input + } as ChecksPanelCheckAndReviewActionsInput } describe('useChecksPanelCheckAndReviewActions GitLab links', () => { @@ -223,7 +223,7 @@ describe('useChecksPanelCheckAndReviewActions GitLab links', () => { activeWorktree: { ...input.activeWorktree, linkedGitLabMR: 43 }, linkedGitLabMR: 43, panelContextKey: 'context::gitlab::43' - } as Input + } as ChecksPanelCheckAndReviewActionsInput }) await act(async () => modal.afterSave({ updates: { linkedGitLabMR: 43 } })) diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx index 86d18a5cb2c..48b8fc53cc1 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-context-state.tsx @@ -1,3 +1,4 @@ +import type { HostedReviewInfo } from '../../../../../shared/hosted-review' import { useCallback, useEffect, useState } from 'react' import { useAppStore } from '@/store' import { useNow } from '@/hooks/use-now' @@ -5,7 +6,7 @@ import { isFolderRepo } from '../../../../../shared/repo-kind' import { getGitHubPRCacheKey } from '@/store/slices/github-cache-key' import { getHostedReviewCacheKey } from '@/store/slices/hosted-review-cache-identity' import { selectReviewCacheEntry } from '../review-cache-entry-selection' -import { selectChecksPanelReview, type ChecksPanelReview } from '../checks-panel-review' +import { selectChecksPanelReview } from '../checks-panel-review' import { isGitLabChecksPanelReview } from './gitlab-review-client' import { clearPendingPRCommentAiAck } from '../pr-comments-ai-launch-ack' import { @@ -222,7 +223,7 @@ export function useChecksPanelContextState(model: ChecksPanelContextStateInput) const linkedBitbucketPR = activeWorktree?.linkedBitbucketPR ?? null const linkedAzureDevOpsPR = activeWorktree?.linkedAzureDevOpsPR ?? null const linkedGiteaPR = activeWorktree?.linkedGiteaPR ?? null - const activeReview: ChecksPanelReview | null = selectChecksPanelReview({ + const activeReview: HostedReviewInfo | null = selectChecksPanelReview({ hostedReview, pr, linkedPR, diff --git a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.tsx b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.tsx index 94df8aea1cc..fb003406c28 100644 --- a/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.tsx +++ b/src/renderer/src/components/right-sidebar/checks-panel/use-checks-panel-generation.tsx @@ -4,7 +4,7 @@ import { getConnectionId } from '@/lib/connection-context' import { generateRuntimePullRequestFields, cancelRuntimeGeneratePullRequestFields, - type RuntimeGeneratePullRequestFieldsOverrides + type RuntimeGenerateCommitMessageOverrides } from '@/runtime/runtime-git-client' import type { ChecksPanelReviewState } from './use-checks-panel-review-state' import type { ChecksPanelControllerState } from './use-checks-panel-controller-state' @@ -62,7 +62,7 @@ export function useChecksPanelGeneration(model: ChecksPanelGenerationInput) { async ( fields: PullRequestGenerationFields, fieldRevisions: PullRequestFieldRevisions, - overrides?: RuntimeGeneratePullRequestFieldsOverrides + overrides?: RuntimeGenerateCommitMessageOverrides ): Promise => { if (!repo || !activePullRequestGenerationKey || !activeWorktreePath || !branch) { return diff --git a/src/renderer/src/components/right-sidebar/create-pull-request-dialog-field-model.ts b/src/renderer/src/components/right-sidebar/create-pull-request-dialog-field-model.ts index 927ef3c4a57..64b324bad33 100644 --- a/src/renderer/src/components/right-sidebar/create-pull-request-dialog-field-model.ts +++ b/src/renderer/src/components/right-sidebar/create-pull-request-dialog-field-model.ts @@ -1,6 +1,6 @@ import type { AppState } from '@/store' import type { - RuntimeGeneratePullRequestFieldsOverrides, + RuntimeGenerateCommitMessageOverrides, RuntimeGitContext } from '@/runtime/runtime-git-client' import type { Repo } from '../../../../shared/repo-types' @@ -44,7 +44,7 @@ export type UseCreatePullRequestDialogFieldsOptions = { onGenerate: ( fields: PullRequestDraftFields, fieldRevisions: PullRequestFieldRevisions, - overrides?: RuntimeGeneratePullRequestFieldsOverrides + overrides?: RuntimeGenerateCommitMessageOverrides ) => void onCancelGenerate: () => void } diff --git a/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts b/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts index b22ed9ce23c..9e1e5e54f85 100644 --- a/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts +++ b/src/renderer/src/components/right-sidebar/github-refresh-error-copy.ts @@ -1,4 +1,4 @@ -import type { PRRefreshUpstreamErrorType } from '../../../../shared/github/pull-request-refresh-types' +import type { PRRefreshErrorType } from '../../../../shared/github/pull-request-refresh-types' import { translate } from '@/i18n/i18n' export type ChecksPanelErrorCopy = { title: string; description: string } @@ -10,7 +10,7 @@ export type ChecksPanelErrorCopy = { title: string; description: string } * "could not refresh" copy — those are user-actionable, not "GitHub is down". */ export function getGitHubUnavailableEmptyStateCopy( - errorType: PRRefreshUpstreamErrorType | undefined + errorType: PRRefreshErrorType | undefined ): ChecksPanelErrorCopy | null { if (errorType === 'server_error') { return { @@ -57,7 +57,7 @@ export function getGitHubUnavailableEmptyStateCopy( * Always returns a line: GitHub-attributed for outage kinds, generic otherwise. */ export function getChecksPanelRefreshErrorBannerLine( - errorType: PRRefreshUpstreamErrorType | undefined + errorType: PRRefreshErrorType | undefined ): string { if (errorType === 'server_error') { return translate( diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-action-types.ts b/src/renderer/src/components/right-sidebar/source-control-primary-action-types.ts index d4043333442..af140c3f1eb 100644 --- a/src/renderer/src/components/right-sidebar/source-control-primary-action-types.ts +++ b/src/renderer/src/components/right-sidebar/source-control-primary-action-types.ts @@ -6,25 +6,14 @@ import type { SourceControlRemoteOpKind } from '../../../../shared/source-control-primary-action-decision-types' -// Why: the primary button collapses to one-label-per-action. Compound -// kinds ('commit_push', 'commit_sync', 'commit_publish') live in -// DropdownActionKind only — never on the primary — so they are not part -// of this union. Narrowing the type here is load-bearing: it lets -// `handlePrimaryClick` switch exhaustively over only the kinds the -// primary can actually emit, and it kills the compound-commit branch in -// the isRemoteOperationActive tooltip below at compile time. -export type PrimaryActionKind = SourceControlPrimaryActionKind - -// Why: the in-flight remote op tracker stores which action the user actually -// triggered, so the primary button can mirror that label/spinner instead of -// claiming a stale or unrelated operation is running. Dropdown-only remote -// kinds are included because they participate in the busy flag, but they are -// intentionally NOT in PrimaryActionKind — when Fetch is in flight the primary -// keeps its natural label, while Force Push maps back to the push icon/slot. -export type RemoteOpKind = SourceControlRemoteOpKind - +// Why: the primary button collapses to one-label-per-action. Compound kinds +// ('commit_push', 'commit_sync', 'commit_publish') live in DropdownActionKind +// only — never on the primary — so SourceControlPrimaryActionKind excludes +// them, which is what lets `handlePrimaryClick` switch exhaustively and kills +// the compound-commit branch in the isRemoteOperationActive tooltip below at +// compile time. export type PrimaryAction = { - kind: PrimaryActionKind + kind: SourceControlPrimaryActionKind label: string title: string disabled: boolean @@ -46,7 +35,7 @@ export type PrimaryActionInputs = { // remote op is in flight. Used by the in-flight branch below to mirror // the user-triggered action on the primary button instead of leaving a // stale label that no longer matches what the slice is doing. - inFlightRemoteOpKind?: RemoteOpKind | null + inFlightRemoteOpKind?: SourceControlRemoteOpKind | null hostedReviewCreation?: HostedReviewCreationEligibility | null // Why: branch-compare counts feed Create Review intent eligibility and // force-push labels; publishing itself can push the current HEAD even at 0. diff --git a/src/renderer/src/components/right-sidebar/source-control-primary-action.ts b/src/renderer/src/components/right-sidebar/source-control-primary-action.ts index 7cddeccd799..f7e19badd71 100644 --- a/src/renderer/src/components/right-sidebar/source-control-primary-action.ts +++ b/src/renderer/src/components/right-sidebar/source-control-primary-action.ts @@ -18,12 +18,9 @@ import { describeSyncCounts } from './source-control-primary-action-titles' -export type { - PrimaryActionKind, - RemoteOpKind, - PrimaryAction, - PrimaryActionInputs -} from './source-control-primary-action-types' +export type { PrimaryAction, PrimaryActionInputs } from './source-control-primary-action-types' +export type { SourceControlRemoteOpKind } from '../../../../shared/source-control-primary-action-decision-types' +export type { SourceControlPrimaryActionKind } from '../../../../shared/source-control-primary-action-decision-types' // Why: the shared module owns the pure state-machine logic; this renderer // adapter keeps localized copy and the historical exported shape in place. diff --git a/src/renderer/src/components/right-sidebar/source-control/commit/commit-area-types.ts b/src/renderer/src/components/right-sidebar/source-control/commit/commit-area-types.ts index 34c1f434697..2ec08bf85ec 100644 --- a/src/renderer/src/components/right-sidebar/source-control/commit/commit-area-types.ts +++ b/src/renderer/src/components/right-sidebar/source-control/commit/commit-area-types.ts @@ -1,5 +1,5 @@ import type { DropdownActionKind, DropdownEntry } from '../../source-control-dropdown-item-types' -import type { PrimaryAction, RemoteOpKind } from '../../source-control-primary-action' +import type { PrimaryAction, SourceControlRemoteOpKind } from '../../source-control-primary-action' import type { SourceControlPushRecovery } from '../sync/push-recovery' import type { SourceControlActionRecipe, @@ -39,7 +39,7 @@ export type CommitAreaProps = { hasPartiallyStagedChanges: boolean hasUnresolvedConflicts: boolean isRemoteOperationActive: boolean - inFlightRemoteOpKind: RemoteOpKind | null + inFlightRemoteOpKind: SourceControlRemoteOpKind | null primaryAction: PrimaryAction dropdownItems: DropdownEntry[] fixCommitFailureRecipe?: SourceControlActionRecipe diff --git a/src/renderer/src/components/right-sidebar/source-control/review/use-pull-request-generation.ts b/src/renderer/src/components/right-sidebar/source-control/review/use-pull-request-generation.ts index e711999901b..94f256b849e 100644 --- a/src/renderer/src/components/right-sidebar/source-control/review/use-pull-request-generation.ts +++ b/src/renderer/src/components/right-sidebar/source-control/review/use-pull-request-generation.ts @@ -3,7 +3,7 @@ import { getConnectionId } from '@/lib/connection-context' import { cancelRuntimeGeneratePullRequestFields, generateRuntimePullRequestFields, - type RuntimeGeneratePullRequestFieldsOverrides + type RuntimeGenerateCommitMessageOverrides } from '@/runtime/runtime-git-client' import { useAppStore } from '@/store' import { @@ -80,7 +80,7 @@ export function useSourceControlPullRequestGeneration({ async ( fields: PullRequestGenerationFields, fieldRevisions: PullRequestFieldRevisions, - overrides?: RuntimeGeneratePullRequestFieldsOverrides + overrides?: RuntimeGenerateCommitMessageOverrides ): Promise => { if (!activeRepo || !activePullRequestGenerationKey || !worktreePath || !branchName) { return diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/action-error.ts b/src/renderer/src/components/right-sidebar/source-control/sync/action-error.ts index 2aad59eab00..4b4b88519ca 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/action-error.ts +++ b/src/renderer/src/components/right-sidebar/source-control/sync/action-error.ts @@ -1,8 +1,8 @@ -import type { RemoteOpKind } from '../../source-control-primary-action' +import type { SourceControlRemoteOpKind } from '../../source-control-primary-action' import type { GitStatusEntry } from '../../../../../../shared/git-status-types' export type AbortActionErrorKind = 'abort_merge' | 'abort_rebase' -export type SourceControlActionErrorKind = RemoteOpKind | AbortActionErrorKind +export type SourceControlActionErrorKind = SourceControlRemoteOpKind | AbortActionErrorKind export type SourceControlRecoveryStatusEntry = Pick export const SOURCE_CONTROL_ACTION_ERROR_ENTRY_SNAPSHOT_LIMIT = 120 diff --git a/src/renderer/src/components/right-sidebar/source-control/sync/remote-refresh.ts b/src/renderer/src/components/right-sidebar/source-control/sync/remote-refresh.ts index 0571ab25625..7114d803550 100644 --- a/src/renderer/src/components/right-sidebar/source-control/sync/remote-refresh.ts +++ b/src/renderer/src/components/right-sidebar/source-control/sync/remote-refresh.ts @@ -4,9 +4,9 @@ import { } from '@/lib/source-control-remote-error' import type { GitConflictOperation } from '../../../../../../shared/git-status-types' import type { SourceControlActionError } from './action-error' -import type { RemoteOpKind } from '../../source-control-primary-action' +import type { SourceControlRemoteOpKind } from '../../source-control-primary-action' -export function resolveRemoteActionError(kind: RemoteOpKind, error: unknown): string { +export function resolveRemoteActionError(kind: SourceControlRemoteOpKind, error: unknown): string { return resolveRemoteOperationErrorMessage(error, { publish: kind === 'publish', isPush: kind === 'push', diff --git a/src/renderer/src/components/right-sidebar/use-create-pull-request-field-generation.ts b/src/renderer/src/components/right-sidebar/use-create-pull-request-field-generation.ts index 3ea2d56923c..ba1a085c7ff 100644 --- a/src/renderer/src/components/right-sidebar/use-create-pull-request-field-generation.ts +++ b/src/renderer/src/components/right-sidebar/use-create-pull-request-field-generation.ts @@ -4,7 +4,7 @@ import { useAppStore, type AppState } from '@/store' import { cancelRuntimeGeneratePullRequestFields, generateRuntimePullRequestFields, - type RuntimeGeneratePullRequestFieldsOverrides + type RuntimeGenerateCommitMessageOverrides } from '@/runtime/runtime-git-client' import type { HostedReviewCreationEligibility } from '../../../../shared/hosted-review' import type { ResolveSourceControlAiResult } from '../../../../shared/source-control-ai' @@ -53,7 +53,7 @@ type CreatePullRequestFieldGenerationResult = { effectiveGenerateError: string | null generateDisabled: boolean generateDisabledReason: string | undefined - handleGenerate: (overrides?: RuntimeGeneratePullRequestFieldsOverrides) => Promise + handleGenerate: (overrides?: RuntimeGenerateCommitMessageOverrides) => Promise handleCancelGenerate: () => void } @@ -99,7 +99,7 @@ export function useCreatePullRequestFieldGeneration({ const generateDisabled = !effectiveGenerating && Boolean(generateDisabledReason) const handleGenerate = useCallback( - async (overrides?: RuntimeGeneratePullRequestFieldsOverrides): Promise => { + async (overrides?: RuntimeGenerateCommitMessageOverrides): Promise => { if (!worktreePath || !base.trim() || effectiveGenerating || generateDisabled) { return } diff --git a/src/renderer/src/components/settings/AccountsPane.tsx b/src/renderer/src/components/settings/AccountsPane.tsx index eb19fc54306..1da5af63358 100644 --- a/src/renderer/src/components/settings/AccountsPane.tsx +++ b/src/renderer/src/components/settings/AccountsPane.tsx @@ -39,7 +39,6 @@ import { GrokAccountsSection } from './GrokAccountsSection' import type { AccountsPaneProps, AccountsPaneSectionModel, - ClaudeAccountAction, CodexAccountAction, RemoveAccountTarget } from './accounts-pane-types' @@ -141,7 +140,7 @@ export function AccountsPane({ const [codexAction, setCodexAction] = useState('idle') const [claudeAccounts, setClaudeAccounts] = useState(emptyClaudeAccountsState) - const [claudeAction, setClaudeAction] = useState('idle') + const [claudeAction, setClaudeAction] = useState('idle') // Why: capture the account's runtime slot when the dialog opens; the roster // can change underneath an open dialog and lose the slot to diff for restarts. const [removeCodexTarget, setRemoveCodexTarget] = useState(null) diff --git a/src/renderer/src/components/settings/MobilePairedDevicesSection.tsx b/src/renderer/src/components/settings/MobilePairedDevicesSection.tsx index d94558cb01d..e4ea94004ab 100644 --- a/src/renderer/src/components/settings/MobilePairedDevicesSection.tsx +++ b/src/renderer/src/components/settings/MobilePairedDevicesSection.tsx @@ -3,10 +3,8 @@ import { Button } from '../ui/button' import { translate } from '@/i18n/i18n' import type { PairedMobileDevice } from '../mobile/paired-mobile-devices' -export type PairedDevice = PairedMobileDevice - type MobilePairedDevicesSectionProps = { - devices: readonly PairedDevice[] + devices: readonly PairedMobileDevice[] hasQrCode: boolean onRevokeDevice: (deviceId: string) => void } diff --git a/src/renderer/src/components/settings/MobilePane.test.tsx b/src/renderer/src/components/settings/MobilePane.test.tsx index 28f83ccf697..8d832611119 100644 --- a/src/renderer/src/components/settings/MobilePane.test.tsx +++ b/src/renderer/src/components/settings/MobilePane.test.tsx @@ -13,10 +13,8 @@ import { } from '../mobile/paired-mobile-devices' import type { MobilePairingConnectionMode } from '../../../../shared/mobile-pairing-connection-mode' -type PairedDevice = PairedMobileDevice - type PairedDevicesProps = { - devices: readonly PairedDevice[] + devices: readonly PairedMobileDevice[] hasQrCode: boolean onRevokeDevice: (deviceId: string) => void } @@ -752,7 +750,7 @@ describe('MobilePane pairing connection mode', () => { const mountedRoots: Root[] = [] -function pairedDevice(deviceId: string): PairedDevice { +function pairedDevice(deviceId: string): PairedMobileDevice { return { deviceId, name: deviceId, diff --git a/src/renderer/src/components/settings/accounts-pane-account-actions.ts b/src/renderer/src/components/settings/accounts-pane-account-actions.ts index 09be1ce001b..eb16bda5c20 100644 --- a/src/renderer/src/components/settings/accounts-pane-account-actions.ts +++ b/src/renderer/src/components/settings/accounts-pane-account-actions.ts @@ -16,9 +16,8 @@ import { getProviderAccountRuntime } from './provider-account-visibility' import type { - ClaudeAccountAction, - ClaudeAccountActionRunner, CodexAccountAction, + ClaudeAccountActionRunner, CodexAccountActionRunner, LocalAccountRuntime } from './accounts-pane-types' @@ -138,7 +137,7 @@ type ClaudeActionContext = { isRemoteAccountScope: boolean claudeAccounts: ClaudeRateLimitAccountsState setClaudeAccounts: Dispatch> - setClaudeAction: Dispatch> + setClaudeAction: Dispatch> fetchSettings: () => Promise recordFeatureInteraction: (featureId: FeatureInteractionId) => void } diff --git a/src/renderer/src/components/settings/accounts-pane-types.ts b/src/renderer/src/components/settings/accounts-pane-types.ts index 2799cfd6509..c251fea74b4 100644 --- a/src/renderer/src/components/settings/accounts-pane-types.ts +++ b/src/renderer/src/components/settings/accounts-pane-types.ts @@ -35,8 +35,6 @@ export type CodexAccountAction = | `remove:${string}` | `select:${string}` -export type ClaudeAccountAction = CodexAccountAction - export type RemoveAccountTarget = { id: string runtime: ProviderAccountRuntimeView @@ -54,7 +52,7 @@ export type CodexAccountActionRunner = ( ) => Promise export type ClaudeAccountActionRunner = ( - action: ClaudeAccountAction, + action: CodexAccountAction, operation: () => Promise, actionRuntime?: ProviderAccountRuntimeView ) => Promise @@ -78,7 +76,7 @@ export type AccountsPaneSectionModel = { accountRuntimeUnavailable: boolean accountVisibilityOptions: ProviderAccountVisibilityOptions claudeAccounts: ClaudeRateLimitAccountsState - claudeAction: ClaudeAccountAction + claudeAction: CodexAccountAction visibleClaudeAccounts: ClaudeRateLimitAccountsState['accounts'] systemClaudeActive: boolean setRemoveClaudeTarget: Dispatch> diff --git a/src/renderer/src/components/settings/integrations-pane-status.ts b/src/renderer/src/components/settings/integrations-pane-status.ts index 35d891827cd..7f6db7add08 100644 --- a/src/renderer/src/components/settings/integrations-pane-status.ts +++ b/src/renderer/src/components/settings/integrations-pane-status.ts @@ -1,9 +1,9 @@ import type { PreflightStatus } from '../../../../preload/api-types' export type GhStatus = 'checking' | 'connected' | 'not-installed' | 'not-authenticated' -// Why: parallel to GhStatus — GitLab uses glab and the same three failure -// modes (probe in-flight / installed-but-unauth / missing entirely). -export type GlabStatus = GhStatus +// Why: spelled out rather than aliased to GhStatus — glab is a separate binary with the same three +// failure modes (probe in-flight / installed-but-unauth / missing entirely), not the same status. +export type GlabStatus = 'checking' | 'connected' | 'not-installed' | 'not-authenticated' export type BitbucketStatus = 'checking' | 'connected' | 'not-configured' | 'not-authenticated' export type AzureDevOpsStatus = 'checking' | 'configured' | 'not-configured' | 'not-authenticated' export type GiteaStatus = 'checking' | 'configured' | 'not-configured' | 'not-authenticated' diff --git a/src/renderer/src/components/settings/runtime-environment-host-details.ts b/src/renderer/src/components/settings/runtime-environment-host-details.ts index a4ce9cafd82..92905bb9962 100644 --- a/src/renderer/src/components/settings/runtime-environment-host-details.ts +++ b/src/renderer/src/components/settings/runtime-environment-host-details.ts @@ -171,11 +171,9 @@ export function isRuntimeEnvironmentRemovalBlocked( return activeRuntimeEnvironmentId === environmentId } -export type RuntimeServerConnectionState = RuntimeHostConnectionState - export function getRuntimeServerConnectionState( details: RuntimeHostDetails | undefined -): RuntimeServerConnectionState { +): RuntimeHostConnectionState { if (!details || details.status === 'loading') { return 'checking' } @@ -202,11 +200,11 @@ export function getRuntimeServerConnectionState( }) } -export function isRuntimeServerTransportConnected(state: RuntimeServerConnectionState): boolean { +export function isRuntimeServerTransportConnected(state: RuntimeHostConnectionState): boolean { return isConnectedRuntimeHostState(state) } -export function getRuntimeServerConnectionLabel(state: RuntimeServerConnectionState): string { +export function getRuntimeServerConnectionLabel(state: RuntimeHostConnectionState): string { switch (state) { case 'connected': return translate( @@ -241,7 +239,7 @@ export function getRuntimeServerConnectionLabel(state: RuntimeServerConnectionSt } } -export function getRuntimeServerDotClass(state: RuntimeServerConnectionState): string { +export function getRuntimeServerDotClass(state: RuntimeHostConnectionState): string { switch (state) { case 'connected': return 'bg-emerald-500' diff --git a/src/renderer/src/components/sidebar/OrcaYamlTrustDialog.tsx b/src/renderer/src/components/sidebar/OrcaYamlTrustDialog.tsx index 60ea9bcf90f..5001f1ab480 100644 --- a/src/renderer/src/components/sidebar/OrcaYamlTrustDialog.tsx +++ b/src/renderer/src/components/sidebar/OrcaYamlTrustDialog.tsx @@ -12,16 +12,14 @@ import { useAppStore } from '@/store' import type { OrcaHookScriptKind } from '@/lib/orca-hook-trust' import { translate } from '@/i18n/i18n' -type ScriptKind = OrcaHookScriptKind - -const SCRIPT_KIND_LABEL: Record = { +const SCRIPT_KIND_LABEL: Record = { setup: 'setup script', archive: 'archive script', issueCommand: 'issue command', vmRecipe: 'VM recipe' } -const SCRIPT_KIND_TRIGGER: Record = { +const SCRIPT_KIND_TRIGGER: Record = { setup: 'when this workspace is created', archive: 'when this workspace is removed', issueCommand: 'when this workspace launches with a linked issue', @@ -53,7 +51,7 @@ const OrcaYamlTrustDialog = React.memo(function OrcaYamlTrustDialog() { const repoId = typeof modalData.repoId === 'string' ? modalData.repoId : '' const repoName = typeof modalData.repoName === 'string' ? modalData.repoName : 'this repository' - const scriptKind: ScriptKind = + const scriptKind: OrcaHookScriptKind = modalData.scriptKind === 'archive' ? 'archive' : modalData.scriptKind === 'issueCommand' diff --git a/src/renderer/src/components/sidebar/StatusIndicator.test.ts b/src/renderer/src/components/sidebar/StatusIndicator.test.ts index 1c238615628..6d42ae34bfc 100644 --- a/src/renderer/src/components/sidebar/StatusIndicator.test.ts +++ b/src/renderer/src/components/sidebar/StatusIndicator.test.ts @@ -1,7 +1,8 @@ import React from 'react' import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it, vi } from 'vitest' -import StatusIndicator, { type Status } from './StatusIndicator' +import type { WorktreeStatus } from '@/lib/worktree-status' +import StatusIndicator from './StatusIndicator' vi.mock('@/components/StateIndicatorTooltip', async () => { const { createElement } = await import('react') @@ -19,11 +20,11 @@ vi.mock('@/components/StateIndicatorTooltip', async () => { } }) -function renderMarkup(status: Status): string { +function renderMarkup(status: WorktreeStatus): string { return renderToStaticMarkup(React.createElement(StatusIndicator, { status })) } -function renderDotClassNames(status: Status): string[] { +function renderDotClassNames(status: WorktreeStatus): string[] { const markup = renderMarkup(status) const dotClassName = markup.match(/, 'title'> & { - status: Status + status: WorktreeStatus showTooltip?: boolean tooltipSide?: StateIndicatorTooltipSide } -const AGENT_STATUS_TOOLTIP_STATUSES = new Set([ +const AGENT_STATUS_TOOLTIP_STATUSES = new Set([ 'working', 'monitoring', 'permission', diff --git a/src/renderer/src/components/sidebar/project-group-header-drop.ts b/src/renderer/src/components/sidebar/project-group-header-drop.ts index 8dc5e8609aa..c03ab13d76c 100644 --- a/src/renderer/src/components/sidebar/project-group-header-drop.ts +++ b/src/renderer/src/components/sidebar/project-group-header-drop.ts @@ -17,8 +17,6 @@ export type ProjectGroupHeaderDragRect = { sectionBottom?: number } -export type ProjectGroupHeaderDropPreview = WorktreeSidebarHeaderDropPreview - export type ProjectGroupTabOrderUpdate = { groupId: string tabOrder: number @@ -185,7 +183,7 @@ export function computeProjectGroupHeaderDropPreview(args: { rects: readonly ProjectGroupHeaderDragRect[] sidebarProjectGroupHeaderIds: readonly string[] contentBottom?: number -}): ProjectGroupHeaderDropPreview | null { +}): WorktreeSidebarHeaderDropPreview | null { const { rects, sidebarProjectGroupHeaderIds } = args return computeWorktreeSidebarHeaderDropPreview({ pointerY: args.pointerY, diff --git a/src/renderer/src/components/sidebar/project-header-drop.ts b/src/renderer/src/components/sidebar/project-header-drop.ts index 4cc23ddd9c6..04d782f1b11 100644 --- a/src/renderer/src/components/sidebar/project-header-drop.ts +++ b/src/renderer/src/components/sidebar/project-header-drop.ts @@ -20,8 +20,6 @@ export type ProjectHeaderDragRect = { sectionBottom?: number } -export type ProjectHeaderDropPreview = WorktreeSidebarHeaderDropPreview - export function getProjectHeaderDragBucketKey( repo: Pick ): ProjectHeaderDragBucketKey { @@ -189,7 +187,7 @@ export function computeProjectHeaderDropPreview(args: { rects: readonly ProjectHeaderDragRect[] sidebarRepoHeaderIds: readonly string[] contentBottom?: number -}): ProjectHeaderDropPreview | null { +}): WorktreeSidebarHeaderDropPreview | null { const { rects, sidebarRepoHeaderIds } = args return computeWorktreeSidebarHeaderDropPreview({ pointerY: args.pointerY, diff --git a/src/renderer/src/components/stats/ShareUsageButton.tsx b/src/renderer/src/components/stats/ShareUsageButton.tsx index fe009152d43..de7b3f6e93b 100644 --- a/src/renderer/src/components/stats/ShareUsageButton.tsx +++ b/src/renderer/src/components/stats/ShareUsageButton.tsx @@ -7,8 +7,6 @@ import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from '../ui/ import { ShareUsageCard, type ShareUsageCardProps } from './ShareUsageCard' import { translate } from '@/i18n/i18n' -type ShareUsageButtonProps = ShareUsageCardProps - function XIcon(): React.JSX.Element { return ( @@ -17,7 +15,7 @@ function XIcon(): React.JSX.Element { ) } -export function ShareUsageButton(props: ShareUsageButtonProps): React.JSX.Element { +export function ShareUsageButton(props: ShareUsageCardProps): React.JSX.Element { const cardRef = useRef(null) const [copied, setCopied] = useState(false) const [capturing, setCapturing] = useState(false) diff --git a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.test.ts b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.test.ts index 91e342d06b6..d8005e88158 100644 --- a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.test.ts +++ b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.test.ts @@ -5,7 +5,8 @@ import type { TerminalTab } from '../../../../shared/terminal-tab-types' import type { Worktree } from '../../../../shared/worktree/types' import { mergeSnapshotAndSessions, UNATTRIBUTED_REPO_ID } from './mergeSnapshotAndSessions' import { requiresKillConfirmation } from './resource-session-kill-confirmation' -import type { DaemonSession, MergeContext } from './resource-usage-merge-types' +import type { MergeContext } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' function emptyAppMemory() { return { @@ -153,7 +154,7 @@ describe('mergeSnapshotAndSessions', () => { history: [], sessions: [{ sessionId: 'pty-1', paneKey: null, pid: 999, cpu: 0.1, memory: 50_000_000 }] } - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'pty-1', cwd: '/Users/me/Triton', title: 'shell', agentOwnership: 'absent' as const } ] const out = mergeSnapshotAndSessions(makeSnapshot([wt]), ds, baseCtx()) @@ -179,7 +180,7 @@ describe('mergeSnapshotAndSessions', () => { history: [], sessions: [{ sessionId: 'pty-agent', paneKey: null, pid: 999, cpu: 0.1, memory: 50_000_000 }] } - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'pty-agent', cwd: '/Users/me/Triton', @@ -234,7 +235,7 @@ describe('mergeSnapshotAndSessions', () => { }) it('@@ parse: an SSH-style session id resolves to its worktree group', () => { - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'orca::/remote/Stingray@@abcd1234', cwd: '', @@ -275,7 +276,7 @@ describe('mergeSnapshotAndSessions', () => { // A live local daemon session whose registry entry the renderer hasn't // re-spawned yet must NOT be flagged as remote. Under the old // predicate (`!hasLocalSamples`) it was — that was the bug. - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'orca::/local/Triton@@deadbeef', cwd: '/local/Triton', @@ -299,7 +300,7 @@ describe('mergeSnapshotAndSessions', () => { it('tab walk wins over @@ parse when they disagree', () => { const tabId = 'tab-xyz' - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'orca::/wrong/path@@feedface', cwd: '', @@ -322,7 +323,7 @@ describe('mergeSnapshotAndSessions', () => { it('treats startup deferred reattach tab ptyId wake hints as bound sessions', () => { const tabId = 'tab-restored' const sessionId = 'orca::/Users/me/Triton@@deferred' - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: sessionId, cwd: '/Users/me/Triton', @@ -364,7 +365,7 @@ describe('mergeSnapshotAndSessions', () => { history: [], sessions: [] } - const remoteDs: DaemonSession[] = [ + const remoteDs: PtyListedSession[] = [ { id: 'remote-repo::/remote/Stingray@@1234', cwd: '', @@ -407,7 +408,7 @@ describe('mergeSnapshotAndSessions', () => { history: [], sessions: [{ sessionId: 'runtime-pty', paneKey: null, pid: 2, cpu: 5, memory: 500_000_000 }] } - const sessions: DaemonSession[] = [ + const sessions: PtyListedSession[] = [ { id: 'runtime-repo::/runtime/Wt@@future-runtime', cwd: '', @@ -468,7 +469,7 @@ describe('mergeSnapshotAndSessions', () => { // not evidence of remoteness — we just don't know what it belongs // to. The chip should stay off; the row still surfaces in the // unattributed bucket with `—` cells because we have no sample. - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'opaque-id-without-prefix', cwd: '', title: 'shell', agentOwnership: 'absent' as const } ] const out = mergeSnapshotAndSessions(null, ds, baseCtx()) @@ -522,7 +523,7 @@ describe('mergeSnapshotAndSessions', () => { }) it('remote-orphan interaction state: null metrics + bound=false', () => { - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'orca::/remote/Wt@@deadbeef', cwd: '', @@ -542,7 +543,7 @@ describe('mergeSnapshotAndSessions', () => { }) it('uses repoDisplayNameById to humanize new project groups when available', () => { - const ds: DaemonSession[] = [ + const ds: PtyListedSession[] = [ { id: 'stably-ai/orca::/remote/Wt@@1', cwd: '', title: '', agentOwnership: 'absent' as const } ] const ctx = baseCtx({ @@ -584,7 +585,7 @@ it('indexes tab labels when merging a large resource inventory', () => { }, ptyId: `pty-${i}` })) - const sessions: DaemonSession[] = tabs.map((_, i) => ({ + const sessions: PtyListedSession[] = tabs.map((_, i) => ({ id: `pty-${i}`, cwd: '/repo', title: '', @@ -602,7 +603,7 @@ it('indexes tab labels when merging a large resource inventory', () => { }) it('does not scan accumulated worktree rows for unrelated daemon sessions', () => { - const sessions: DaemonSession[] = Array.from({ length: 1000 }, (_, i) => ({ + const sessions: PtyListedSession[] = Array.from({ length: 1000 }, (_, i) => ({ id: `opaque-${i}`, cwd: '', title: '', diff --git a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts index 5cd992e2b60..6393260e7aa 100644 --- a/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts +++ b/src/renderer/src/components/status-bar/mergeSnapshotAndSessions.ts @@ -28,8 +28,8 @@ import { getRepoIdFromWorktreeId, getWorktreePathBasenameFromId } from '../../../../shared/worktree/id' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' import type { - DaemonSession, MergeContext, UnifiedProjectGroup, UnifiedSessionRow, @@ -93,7 +93,7 @@ function resolveSnapshotSessionLabel( } function resolveDaemonSessionLabel( - session: DaemonSession, + session: PtyListedSession, resolvedWorktreeId: string | null, tabId: string | null, ctx: MergeContext, @@ -138,7 +138,7 @@ export const UNATTRIBUTED_REPO_NAME = 'Unattributed' export function mergeSnapshotAndSessions( snapshot: MemorySnapshot | null, - daemonSessions: readonly DaemonSession[], + daemonSessions: readonly PtyListedSession[], ctx: MergeContext ): UnifiedProjectGroup[] { const repos = new Map() diff --git a/src/renderer/src/components/status-bar/resource-session-bindings.ts b/src/renderer/src/components/status-bar/resource-session-bindings.ts index 2f04279df62..c531e80a0a5 100644 --- a/src/renderer/src/components/status-bar/resource-session-bindings.ts +++ b/src/renderer/src/components/status-bar/resource-session-bindings.ts @@ -1,6 +1,8 @@ import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/terminal-tab-types' -import { mayDestroyWithoutOwnerEvidence } from '../../../../shared/pty-listed-session' -import type { DaemonSession } from './resource-usage-merge-types' +import { + mayDestroyWithoutOwnerEvidence, + type PtyListedSession +} from '../../../../shared/pty-listed-session' export type ResourceSessionBindingInputs = { tabsByWorktree: Record @@ -101,9 +103,9 @@ export function buildResourceSessionBindingIndex( * killed cannot diverge — that divergence would be live agent sessions (#8459). */ export function selectUnboundDaemonSessions( - sessions: readonly DaemonSession[], + sessions: readonly PtyListedSession[], inputs: ResourceSessionBindingInputs -): DaemonSession[] { +): PtyListedSession[] { if (!inputs.workspaceSessionReady) { return [] } @@ -116,7 +118,7 @@ export function selectUnboundDaemonSessions( } export function countUnboundDaemonSessions( - sessions: readonly DaemonSession[], + sessions: readonly PtyListedSession[], inputs: ResourceSessionBindingInputs ): number { return selectUnboundDaemonSessions(sessions, inputs).length diff --git a/src/renderer/src/components/status-bar/resource-session-inventory.test.ts b/src/renderer/src/components/status-bar/resource-session-inventory.test.ts index 63784cc00a5..b664497c454 100644 --- a/src/renderer/src/components/status-bar/resource-session-inventory.test.ts +++ b/src/renderer/src/components/status-bar/resource-session-inventory.test.ts @@ -5,9 +5,9 @@ import { removeSessionFromInventory, removeSessionsFromInventory } from './resource-session-inventory' -import type { DaemonSession } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' -function session(id: string): DaemonSession { +function session(id: string): PtyListedSession { return { id, cwd: '/workspace', title: id, agentOwnership: 'absent' as const } } diff --git a/src/renderer/src/components/status-bar/resource-session-inventory.ts b/src/renderer/src/components/status-bar/resource-session-inventory.ts index fda71c1beae..63966a628b4 100644 --- a/src/renderer/src/components/status-bar/resource-session-inventory.ts +++ b/src/renderer/src/components/status-bar/resource-session-inventory.ts @@ -1,8 +1,8 @@ -import type { DaemonSession } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' /** Last-known daemon terminal inventory for the Resource Manager badge. */ export type DaemonSessionInventory = { - sessions: DaemonSession[] + sessions: PtyListedSession[] count: number } @@ -11,7 +11,9 @@ export const EMPTY_DAEMON_SESSION_INVENTORY: DaemonSessionInventory = { count: 0 } -export function inventoryFromSessions(sessions: readonly DaemonSession[]): DaemonSessionInventory { +export function inventoryFromSessions( + sessions: readonly PtyListedSession[] +): DaemonSessionInventory { return { sessions: sessions.slice(), count: sessions.length diff --git a/src/renderer/src/components/status-bar/resource-usage-merge-types.ts b/src/renderer/src/components/status-bar/resource-usage-merge-types.ts index a67f5e0ec66..ef074825166 100644 --- a/src/renderer/src/components/status-bar/resource-usage-merge-types.ts +++ b/src/renderer/src/components/status-bar/resource-usage-merge-types.ts @@ -1,17 +1,11 @@ import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' import type { TerminalLayoutSnapshot, TerminalTab } from '../../../../shared/terminal-tab-types' import type { Worktree } from '../../../../shared/worktree/types' -import type { - AgentOwnershipEvidence, - PtyListedSession -} from '../../../../shared/pty-listed-session' +import type { AgentOwnershipEvidence } from '../../../../shared/pty-listed-session' /** `null` === "no local sample" (e.g. SSH PTY); UI renders as em-dash. */ export type Metric = number | null -/** One `pty.listSessions()` row. Aliased so ownership evidence cannot be dropped locally. */ -export type DaemonSession = PtyListedSession - export type UnifiedSessionRow = { sessionId: string paneKey: string | null diff --git a/src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx b/src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx index 5112e613672..bfe94f468d0 100644 --- a/src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx +++ b/src/renderer/src/components/status-bar/use-resource-session-inventory.test.tsx @@ -1,11 +1,11 @@ // @vitest-environment happy-dom import { act, cleanup, renderHook, waitFor } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' -import type { DaemonSession } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' import { notifyDaemonSessionInventoryInvalidated } from './daemon-session-inventory-invalidation' import { useResourceSessionInventory } from './use-resource-session-inventory' -function session(id: string): DaemonSession { +function session(id: string): PtyListedSession { return { id, cwd: '/workspace', title: id, agentOwnership: 'absent' as const } } @@ -18,7 +18,7 @@ function deferred(): { promise: Promise; resolve: (value: T) => void } { } describe('useResourceSessionInventory', () => { - const listSessions = vi.fn<() => Promise>() + const listSessions = vi.fn<() => Promise>() const unsubscribeSpawned = vi.fn() const unsubscribeExit = vi.fn() let spawnedCallback: ((data: { id: string }) => void) | null = null @@ -119,7 +119,7 @@ describe('useResourceSessionInventory', () => { const { result } = renderHook(() => useResourceSessionInventory(true)) await waitFor(() => expect(result.current.sessionInventory.count).toBe(1)) - const inFlight = deferred() + const inFlight = deferred() listSessions.mockReturnValueOnce(inFlight.promise) await act(async () => { spawnedCallback?.({ id: 'background-one' }) @@ -146,7 +146,7 @@ describe('useResourceSessionInventory', () => { const { result } = renderHook(() => useResourceSessionInventory(true)) await waitFor(() => expect(result.current.sessionInventory.count).toBe(1)) - const inFlight = deferred() + const inFlight = deferred() listSessions .mockReturnValueOnce(inFlight.promise) .mockResolvedValueOnce([session('one'), session('background-one'), session('background-two')]) @@ -186,7 +186,7 @@ describe('useResourceSessionInventory', () => { const { result, unmount } = renderHook(() => useResourceSessionInventory(true)) await waitFor(() => expect(result.current.sessionInventory.count).toBe(1)) - const inFlight = deferred() + const inFlight = deferred() listSessions.mockReturnValueOnce(inFlight.promise) await act(async () => { spawnedCallback?.({ id: 'background-one' }) @@ -209,7 +209,7 @@ describe('useResourceSessionInventory', () => { const { result } = renderHook(() => useResourceSessionInventory(true)) await waitFor(() => expect(result.current.sessionInventory.count).toBe(2)) - const stale = deferred() + const stale = deferred() listSessions.mockReturnValueOnce(stale.promise) let refresh!: Promise act(() => { @@ -235,8 +235,8 @@ describe('useResourceSessionInventory', () => { const { result } = renderHook(() => useResourceSessionInventory(true)) await waitFor(() => expect(result.current.sessionInventory.count).toBe(1)) - const older = deferred() - const newer = deferred() + const older = deferred() + const newer = deferred() listSessions.mockReturnValueOnce(older.promise).mockReturnValueOnce(newer.promise) let olderRefresh!: Promise let newerRefresh!: Promise diff --git a/src/renderer/src/components/status-bar/use-resource-usage-actions.ts b/src/renderer/src/components/status-bar/use-resource-usage-actions.ts index f94237a0bbe..b143b9f12c9 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-actions.ts +++ b/src/renderer/src/components/status-bar/use-resource-usage-actions.ts @@ -8,7 +8,8 @@ import { runWorktreeDelete } from '../sidebar/delete-worktree-flow' import { ORPHAN_WORKTREE_ID } from '../../../../shared/constants' import { parseWorkspaceKey } from '../../../../shared/workspace-scope' import { UNATTRIBUTED_REPO_ID } from './mergeSnapshotAndSessions' -import type { DaemonSession, UnifiedSessionRow } from './resource-usage-merge-types' +import type { UnifiedSessionRow } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' import type { ResourceSessionBindingInputs } from './resource-session-bindings' import { selectUnboundDaemonSessions } from './resource-session-bindings' import { navigateResourceSessionToTab } from './resource-session-navigation' @@ -47,7 +48,7 @@ export function useResourceUsageActions({ refreshSessions: () => Promise removeSession: (sessionId: string) => void removeSessions: (sessionIds: ReadonlySet) => void - sessions: readonly DaemonSession[] + sessions: readonly PtyListedSession[] resourceSessionBindings: ResourceSessionBindingInputs workspaceSessionReady: boolean killConfirm: UnifiedSessionRow | null diff --git a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx index f5e1bac9dbb..4e7930a6d99 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx +++ b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.test.tsx @@ -5,7 +5,7 @@ import type { BrowserWorkspace } from '../../../../shared/browser-workspace-type import type { MemorySnapshot, WorktreeMemory } from '../../../../shared/process-stats-types' import type { Worktree } from '../../../../shared/worktree/types' import type { ProjectGroup } from '../../../../shared/project-group-types' -import type { DaemonSession } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' import { useResourceUsageDerivedModel } from './use-resource-usage-derived-model' const local = { @@ -28,7 +28,7 @@ const group = { id: 'group', name: 'Local project', executionHostId: 'local' } a function derive( worktrees: Worktree[], - sessions: DaemonSession[] = [], + sessions: PtyListedSession[] = [], row = sampled, projectGroups = [group], browserTabsByWorktree: Record = {} diff --git a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts index cc56fd7f357..78145489618 100644 --- a/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts +++ b/src/renderer/src/components/status-bar/use-resource-usage-derived-model.ts @@ -4,7 +4,7 @@ import type { MemorySnapshot } from '../../../../shared/process-stats-types' import type { Worktree } from '../../../../shared/worktree/types' import { getRepoExecutionHostId, parseExecutionHostId } from '../../../../shared/execution-host' import { mergeSnapshotAndSessions } from './mergeSnapshotAndSessions' -import type { DaemonSession } from './resource-usage-merge-types' +import type { PtyListedSession } from '../../../../shared/pty-listed-session' import type { ResourceSessionBindingInputs } from './resource-session-bindings' import { countUnboundDaemonSessions } from './resource-session-bindings' import { @@ -38,7 +38,7 @@ export function useResourceUsageDerivedModel({ }: { open: boolean resourceSnapshot: MemorySnapshot | null - sessions: readonly DaemonSession[] + sessions: readonly PtyListedSession[] resourceSessionBindings: ResourceSessionBindingInputs runtimePaneTitlesByTabId: AppState['runtimePaneTitlesByTabId'] repos: AppState['repos'] diff --git a/src/renderer/src/components/tab-bar/BrowserTab.test.tsx b/src/renderer/src/components/tab-bar/BrowserTab.test.tsx index 125497284c8..1a589a61f3a 100644 --- a/src/renderer/src/components/tab-bar/BrowserTab.test.tsx +++ b/src/renderer/src/components/tab-bar/BrowserTab.test.tsx @@ -1,5 +1,5 @@ import { beforeEach, describe, expect, it, vi } from 'vitest' -import type { BrowserTab as BrowserTabState } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../shared/browser-workspace-types' const reactHookRuntime = vi.hoisted(() => ({ states: [] as unknown[], diff --git a/src/renderer/src/components/tab-bar/BrowserTab.tsx b/src/renderer/src/components/tab-bar/BrowserTab.tsx index 3b716c3bd51..7ebaa069545 100644 --- a/src/renderer/src/components/tab-bar/BrowserTab.tsx +++ b/src/renderer/src/components/tab-bar/BrowserTab.tsx @@ -20,7 +20,7 @@ import { import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip' import { ORCA_BROWSER_BLANK_URL } from '../../../../shared/constants' import { redactKagiSessionToken } from '../../../../shared/browser-url' -import type { BrowserTab as BrowserTabState } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace as BrowserTabState } from '../../../../shared/browser-workspace-types' import { CLOSE_ALL_CONTEXT_MENUS_EVENT } from './SortableTab' import { getLiveBrowserUrl } from '../browser-pane/describe-page/live-browser-url-registry' import type { TabDragItemData } from '../tab-group/useTabDragSplit' diff --git a/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx b/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx index ca755e18271..57385972e52 100644 --- a/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx +++ b/src/renderer/src/components/tab-bar/TerminalTabLeadingIcon.test.tsx @@ -2,10 +2,10 @@ import { renderToStaticMarkup } from 'react-dom/server' import { describe, expect, it } from 'vitest' import { TooltipProvider } from '@/components/ui/tooltip' import { TerminalTabLeadingIcon } from './TerminalTabLeadingIcon' -import type { TerminalTabActivityStatus } from './terminal-tab-activity-status' +import type { WorktreeStatus } from '@/lib/worktree-status' /** Render one activity status through the production leading-icon component. */ -function renderStatus(status: TerminalTabActivityStatus): string { +function renderStatus(status: WorktreeStatus): string { return renderToStaticMarkup( void | Promise onAllAction?: () => void | Promise diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts index f28a3579a66..0b28c7bd73e 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-hook-title-precedence.test.ts @@ -7,7 +7,7 @@ import { processResult, useAgentCompletionCoordinatorLifecycle } from './agent-completion-coordinator-test-harness' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' describe('agent completion coordinator', () => { useAgentCompletionCoordinatorLifecycle() @@ -188,7 +188,7 @@ describe('agent completion coordinator', () => { }) it('keeps duplicate done-only hooks inside replay guard suppressed after process inspection', async () => { - const inspection = createDeferred() + const inspection = createDeferred() const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', @@ -259,7 +259,7 @@ describe('agent completion coordinator', () => { }) it('ignores process inspections that resolve after completion state reset', async () => { - const inspection = createDeferred() + const inspection = createDeferred() const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', @@ -282,8 +282,8 @@ describe('agent completion coordinator', () => { }) it('starts a fresh pending-title inspection after stale inspection resolves', async () => { - const firstInspection = createDeferred() - const secondInspection = createDeferred() + const firstInspection = createDeferred() + const secondInspection = createDeferred() const inspectProcess = vi .fn() .mockReturnValueOnce(firstInspection.promise) diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts index 3489bc0d701..a817b61afc6 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-pending-title-inspection.test.ts @@ -6,7 +6,7 @@ import { processResult, useAgentCompletionCoordinatorLifecycle } from './agent-completion-coordinator-test-harness' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' function createRejectableDeferred(): { promise: Promise @@ -41,8 +41,8 @@ describe('agent completion coordinator', () => { }) it('does not validate a pending cwd title with an already in-flight inspection', async () => { - const staleInspection = createDeferred() - const freshInspection = createDeferred() + const staleInspection = createDeferred() + const freshInspection = createDeferred() const inspectProcess = vi .fn() .mockReturnValueOnce(staleInspection.promise) @@ -76,8 +76,8 @@ describe('agent completion coordinator', () => { }) it('does not validate a replaced pending title with an older pending-title inspection', async () => { - const titleAInspection = createDeferred() - const titleBInspection = createDeferred() + const titleAInspection = createDeferred() + const titleBInspection = createDeferred() const inspectProcess = vi .fn() .mockReturnValueOnce(titleAInspection.promise) @@ -111,8 +111,8 @@ describe('agent completion coordinator', () => { }) it('does not drop a replaced pending title from an older non-agent inspection', async () => { - const titleAInspection = createDeferred() - const titleBInspection = createDeferred() + const titleAInspection = createDeferred() + const titleBInspection = createDeferred() const inspectProcess = vi .fn() .mockReturnValueOnce(titleAInspection.promise) @@ -146,7 +146,7 @@ describe('agent completion coordinator', () => { }) it('does not dispatch a pending cwd title when process inspection fails', async () => { - const inspection = createRejectableDeferred() + const inspection = createRejectableDeferred() const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', @@ -166,7 +166,7 @@ describe('agent completion coordinator', () => { }) it('prefers a later explicit completion title over a pending cwd title', async () => { - const inspection = createDeferred() + const inspection = createDeferred() const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', @@ -206,7 +206,7 @@ describe('agent completion coordinator', () => { }) it('keeps a generic title completion pending long enough for the first remote inspection', async () => { - const inspection = createDeferred() + const inspection = createDeferred() const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts index e9d60cabace..9ac4013b00b 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-process-cadence.test.ts @@ -6,7 +6,7 @@ import { processResult, useAgentCompletionCoordinatorLifecycle } from './agent-completion-coordinator-test-harness' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' describe('agent completion coordinator', () => { useAgentCompletionCoordinatorLifecycle() @@ -291,7 +291,7 @@ describe('agent completion coordinator', () => { }) it('resets exit confirmation across an unavailable inspection', async () => { - let result: RuntimeTerminalProcessInspection = processResult('codex') + let result: TerminalProcessInspection = processResult('codex') const dispatchCompletion = vi.fn() const coordinator = createAgentCompletionCoordinator({ paneKey: 'tab-1:leaf-1', diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts index 9d83b1d6a93..5dbc837fe9d 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-queued-inspection-disposal.test.ts @@ -6,15 +6,13 @@ import { processResult, useAgentCompletionCoordinatorLifecycle } from './agent-completion-coordinator-test-harness' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' describe('agent completion coordinator queued inspections', () => { useAgentCompletionCoordinatorLifecycle() it('drops inspections queued by a disposed coordinator before starting live work', async () => { - const blockers = Array.from({ length: 4 }, () => - createDeferred() - ) + const blockers = Array.from({ length: 4 }, () => createDeferred()) const blockerInspectors = blockers.map((inspection) => vi.fn(() => inspection.promise)) const blockerCoordinators = blockerInspectors.map((inspectProcess, index) => createAgentCompletionCoordinator({ diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-test-harness.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-test-harness.ts index a9affa8edbf..0b4568512db 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-test-harness.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-test-harness.ts @@ -1,7 +1,7 @@ import { afterEach, beforeEach, vi } from 'vitest' import { resetAgentCompletionCoordinatorIdentitiesForTest } from './agent-completion-coordinator' import { resetAgentProcessInspectionQueueForTests } from './agent-process-inspection-queue' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' export const HOOK_DONE_QUIET_MS = 1_500 @@ -14,7 +14,7 @@ export async function flushAsyncTicks(count = 4): Promise { export function processResult( foregroundProcess: string | null, hasChildProcesses = foregroundProcess !== null -): RuntimeTerminalProcessInspection { +): TerminalProcessInspection { return { foregroundProcess, hasChildProcesses } } diff --git a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts index 7bf7e90f7f9..44be013f7c0 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-coordinator-types.ts @@ -1,7 +1,7 @@ import type { ParsedAgentStatusPayload } from '../../../../shared/agent-status-types' import type { GlobalSettings } from '../../../../shared/global-settings-types' import type { RecognizedAgentProcess } from '../../../../shared/agent-process-recognition' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' export type AgentCompletionStatusSnapshot = ParsedAgentStatusPayload & { stateStartedAt?: number @@ -33,7 +33,7 @@ export type AgentCompletionCoordinatorOptions = { settings: Pick | null | undefined, ptyId: string, options?: { expectedIncarnationId?: string; steadyState?: boolean } - ) => Promise + ) => Promise dispatchCompletion: (title: string, meta?: AgentCompletionDispatchMeta) => void dispatchAttention?: (title: string, meta: AgentAttentionDispatchMeta) => void dispatchHookLifecycle?: (payload: AgentCompletionStatusSnapshot) => void diff --git a/src/renderer/src/components/terminal-pane/agent-completion-inspection-result.ts b/src/renderer/src/components/terminal-pane/agent-completion-inspection-result.ts index f27960361bb..0aefd1cc562 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-inspection-result.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-inspection-result.ts @@ -2,9 +2,11 @@ import type { RecognizedAgentProcess } from '../../../../shared/agent-process-re import { recognizeAgentProcess } from '../../../../shared/agent-process-recognition' import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id' import { admitRemoteForegroundEvidence } from '../../../../shared/remote-foreground-evidence-admission' -import { isClientOnlyUnverifiableInspection } from '../../../../shared/terminal-process-inspection' +import { + isClientOnlyUnverifiableInspection, + type TerminalProcessInspection +} from '../../../../shared/terminal-process-inspection' import { getRemoteRuntimeTerminalHandle } from '@/runtime/runtime-terminal-stream' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' import type { AgentCompletionCoordinatorOptions } from './agent-completion-coordinator-types' import type { AgentCompletionIdentityScope, @@ -26,7 +28,7 @@ type CompletionDispatch = ( ) => boolean export function handleAgentCompletionInspectionResult(args: { - result: RuntimeTerminalProcessInspection + result: TerminalProcessInspection requestStartedAtMonotonic: number options: AgentCompletionCoordinatorOptions state: ProcessMonitorState diff --git a/src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts index 238f073fd38..2ccbb1e4356 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-no-evidence-cadence.test.ts @@ -16,7 +16,7 @@ import { resetAgentProcessInspectionQueueForTests } from './agent-process-inspec import { isAgentProcessInspectionCostly } from './agent-process-inspection-cost' import { toRemoteRuntimePtyId } from '../../../../shared/remote-runtime-pty-id' import { toAppSshPtyId } from '../../../../shared/ssh-pty-id' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' import type { AgentCompletionCoordinatorOptions } from './agent-completion-coordinator-types' const MAC_UA = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7)' @@ -25,7 +25,7 @@ const WINDOWS_UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64)' function processResult( foregroundProcess: string | null, hasChildProcesses = foregroundProcess !== null -): RuntimeTerminalProcessInspection { +): TerminalProcessInspection { return { foregroundProcess, hasChildProcesses } } diff --git a/src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts b/src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts index 82714edce6e..707bf886163 100644 --- a/src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts +++ b/src/renderer/src/components/terminal-pane/agent-completion-stale-evidence-backoff.test.ts @@ -14,14 +14,14 @@ import { handleAgentCompletionInspectionResult } from './agent-completion-inspec import type { RemoteInspectionState } from './agent-completion-inspection-result' import type { ProcessMonitorState } from './agent-completion-process-types' import type { AgentCompletionCoordinatorOptions } from './agent-completion-coordinator-types' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' import { toAppSshPtyId } from '../../../../shared/ssh-pty-id' import { REMOTE_FOREGROUND_EVIDENCE_MAX_AGE_MS } from '../../../../shared/remote-foreground-evidence-admission' const SSH_PTY_ID = toAppSshPtyId('target-1', 'pty-1') const INCARNATION = 'inc-1' -function liveRecord(capturedAgeMs: number): RuntimeTerminalProcessInspection { +function liveRecord(capturedAgeMs: number): TerminalProcessInspection { return { foregroundProcess: 'claude', hasChildProcesses: true, @@ -46,7 +46,7 @@ function liveRecord(capturedAgeMs: number): RuntimeTerminalProcessInspection { } /** What both relay call sites publish when the capture misses its budget. */ -function unreadableTableRecord(): RuntimeTerminalProcessInspection { +function unreadableTableRecord(): TerminalProcessInspection { return { foregroundProcess: 'claude', hasChildProcesses: true, @@ -62,7 +62,7 @@ function unreadableTableRecord(): RuntimeTerminalProcessInspection { } } -function inspect(result: RuntimeTerminalProcessInspection, roundTripMs = 20): ProcessMonitorState { +function inspect(result: TerminalProcessInspection, roundTripMs = 20): ProcessMonitorState { const state: ProcessMonitorState = { disposed: false, inspectionInFlight: false, diff --git a/src/renderer/src/components/terminal-pane/deferred-split-pane-handoff.ts b/src/renderer/src/components/terminal-pane/deferred-split-pane-handoff.ts index 6dfa1f30866..51697da0dc5 100644 --- a/src/renderer/src/components/terminal-pane/deferred-split-pane-handoff.ts +++ b/src/renderer/src/components/terminal-pane/deferred-split-pane-handoff.ts @@ -4,10 +4,7 @@ import { PTY_PRECONNECT_INPUT_MAX_CODE_UNITS, PTY_PRECONNECT_INPUT_MAX_ENTRIES } from './pty-preconnect-input-buffer' -import type { PtyPreconnectInputEntry, PtyPreconnectInputKind } from './pty-preconnect-input-buffer' - -export type DeferredSplitPaneInputKind = PtyPreconnectInputKind -export type DeferredSplitPaneInput = PtyPreconnectInputEntry +import type { PtyPreconnectInputEntry } from './pty-preconnect-input-buffer' declare const deferredSplitPaneHandoffHandleBrand: unique symbol @@ -18,7 +15,7 @@ export type DeferredSplitPaneHandoffHandle = { export type ClaimedDeferredSplitPaneHandoff = { handle: DeferredSplitPaneHandoffHandle cwdPromise: Promise - preconnectInput: DeferredSplitPaneInput[] + preconnectInput: PtyPreconnectInputEntry[] } export const DEFERRED_SPLIT_PANE_HANDOFF_TTL_MS = 15_000 @@ -30,7 +27,7 @@ type DeferredSplitPaneHandoffRecord = { expiryTimer: ReturnType inputCodeUnits: number owner: DeferredSplitPaneHandoffHandle - preconnectInput: DeferredSplitPaneInput[] + preconnectInput: PtyPreconnectInputEntry[] } const handoffs = new Map() @@ -125,7 +122,7 @@ export function claimDeferredSplitPaneHandoff( export function appendDeferredSplitPaneInput( handle: DeferredSplitPaneHandoffHandle, - input: DeferredSplitPaneInput + input: PtyPreconnectInputEntry ): void { const owned = getOwnedRecord(handle) if ( diff --git a/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.ts b/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.ts index d831226267d..3fb3e522459 100644 --- a/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.ts +++ b/src/renderer/src/components/terminal-pane/pane-foreground-agent-tracker.ts @@ -5,7 +5,7 @@ import { import { isShellProcess } from '../../../../shared/shell-process-detection' import type { TuiAgent } from '../../../../shared/tui-agent' import type { PaneForegroundAgentEntry } from '@/store/slices/pane-foreground-agent' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../../shared/terminal-process-inspection' import { createPaneForegroundProcessReader } from './pane-foreground-process-reader' // Why: settle after exec, then place the final generic retry beyond sequential @@ -23,12 +23,12 @@ type PaneForegroundAgentTrackerDeps = { readForegroundProcess: ( ptyId: string, options?: { expectedIncarnationId?: string } - ) => Promise + ) => Promise /** Fresh, provider-owned evidence used only when input routing may change. */ confirmForegroundProcess?: ( ptyId: string, options?: { expectedIncarnationId?: string } - ) => Promise + ) => Promise /** Remote authorities must provide fenced evidence; local panes retain the string path. */ isRemotePtyId?: (ptyId: string) => boolean getExpectedIncarnationId?: () => string | null diff --git a/src/renderer/src/components/terminal-pane/pane-foreground-process-reader.ts b/src/renderer/src/components/terminal-pane/pane-foreground-process-reader.ts index fac54efc967..5a82ebb549c 100644 --- a/src/renderer/src/components/terminal-pane/pane-foreground-process-reader.ts +++ b/src/renderer/src/components/terminal-pane/pane-foreground-process-reader.ts @@ -1,13 +1,15 @@ -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' import { getRemoteRuntimeTerminalHandle } from '@/runtime/runtime-terminal-stream' import { parseAppSshPtyId } from '../../../../shared/ssh-pty-id' import { admitRemoteForegroundEvidence } from '../../../../shared/remote-foreground-evidence-admission' -import { isClientOnlyUnverifiableInspection } from '../../../../shared/terminal-process-inspection' +import { + isClientOnlyUnverifiableInspection, + type TerminalProcessInspection +} from '../../../../shared/terminal-process-inspection' type ForegroundReader = ( ptyId: string, options?: { expectedIncarnationId?: string } -) => Promise +) => Promise export function createPaneForegroundProcessReader(deps: { readForegroundProcess: ForegroundReader diff --git a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts index 768b0dfb3d1..d0049a64d42 100644 --- a/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts +++ b/src/renderer/src/components/terminal-pane/regular-terminal-focus-ownership.ts @@ -6,7 +6,6 @@ import { } from './terminal-ime-input-context-refresh' export type TerminalInputFocusSync = (focused: boolean) => void -export type RefocusScheduler = TerminalImeInputContextRefocusScheduler export const REGULAR_TERMINAL_INPUT_FOCUSED_ATTRIBUTE = 'data-regular-terminal-input-focused' export function isXtermHelperTextarea(target: EventTarget | null): target is HTMLElement { @@ -80,7 +79,7 @@ export function resyncTerminalFocusForWindowFocus(args: { /** Override the macOS check (tests). Defaults to the navigator user agent. */ isMac?: boolean /** Override the refocus scheduler (tests). Defaults to requestAnimationFrame. */ - scheduleRefocus?: RefocusScheduler + scheduleRefocus?: TerminalImeInputContextRefocusScheduler }): boolean { const ownedActive = getPaneOwnedActiveHelperTextarea(args.container, args.activeElement) let helper = ownedActive diff --git a/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts b/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts index 833d05ffa4d..458b93fd39c 100644 --- a/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts +++ b/src/renderer/src/components/terminal-pane/terminal-link-action-request.ts @@ -2,15 +2,9 @@ import type { TerminalLinkPointerGesture } from './terminal-link-pointer-gesture import { isTerminalLinkActionActivation } from './terminal-link-activation' import { closeLinkActionRequest, - type LinkAction, - type LinkActionKind, type LinkActionRequest } from '@/components/link-actions/link-action-request' -export type TerminalLinkActionKind = LinkActionKind - -export type TerminalLinkAction = LinkAction - export type TerminalLinkActionRequest = LinkActionRequest & { paneId: number } export type TerminalLinkActionRequester = (request: TerminalLinkActionRequest) => void diff --git a/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts b/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts index b041cb65f0d..5e47033c8dc 100644 --- a/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-osc-link-routing.ts @@ -6,11 +6,9 @@ import { isTerminalLinkActionActivation, isTerminalLinkDirectActivation } from './terminal-link-activation' -import { - handleTerminalHttpLink, - type TerminalHttpLinkActionDestinations, - type TerminalLinkRoutingPreferenceRequester -} from './terminal-url-link-hit-testing' +import { handleTerminalHttpLink } from './terminal-url-link-hit-testing' +import type { HttpLinkRoutingPreferenceRequester } from '@/lib/http-link-destinations' +import type { HttpLinkActionDestinations } from '@/lib/http-link-destinations' import type { HttpLinkSourceOwner } from '@/lib/http-link-routing' import type { TerminalLinkActionContext } from './terminal-link-action-request' import { handleTerminalFileLink } from './terminal-file-link-actions' @@ -52,9 +50,9 @@ export function handleOscLink( > > & { sourceOwner?: HttpLinkSourceOwner - requestOpenLinksInAppPreference?: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference?: HttpLinkRoutingPreferenceRequester linkActionContext?: TerminalLinkActionContext | null - actionDestinations?: TerminalHttpLinkActionDestinations + actionDestinations?: HttpLinkActionDestinations } ): boolean { if (!isDesktopOscLinkActivation(event)) { diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts b/src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts index 9a782f98dcf..86120793aaf 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-lifecycle-types.ts @@ -18,7 +18,7 @@ import type { PtyTransport } from './pty-transport' import type { PtyTransportRecoveryState } from './pty-transport-types' import type { ReplayingPanesRef } from './replay-guard' import type { TerminalLinkActionRequester } from './terminal-link-action-request' -import type { TerminalLinkRoutingPreferenceRequester } from './terminal-url-link-hit-testing' +import type { HttpLinkRoutingPreferenceRequester } from '@/lib/http-link-destinations' import type { SessionRestoredBannerReason } from './session-restored-banner-pane-state' export type TerminalPaneStartup = Exclude @@ -48,7 +48,7 @@ export type UseTerminalPaneLifecycleDeps = { systemPrefersDark: boolean settings: GlobalSettings | null | undefined settingsRef: React.RefObject - requestOpenLinksInAppPreference: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference: HttpLinkRoutingPreferenceRequester requestTerminalLinkAction: TerminalLinkActionRequester /** Resolved Option-as-Alt: `'auto'` already mapped via the layout probe. */ effectiveMacOptionAsAlt: EffectiveMacOptionAsAlt diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-mount-context.ts b/src/renderer/src/components/terminal-pane/terminal-pane-mount-context.ts index 2db53efd96d..45502d8e530 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-mount-context.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-mount-context.ts @@ -1,9 +1,7 @@ import type { PaneManager } from '@/lib/pane-manager/pane-manager' import type { DeferredSplitPaneHandoffHandle } from './deferred-split-pane-handoff' -import type { - TerminalHttpLinkActionDestinations, - TerminalLinkRoutingPreferenceRequester -} from './terminal-url-link-hit-testing' +import type { HttpLinkRoutingPreferenceRequester } from '@/lib/http-link-destinations' +import type { HttpLinkActionDestinations } from '@/lib/http-link-destinations' import type { PtyConnectionDeps } from './pty-connection-types' import type { LinkHandlerDeps } from './terminal-link-handlers' import type { TerminalPaneLifecycleRefs } from './use-terminal-pane-lifecycle-refs' @@ -31,10 +29,10 @@ export type TerminalPaneMountContext = { getHttpLinkSourceOwnerForPane: ( paneId: number ) => ReturnType - getHttpLinkActionDestinations: (paneId: number) => TerminalHttpLinkActionDestinations + getHttpLinkActionDestinations: (paneId: number) => HttpLinkActionDestinations getLinkActionContext: (paneId: number) => TerminalLinkActionContext | null canOpenOwnedBrowserForPane: (paneId: number) => boolean - requestOpenLinksInAppPreference: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference: HttpLinkRoutingPreferenceRequester onShowSessionRestoredBanner: (paneId: number, reason?: SessionRestoredBannerReason) => void queueResizeAll: (focusActive: boolean) => void syncPaneCount: () => void @@ -51,8 +49,6 @@ export type TerminalPaneManagerOptionsContext = TerminalPaneMountContext & { osc7UncHost: string | null } -export type PaneCreatedHandlerContext = TerminalPaneManagerOptionsContext - export type PaneClosedHandlerContext = TerminalPaneMountContext & { paneId: number closedPane?: { leafId: string; reason?: 'close' | 'detach' | 'retire' } diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts b/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts index 56c9825212d..4a493ab7b68 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-mount-preparation.ts @@ -15,7 +15,7 @@ import { } from './terminal-link-open-hints' import type { LinkHandlerDeps } from './terminal-link-handlers' import type { TerminalLinkActionContext } from './terminal-link-action-request' -import type { TerminalHttpLinkActionDestinations } from './terminal-url-link-hit-testing' +import type { HttpLinkActionDestinations } from '@/lib/http-link-destinations' import type { PtyConnectionDeps } from './pty-connection-types' import type { UseTerminalPaneLifecycleDeps } from './terminal-pane-lifecycle-types' import type { TerminalPaneLifecycleRefs } from './use-terminal-pane-lifecycle-refs' @@ -54,7 +54,7 @@ export type TerminalPaneMountPreparation = { paneId: number ) => ReturnType canOpenOwnedBrowserForPane: (paneId: number) => boolean - getHttpLinkActionDestinations: (paneId: number) => TerminalHttpLinkActionDestinations + getHttpLinkActionDestinations: (paneId: number) => HttpLinkActionDestinations getLinkActionContext: (paneId: number) => TerminalLinkActionContext | null linkDeps: LinkHandlerDeps queueResizeAll: (focusActive: boolean) => void @@ -123,7 +123,7 @@ export function prepareTerminalPaneMount( ) ) } - const getHttpLinkActionDestinations = (paneId: number): TerminalHttpLinkActionDestinations => + const getHttpLinkActionDestinations = (paneId: number): HttpLinkActionDestinations => httpLinkActionDestinationsFor( deps.settingsRef.current, getHttpLinkSourceOwnerForPane(paneId), diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-pane-created.ts b/src/renderer/src/components/terminal-pane/terminal-pane-pane-created.ts index f422f99fb53..5e7c0d70d44 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-pane-created.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-pane-created.ts @@ -31,11 +31,9 @@ import { installTerminalPaneInputHandling } from './terminal-pane-pane-input' import { installTerminalPaneLinkHandling } from './terminal-pane-pane-links' import { scheduleRuntimeGraphSync } from '@/runtime/sync-runtime-graph' -export type PaneCreatedSetupContext = TerminalPaneManagerOptionsContext - /** Creates the PaneManager `onPaneCreated` callback. */ export function createTerminalPaneCreatedHandler( - context: PaneCreatedSetupContext + context: TerminalPaneManagerOptionsContext ): (pane: ManagedPane, spawnHints?: PaneSpawnHints) => void { // Split spawn hints let the renderer pane appear before a slow inherited-cwd lookup finishes. return (pane, spawnHints) => { diff --git a/src/renderer/src/components/terminal-pane/terminal-pane-pane-links.ts b/src/renderer/src/components/terminal-pane/terminal-pane-pane-links.ts index 3db5e48fdcf..7a00ccfb650 100644 --- a/src/renderer/src/components/terminal-pane/terminal-pane-pane-links.ts +++ b/src/renderer/src/components/terminal-pane/terminal-pane-pane-links.ts @@ -5,9 +5,9 @@ import type { TerminalPaneLifecycleRefs } from './use-terminal-pane-lifecycle-re import type { LinkHandlerDeps } from './terminal-link-handlers' import type { TerminalLinkActionContext } from './terminal-link-action-request' import type { - TerminalHttpLinkActionDestinations, - TerminalLinkRoutingPreferenceRequester -} from './terminal-url-link-hit-testing' + HttpLinkActionDestinations, + HttpLinkRoutingPreferenceRequester +} from '@/lib/http-link-destinations' import { createFilePathLinkProvider, installFilePathLinkClickFallback @@ -47,9 +47,9 @@ type PaneLinkContext = { > linkDeps: LinkHandlerDeps fileOpenLinkHint: string - requestOpenLinksInAppPreference: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference: HttpLinkRoutingPreferenceRequester getHttpLinkSourceOwnerForPane: (paneId: number) => HttpLinkSourceOwner - getHttpLinkActionDestinations: (paneId: number) => TerminalHttpLinkActionDestinations + getHttpLinkActionDestinations: (paneId: number) => HttpLinkActionDestinations getLinkActionContext: (paneId: number) => TerminalLinkActionContext | null getPaneLinkCwd: (paneId: number) => string getUrlOpenLinkHint: (paneId: number) => string diff --git a/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts b/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts index 0139df29e16..6c9656da0da 100644 --- a/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts +++ b/src/renderer/src/components/terminal-pane/terminal-url-link-hit-testing.ts @@ -32,32 +32,26 @@ type UrlLinkHitTestDeps = { worktreeId: string sourceOwner?: HttpLinkSourceOwner modifierHeld?: boolean - requestOpenLinksInAppPreference?: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference?: HttpLinkRoutingPreferenceRequester linkActionContext?: TerminalLinkActionContext | null - actionDestinations?: TerminalHttpLinkActionDestinations + actionDestinations?: HttpLinkActionDestinations actionDestination?: string - forceDestination?: TerminalHttpLinkDestination + forceDestination?: HttpLinkDestination } type UrlLinkClickFallbackDeps = { worktreeId: string /** Resolved per click: the pane's PTY (and its runtime binding) may not exist at install time. */ getSourceOwner?: () => HttpLinkSourceOwner - requestOpenLinksInAppPreference?: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference?: HttpLinkRoutingPreferenceRequester getLinkActionContext?: () => TerminalLinkActionContext | null - getActionDestinations?: () => TerminalHttpLinkActionDestinations + getActionDestinations?: () => HttpLinkActionDestinations } export type HttpLinkClickFallbackBinding = IDisposable & { ptyMouseSuppression: TerminalLinkPtyMouseSuppression } -export type TerminalHttpLinkDestination = HttpLinkDestination - -export type TerminalHttpLinkActionDestinations = HttpLinkActionDestinations - -export type TerminalLinkRoutingPreferenceRequester = HttpLinkRoutingPreferenceRequester - function isDesktopHttpLinkFallbackActivation(event: MouseEvent): boolean { if (event.defaultPrevented || event.button !== 0) { return false diff --git a/src/renderer/src/components/terminal-pane/terminal-web-link-click.ts b/src/renderer/src/components/terminal-pane/terminal-web-link-click.ts index 441ff06ce86..881ae51c434 100644 --- a/src/renderer/src/components/terminal-pane/terminal-web-link-click.ts +++ b/src/renderer/src/components/terminal-pane/terminal-web-link-click.ts @@ -4,10 +4,10 @@ import { isTerminalOwnedLinkGesture } from './terminal-link-activation' import { handleOscLink } from './terminal-osc-link-routing' import { findHttpLinkAtTerminalMouseEvent, - handleTerminalHttpLink, - type TerminalHttpLinkActionDestinations, - type TerminalLinkRoutingPreferenceRequester + handleTerminalHttpLink } from './terminal-url-link-hit-testing' +import type { HttpLinkRoutingPreferenceRequester } from '@/lib/http-link-destinations' +import type { HttpLinkActionDestinations } from '@/lib/http-link-destinations' import type { HttpLinkSourceOwner } from '@/lib/http-link-routing' import type { TerminalLinkActionContext } from './terminal-link-action-request' @@ -17,9 +17,9 @@ type TerminalWebLinkClickDeps = Pick< > & { terminal: Terminal | null sourceOwner?: HttpLinkSourceOwner - requestOpenLinksInAppPreference?: TerminalLinkRoutingPreferenceRequester + requestOpenLinksInAppPreference?: HttpLinkRoutingPreferenceRequester linkActionContext?: TerminalLinkActionContext | null - actionDestinations?: TerminalHttpLinkActionDestinations + actionDestinations?: HttpLinkActionDestinations } export function handleTerminalWebLinkClick( diff --git a/src/renderer/src/components/terminal/tab-type-cycle.ts b/src/renderer/src/components/terminal/tab-type-cycle.ts index 3de13076c15..0e5c6b5e29c 100644 --- a/src/renderer/src/components/terminal/tab-type-cycle.ts +++ b/src/renderer/src/components/terminal/tab-type-cycle.ts @@ -1,16 +1,14 @@ import type { WorkspaceVisibleTabType } from '../../../../shared/tab-types' -export type TabCycleType = WorkspaceVisibleTabType - export type TypeCyclableTab = { - type: TabCycleType + type: WorkspaceVisibleTabType id: string tabId?: string } type GetNextTabWithinActiveTypeParams = { tabs: TypeCyclableTab[] - activeTabType: TabCycleType + activeTabType: WorkspaceVisibleTabType activeTabId: string | null activeFileId: string | null activeBrowserTabId: string | null @@ -26,7 +24,7 @@ type GetNextTabWithinActiveTypeParams = { * pass it, or a structured tab resolves to a live background terminal (see the branch below). */ export function getActiveEntityIdForTabType( - activeTabType: TabCycleType, + activeTabType: WorkspaceVisibleTabType, activeTabId: string | null, activeFileId: string | null, activeBrowserTabId: string | null, @@ -51,7 +49,7 @@ export function getActiveEntityIdForTabType( type GetNextTabAcrossAllTypesParams = { tabs: TypeCyclableTab[] - activeTabType: TabCycleType + activeTabType: WorkspaceVisibleTabType activeTabId: string | null activeFileId: string | null activeBrowserTabId: string | null diff --git a/src/renderer/src/components/use-task-page-linear-collection-effects.ts b/src/renderer/src/components/use-task-page-linear-collection-effects.ts index a0c7e03ccb2..07f81778d64 100644 --- a/src/renderer/src/components/use-task-page-linear-collection-effects.ts +++ b/src/renderer/src/components/use-task-page-linear-collection-effects.ts @@ -3,7 +3,7 @@ import { useEffect } from 'react' import { TASK_SEARCH_DEBOUNCE_MS, LINEAR_ITEM_LIMIT } from './task-page-source-context' import { clampLinearIssueListLimit } from '../../../shared/linear/issue-read-limits' import { useTaskPageLinearCustomViewEffects } from './use-task-page-linear-custom-view-effects' -export type TaskPageLinearCollectionEffectsPreludeModel = TaskPageLinearInOrcaEffectsModel + export function useTaskPageLinearCollectionEffectsPrelude(model: TaskPageLinearInOrcaEffectsModel) { const { setTaskResumeState, diff --git a/src/renderer/src/components/use-task-page-linear-custom-view-effects.ts b/src/renderer/src/components/use-task-page-linear-custom-view-effects.ts index 530367593d1..8aadf5428c3 100644 --- a/src/renderer/src/components/use-task-page-linear-custom-view-effects.ts +++ b/src/renderer/src/components/use-task-page-linear-custom-view-effects.ts @@ -1,4 +1,4 @@ -import type { TaskPageLinearCollectionEffectsPreludeModel } from './use-task-page-linear-collection-effects' +import type { TaskPageLinearInOrcaEffectsModel } from './use-task-page-linear-in-orca-effects' import { useEffect } from 'react' import { LINEAR_CUSTOM_VIEW_MODELS, @@ -12,9 +12,7 @@ import type { LinearProjectSummary } from '../../../shared/linear/project-types' import { clampLinearIssueListLimit } from '../../../shared/linear/issue-read-limits' -export function useTaskPageLinearCustomViewEffects( - model: TaskPageLinearCollectionEffectsPreludeModel -) { +export function useTaskPageLinearCustomViewEffects(model: TaskPageLinearInOrcaEffectsModel) { const { getCachedLinearCustomViews, listLinearCustomViews, diff --git a/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx b/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx index 524707485f6..aecede41765 100644 --- a/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx +++ b/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.test.tsx @@ -1,14 +1,14 @@ // @vitest-environment happy-dom +import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' import { useRef } from 'react' import { cleanup, render, screen } from '@testing-library/react' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' import { WorkspaceEmojiSuggestionPopover } from './WorkspaceEmojiSuggestionPopover' -import type { WorkspaceEmojiSuggestion } from '@/lib/workspace-emoji-shortcodes' Element.prototype.scrollIntoView ??= () => {} -const SUGGESTIONS: WorkspaceEmojiSuggestion[] = [ +const SUGGESTIONS: StandardEmojiShortcodeEntry[] = [ { shortcode: 'smile', emoji: '😄' }, { shortcode: 'smiley', emoji: '😃' }, { shortcode: 'smirk', emoji: '😏' } diff --git a/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.tsx b/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.tsx index b54c6fcb205..3c6462da79a 100644 --- a/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.tsx +++ b/src/renderer/src/components/workspace-emoji/WorkspaceEmojiSuggestionPopover.tsx @@ -1,8 +1,8 @@ +import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' import { useEffect, useRef, type ComponentProps, type RefObject } from 'react' import { Command, CommandGroup, CommandItem, CommandList } from '@/components/ui/command' import { Popover, PopoverAnchor, PopoverContent } from '@/components/ui/popover' import { cn } from '@/lib/utils' -import type { WorkspaceEmojiSuggestion } from '@/lib/workspace-emoji-shortcodes' type WorkspaceEmojiSuggestionPopoverProps = { anchorRef: RefObject @@ -11,11 +11,11 @@ type WorkspaceEmojiSuggestionPopoverProps = { heading: string onCommandValueChange: (value: string) => void onOpenChange: (open: boolean) => void - onSelect: (suggestion: WorkspaceEmojiSuggestion) => void + onSelect: (suggestion: StandardEmojiShortcodeEntry) => void open: boolean portalContainer?: HTMLElement | null side?: ComponentProps['side'] - suggestions: readonly WorkspaceEmojiSuggestion[] + suggestions: readonly StandardEmojiShortcodeEntry[] } export function WorkspaceEmojiSuggestionPopover({ diff --git a/src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.ts b/src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.ts index 567e3ed8bfb..87eb7ef1421 100644 --- a/src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.ts +++ b/src/renderer/src/components/workspace-emoji/useWorkspaceEmojiShortcodeInput.ts @@ -1,3 +1,4 @@ +import type { StandardEmojiShortcodeEntry } from '../../../../shared/emoji-shortcode-catalog' import { useCallback, useEffect, useMemo, useRef, useState, type RefObject } from 'react' import { isImeCompositionKeyDown } from '@/lib/ime-composition-keyboard-event' import { @@ -5,8 +6,7 @@ import { getActiveWorkspaceEmojiShortcode, replaceCompletedWorkspaceEmojiShortcode, searchWorkspaceEmojiShortcodes, - type WorkspaceEmojiReplacement, - type WorkspaceEmojiSuggestion + type WorkspaceEmojiReplacement } from '@/lib/workspace-emoji-shortcodes' type WorkspaceEmojiShortcodeInputOptions = { @@ -92,7 +92,7 @@ export function useWorkspaceEmojiShortcodeInput({ const close = useCallback(() => setCursor(null), []) const selectSuggestion = useCallback( - (suggestion: WorkspaceEmojiSuggestion) => { + (suggestion: StandardEmojiShortcodeEntry) => { if (!activeShortcode) { return } diff --git a/src/renderer/src/hooks/agent-hook-completion-notifications.ts b/src/renderer/src/hooks/agent-hook-completion-notifications.ts index dbf8ca45f6d..88d3590a762 100644 --- a/src/renderer/src/hooks/agent-hook-completion-notifications.ts +++ b/src/renderer/src/hooks/agent-hook-completion-notifications.ts @@ -5,7 +5,7 @@ import type { AgentCompletionCoordinator, AgentCompletionStatusSnapshot } from '@/components/terminal-pane/agent-completion-coordinator-types' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../shared/terminal-process-inspection' import { dispatchTerminalNotification } from '@/components/terminal-pane/use-notification-dispatch' import { collectLeafIdsInOrder } from '@/components/terminal-pane/layout-serialization' import { createCodexAutoApprovalHookCompletionSuppressor } from '@/components/terminal-pane/codex-auto-approval-notification-suppression' @@ -238,7 +238,7 @@ function createCoordinator(paneKey: string, worktreeId: string): AgentCompletion statusLane: 'hook', getPtyId: () => getPtyIdForPaneKey(paneKey), getSettings: () => useAppStore.getState().settings, - inspectProcess: async (): Promise => ({ + inspectProcess: async (): Promise => ({ foregroundProcess: null, hasChildProcesses: false }), diff --git a/src/renderer/src/hooks/useEditorExternalWatch.ts b/src/renderer/src/hooks/useEditorExternalWatch.ts index 20f02e719d5..c9b7db652f8 100644 --- a/src/renderer/src/hooks/useEditorExternalWatch.ts +++ b/src/renderer/src/hooks/useEditorExternalWatch.ts @@ -7,7 +7,7 @@ import { getEditorExternalWatchTargetKey, selectEditorExternalWatchTargets, type EditorExternalWatchTarget, - type EditorExternalWatchTargetState as EditorExternalWatchTargetStateShape + type EditorExternalWatchTargetState } from './editor-external-watch-targets' import { buildEditorExternalWatchEventHandler, @@ -15,7 +15,7 @@ import { } from './editor-external-watch-event-reconciliation' import { verifyLatchedEditorMoveDestinations } from './editor-external-watch-disk-verification' -export type EditorExternalWatchTargetState = EditorExternalWatchTargetStateShape +export type { EditorExternalWatchTargetState } function warnExternalWatchFailure(target: EditorExternalWatchTarget, err: unknown): void { console.warn('[filesystem-watch] failed to watch worktree', { diff --git a/src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts b/src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts index ec150ac1099..4055714d033 100644 --- a/src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts +++ b/src/renderer/src/hooks/useIpcEvents-runtime-environment-selectors.test.ts @@ -72,16 +72,13 @@ describe('getRuntimeProjectRefreshEnvironmentIds', () => { }) }) -type RuntimeEnvironmentStoreSubscriber = RuntimeEnvironmentStoreSyncSubscriber -type RuntimeEnvironmentStoreState = RuntimeEnvironmentStoreSyncState - function makeRuntimeEnvironmentStoreState(args: { environments: readonly { id: string; createdAt: number; pairingRevision?: number }[] statuses: ReadonlyMap sshStateByEnvironment?: ReadonlyMap activeEnvironmentId?: string | null settingsRevision?: number -}): RuntimeEnvironmentStoreState { +}): RuntimeEnvironmentStoreSyncState { return { runtimeEnvironments: args.environments, runtimeStatusByEnvironmentId: args.statuses, @@ -90,7 +87,7 @@ function makeRuntimeEnvironmentStoreState(args: { activeRuntimeEnvironmentId: args.activeEnvironmentId ?? null, terminalFontSize: args.settingsRevision ?? 0 } - } as unknown as RuntimeEnvironmentStoreState + } as unknown as RuntimeEnvironmentStoreSyncState } describe('createRuntimeEnvironmentStoreSyncSubscriber', () => { @@ -146,7 +143,7 @@ describe('createRuntimeEnvironmentStoreSyncSubscriber', () => { pendingStartupByTabId: { [`local-tab-${write}`]: true }, sshConnectionStates: new Map([[`direct-ssh-${write}`, { status: 'connected' }]]), folderWorkspaces: [{ id: `folder-${write}` }] - } as unknown as RuntimeEnvironmentStoreState + } as unknown as RuntimeEnvironmentStoreSyncState subscriber(currentState, previousState) } @@ -187,8 +184,8 @@ describe('createRuntimeEnvironmentStoreSyncSubscriber', () => { ]) const refreshes: string[] = [] let syncs = 0 - let subscriber: RuntimeEnvironmentStoreSubscriber - const publish = (nextState: RuntimeEnvironmentStoreState): void => { + let subscriber: RuntimeEnvironmentStoreSyncSubscriber + const publish = (nextState: RuntimeEnvironmentStoreSyncState): void => { const previousState = currentState currentState = nextState subscriber(nextState, previousState) diff --git a/src/renderer/src/i18n/hosted-review-localized-copy.ts b/src/renderer/src/i18n/hosted-review-localized-copy.ts index 2071d8aa82e..14c85d53c9c 100644 --- a/src/renderer/src/i18n/hosted-review-localized-copy.ts +++ b/src/renderer/src/i18n/hosted-review-localized-copy.ts @@ -5,8 +5,6 @@ import { } from '../../../shared/hosted-review-creation-providers' import { translate } from '@/i18n/i18n' -export type SupportedHostedReviewCopyProvider = HostedReviewCreationProvider - export type LocalizedHostedReviewCopy = { shortLabel: string reviewLabel: string @@ -16,12 +14,12 @@ export type LocalizedHostedReviewCopy = { export function resolveSupportedHostedReviewCopyProvider( provider: HostedReviewProvider | null | undefined -): SupportedHostedReviewCopyProvider { +): HostedReviewCreationProvider { return resolveHostedReviewCreationProvider(provider) } export function localizedHostedReviewCopy( - provider: SupportedHostedReviewCopyProvider + provider: HostedReviewCreationProvider ): LocalizedHostedReviewCopy { if (provider === 'gitlab') { return { diff --git a/src/renderer/src/lib/browser-palette-page-entries.ts b/src/renderer/src/lib/browser-palette-page-entries.ts index 0220bbb53dd..d3c92c1ad05 100644 --- a/src/renderer/src/lib/browser-palette-page-entries.ts +++ b/src/renderer/src/lib/browser-palette-page-entries.ts @@ -19,8 +19,6 @@ import { } from './unified-tab-host-ownership' import { maxValidPaletteActivityTimestamp } from './palette-match/palette-ranking' -type BrowserPaletteActiveTabType = WorkspaceVisibleTabType - export type BuildSearchableBrowserPagesOptions = { worktrees: readonly Worktree[] ownershipWorktrees?: readonly Pick[] @@ -34,7 +32,7 @@ export type BuildSearchableBrowserPagesOptions = { activeBrowserTabId: string | null activeWorktreeId: string | null activeWorkspaceExecutionHostId?: ExecutionHostId | null - activeTabType: BrowserPaletteActiveTabType + activeTabType: WorkspaceVisibleTabType } export function buildSearchableBrowserPages({ diff --git a/src/renderer/src/lib/codex-pane-restart-eligibility.ts b/src/renderer/src/lib/codex-pane-restart-eligibility.ts index f778833b986..4f52dabfbc9 100644 --- a/src/renderer/src/lib/codex-pane-restart-eligibility.ts +++ b/src/renderer/src/lib/codex-pane-restart-eligibility.ts @@ -4,8 +4,10 @@ import { } from '../../../shared/agent-process-recognition' import { isShellProcess } from '../../../shared/shell-process-detection' import type { TuiAgent } from '../../../shared/tui-agent' -import type { RuntimeTerminalProcessInspection } from '@/runtime/runtime-terminal-inspection' -import { isClientOnlyUnverifiableInspection } from '../../../shared/terminal-process-inspection' +import { + isClientOnlyUnverifiableInspection, + type TerminalProcessInspection +} from '../../../shared/terminal-process-inspection' function normalizeProcessName(processName: string | null): string | null { if (!processName) { @@ -42,7 +44,7 @@ export function isCodexForegroundProcess(processName: string | null): boolean { * that pane means the user exited Codex and is typing at their own program. */ export function isCodexRestartEligiblePane(args: { - inspection: RuntimeTerminalProcessInspection + inspection: TerminalProcessInspection launchAgent: TuiAgent | undefined }): boolean { if (isClientOnlyUnverifiableInspection(args.inspection)) { diff --git a/src/renderer/src/lib/codex-session-restart.ts b/src/renderer/src/lib/codex-session-restart.ts index c2f1119ca54..b144a7d9f16 100644 --- a/src/renderer/src/lib/codex-session-restart.ts +++ b/src/renderer/src/lib/codex-session-restart.ts @@ -2,9 +2,9 @@ import type { AppState } from '@/store' import { useAppStore } from '@/store' import { confirmRuntimeTerminalForegroundProcess, - inspectRuntimeTerminalProcess, - type RuntimeTerminalProcessInspection + inspectRuntimeTerminalProcess } from '@/runtime/runtime-terminal-inspection' +import type { TerminalProcessInspection } from '../../../shared/terminal-process-inspection' import { translate } from '@/i18n/i18n' import { getCodexAccountDisplayLabel, @@ -94,7 +94,7 @@ async function isConfirmedCodexForegroundDespiteShellReading( state: AppState, ptyId: string, launchAgent: TuiAgent | undefined, - inspection: RuntimeTerminalProcessInspection + inspection: TerminalProcessInspection ): Promise { if ( launchAgent !== 'codex' || diff --git a/src/renderer/src/lib/composer-issue-command.ts b/src/renderer/src/lib/composer-issue-command.ts index 3ac90dcda7b..c3129ba200f 100644 --- a/src/renderer/src/lib/composer-issue-command.ts +++ b/src/renderer/src/lib/composer-issue-command.ts @@ -3,11 +3,11 @@ import { canUseIssueCommandForLinkedItemProvider, renderIssueCommandTemplate } from '@/lib/new-workspace' -import type { FolderWorkspaceLinkedTask } from '../../../shared/folder-workspace-types' +import type { WorkspaceLinkedItem } from '../../../shared/worktree/types' type ComposerIssueCommandInput = { enabled: boolean - provider: FolderWorkspaceLinkedTask['provider'] | null + provider: WorkspaceLinkedItem['provider'] | null issueNumber: number | null template: string artifactUrl: string | null diff --git a/src/renderer/src/lib/duplicate-browser-tab-options.ts b/src/renderer/src/lib/duplicate-browser-tab-options.ts index 6b24b2b0417..b279b8d6aa2 100644 --- a/src/renderer/src/lib/duplicate-browser-tab-options.ts +++ b/src/renderer/src/lib/duplicate-browser-tab-options.ts @@ -1,7 +1,7 @@ -import type { BrowserTab } from '../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../shared/browser-workspace-types' export function buildDuplicatedBrowserTabOptions( - source: Pick + source: Pick ): { title: string sessionProfileId: string | null diff --git a/src/renderer/src/lib/ensure-hooks-confirmed.ts b/src/renderer/src/lib/ensure-hooks-confirmed.ts index 0a24c776c62..03d9ea55faf 100644 --- a/src/renderer/src/lib/ensure-hooks-confirmed.ts +++ b/src/renderer/src/lib/ensure-hooks-confirmed.ts @@ -15,8 +15,6 @@ import { type ExecutionHostId } from '../../../shared/execution-host' -export type HookScriptKind = OrcaHookScriptKind - const NEVER_CANCEL_TRUST_CHECK = (): boolean => false // Serialize the singleton modal callback so overlapping worktree actions cannot replace it. @@ -103,7 +101,7 @@ function canUseRepoWideTrust(state: AppState, repoId: string): boolean { async function confirmScriptContent( state: AppState, repoId: string, - scriptKind: HookScriptKind, + scriptKind: OrcaHookScriptKind, scriptContent: string, hostId?: ExecutionHostId, isCancelled: () => boolean = NEVER_CANCEL_TRUST_CHECK @@ -236,7 +234,7 @@ export async function readAndConfirmRuntimeIssueCommand( export async function ensureHooksConfirmed( state: AppState, repoId: string, - scriptKind: HookScriptKind, + scriptKind: OrcaHookScriptKind, hostId?: ExecutionHostId, runtimeOwnerEnvironmentId?: string | null, isCancelled: () => boolean = NEVER_CANCEL_TRUST_CHECK diff --git a/src/renderer/src/lib/explicit-file-link-target.ts b/src/renderer/src/lib/explicit-file-link-target.ts index c6188ca7518..a74297fcbb7 100644 --- a/src/renderer/src/lib/explicit-file-link-target.ts +++ b/src/renderer/src/lib/explicit-file-link-target.ts @@ -8,12 +8,7 @@ import { resolveTildePath } from './terminal-path-normalization' -export type ParsedExplicitFileLinkTarget = ParsedFileLinkLocation - -export type ResolvedExplicitFileLinkTarget = Pick< - ParsedExplicitFileLinkTarget, - 'line' | 'column' -> & { +export type ResolvedExplicitFileLinkTarget = Pick & { absolutePath: string } @@ -33,7 +28,7 @@ function canKeepTrailingSeparator(pathText: string): boolean { export function parseExplicitFileLinkTarget( value: string, options: ParseExplicitFileLinkTargetOptions = {} -): ParsedExplicitFileLinkTarget | null { +): ParsedFileLinkLocation | null { const parsed = parseFileLinkLocation(value) if (!parsed) { return null @@ -65,7 +60,7 @@ export function resolveExplicitFileLinkTargetPath( } export function resolveExplicitFileLinkTarget( - parsed: ParsedExplicitFileLinkTarget, + parsed: ParsedFileLinkLocation, cwd: string, homePath?: string | null ): ResolvedExplicitFileLinkTarget | null { diff --git a/src/renderer/src/lib/floating-workspace-tab-creation.ts b/src/renderer/src/lib/floating-workspace-tab-creation.ts index a443c572106..bd82263afe1 100644 --- a/src/renderer/src/lib/floating-workspace-tab-creation.ts +++ b/src/renderer/src/lib/floating-workspace-tab-creation.ts @@ -1,5 +1,5 @@ import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants' -import type { BrowserTab } from '../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../shared/browser-workspace-types' import type { TerminalTab } from '../../../shared/terminal-tab-types' import { createUntitledMarkdownFileWithTemplateSelection } from './create-untitled-markdown' import { getConnectionId } from './connection-context' @@ -39,7 +39,7 @@ export async function createFloatingWorkspaceTerminalTab( export async function createFloatingWorkspaceBrowserTab( store: FloatingWorkspaceBrowserStore -): Promise { +): Promise { assertClientCreationActionAvailable( store as AppState, FLOATING_TERMINAL_WORKTREE_ID, diff --git a/src/renderer/src/lib/floating-workspace-terminal-actions.ts b/src/renderer/src/lib/floating-workspace-terminal-actions.ts index d8c81add055..65811876199 100644 --- a/src/renderer/src/lib/floating-workspace-terminal-actions.ts +++ b/src/renderer/src/lib/floating-workspace-terminal-actions.ts @@ -1,11 +1,11 @@ +import type { WorkspaceVisibleTabType } from '../../../shared/tab-types' import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants' -import type { BrowserTab } from '../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../shared/browser-workspace-types' import type { TabGroup } from '../../../shared/tab-types' import { getGroupVisibleTabOrder } from '@/components/tab-bar/group-tab-order' import { getNextTabAcrossAllTypes, getNextTabWithinActiveType, - type TabCycleType, type TypeCyclableTab } from '@/components/terminal/tab-type-cycle' import type { AppState } from '@/store/types' @@ -114,7 +114,7 @@ function getActiveIdsForFloatingEntry(entry: TypeCyclableTab): { activeBrowserTabId: string | null activeFileId: string | null activeTabId: string | null - activeTabType: TabCycleType + activeTabType: WorkspaceVisibleTabType } { return { activeBrowserTabId: entry.type === 'browser' ? entry.id : null, @@ -127,7 +127,7 @@ function getActiveIdsForFloatingEntry(entry: TypeCyclableTab): { function getFloatingWorkspaceBrowserTab( store: FloatingWorkspaceTabSwitchStore, browserTabId: string -): BrowserTab | null { +): BrowserWorkspace | null { return ( (store.browserTabsByWorktree[FLOATING_TERMINAL_WORKTREE_ID] ?? []).find( (tab) => tab.id === browserTabId diff --git a/src/renderer/src/lib/folder-workspace-connection.ts b/src/renderer/src/lib/folder-workspace-connection.ts index b0840fc7e9f..606b3db2282 100644 --- a/src/renderer/src/lib/folder-workspace-connection.ts +++ b/src/renderer/src/lib/folder-workspace-connection.ts @@ -5,10 +5,8 @@ import { type FolderWorkspaceHostState } from '../../../shared/folder-workspace-execution-host' -export type FolderWorkspaceConnectionState = FolderWorkspaceHostState - export function getFolderWorkspaceCandidateRepos( - state: FolderWorkspaceConnectionState, + state: FolderWorkspaceHostState, folderWorkspaceId: string ): Repo[] { return findFolderWorkspaceCandidateRepos(state, folderWorkspaceId) @@ -16,7 +14,7 @@ export function getFolderWorkspaceCandidateRepos( /** Legacy tri-state view of the shared resolution: `undefined` = gone or ambiguous. */ export function getFolderWorkspaceConnectionId( - state: FolderWorkspaceConnectionState, + state: FolderWorkspaceHostState, folderWorkspaceId: string ): string | null | undefined { const host = resolveFolderWorkspaceHost(state, folderWorkspaceId) diff --git a/src/renderer/src/lib/http-link-routing.ts b/src/renderer/src/lib/http-link-routing.ts index 628a205f00f..b5a70ece494 100644 --- a/src/renderer/src/lib/http-link-routing.ts +++ b/src/renderer/src/lib/http-link-routing.ts @@ -40,7 +40,7 @@ type StoreAccessor = () => { setActiveWorktree: (worktreeId: string) => void createBrowserTab: (worktreeId: string, url: string, opts: { activate: boolean }) => unknown repos?: readonly LocalhostLinkRepo[] - projects?: readonly LocalhostLinkProject[] + projects?: readonly LocalhostLinkRepo[] worktreesByRepo?: Record allWorktrees?: () => LocalhostLinkWorktree[] workspacePortScan?: { result: WorkspacePortScanResult } | null @@ -62,8 +62,6 @@ type LocalhostLinkRepo = { displayName: string } -type LocalhostLinkProject = LocalhostLinkRepo - type LocalhostLinkWorktree = { id: string projectId?: string diff --git a/src/renderer/src/lib/lazy-with-retry.test.ts b/src/renderer/src/lib/lazy-with-retry.test.ts index 86b0bcef247..87aa3cc443b 100644 --- a/src/renderer/src/lib/lazy-with-retry.test.ts +++ b/src/renderer/src/lib/lazy-with-retry.test.ts @@ -16,7 +16,7 @@ import { ORCA_RENDERER_UNLOAD_PREVENTED_EVENT } from '../../../shared/renderer-s import { ORCA_APP_RESTART_ABORTED_EVENT } from '../../../shared/updater-renderer-events' import { ORCA_EDITOR_PREPARE_HOT_EXIT_EVENT, - type EditorPrepareHotExitDetail + type EditorSaveDirtyFilesDetail } from '../../../shared/editor-save-events' const RELOAD_GUARD_KEY = 'orca:lazy-chunk-reload-attempted' @@ -387,7 +387,7 @@ describe('loadLazyWithRetry recovery reload vs the dirty-editor-tab unload veto' preventUnloadAndScheduleShutdownCheckpointReset(event, window) } const hotExitBackup = (event: Event): void => { - const detail = (event as CustomEvent).detail + const detail = (event as CustomEvent).detail detail.claim() harness.hotExitBackups += 1 if (options.hotExitBackupFails === true) { diff --git a/src/renderer/src/lib/new-workspace.ts b/src/renderer/src/lib/new-workspace.ts index d19da5e5de4..5e0175289d1 100644 --- a/src/renderer/src/lib/new-workspace.ts +++ b/src/renderer/src/lib/new-workspace.ts @@ -13,7 +13,7 @@ import { queuePendingAgentStartupDelivery, resolveAgentStartupTabId } from '@/lib/agent-startup-delayed-delivery' -import type { FolderWorkspaceLinkedTask } from '../../../shared/folder-workspace-types' +import type { WorkspaceLinkedItem } from '../../../shared/worktree/types' import type { OrcaHooks } from '../../../shared/orca-yaml-hook-types' import { resolveHookCommandSourcePolicy } from '../../../shared/hook-command-source-policy' import { slugifyForWorkspaceName } from '../../../shared/workspace-name' @@ -32,8 +32,8 @@ export const CLIENT_PLATFORM: NodeJS.Platform = navigator.userAgent.includes('Wi export { getLinkedWorkItemProvider, isGitLabIssueUrl } from './linked-work-item-provider' -export type LinkedWorkItemSummary = Omit & { - provider?: FolderWorkspaceLinkedTask['provider'] +export type LinkedWorkItemSummary = Omit & { + provider?: WorkspaceLinkedItem['provider'] linearWorkspaceId?: string linearOrganizationUrlKey?: string linearBranchName?: string @@ -41,7 +41,7 @@ export type LinkedWorkItemSummary = Omit } export function canUseIssueCommandForLinkedItemProvider( - provider: FolderWorkspaceLinkedTask['provider'] | null + provider: WorkspaceLinkedItem['provider'] | null ): boolean { return provider === 'github' || provider === 'gitlab' } diff --git a/src/renderer/src/lib/pane-manager/browser-mobile-driver-state.ts b/src/renderer/src/lib/pane-manager/browser-mobile-driver-state.ts index dd95a026d30..0e17250e6cd 100644 --- a/src/renderer/src/lib/pane-manager/browser-mobile-driver-state.ts +++ b/src/renderer/src/lib/pane-manager/browser-mobile-driver-state.ts @@ -1,16 +1,14 @@ import { useSyncExternalStore } from 'react' import type { RuntimeBrowserDriverState } from '../../../../shared/runtime-types' -export type BrowserDriverState = RuntimeBrowserDriverState - -const driverByBrowserPageId = new Map() +const driverByBrowserPageId = new Map() // Why: a shared instance keeps getDriverForBrowserPage referentially stable for useSyncExternalStore snapshots. -export const IDLE_BROWSER_DRIVER: BrowserDriverState = { kind: 'idle' } +export const IDLE_BROWSER_DRIVER: RuntimeBrowserDriverState = { kind: 'idle' } type BrowserDriverChangeEvent = { browserPageId: string - driver: BrowserDriverState + driver: RuntimeBrowserDriverState } type BrowserDriverChangeListener = (event: BrowserDriverChangeEvent) => void @@ -48,7 +46,10 @@ function notifyChange(event: BrowserDriverChangeEvent): void { } } -export function setDriverForBrowserPage(browserPageId: string, driver: BrowserDriverState): void { +export function setDriverForBrowserPage( + browserPageId: string, + driver: RuntimeBrowserDriverState +): void { if (driver.kind === 'idle') { driverByBrowserPageId.delete(browserPageId) } else { @@ -57,13 +58,13 @@ export function setDriverForBrowserPage(browserPageId: string, driver: BrowserDr notifyChange({ browserPageId, driver }) } -export function getDriverForBrowserPage(browserPageId: string): BrowserDriverState { +export function getDriverForBrowserPage(browserPageId: string): RuntimeBrowserDriverState { return driverByBrowserPageId.get(browserPageId) ?? IDLE_BROWSER_DRIVER } export function useBrowserDriverForPage( browserPageId: string | null | undefined -): BrowserDriverState { +): RuntimeBrowserDriverState { useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) return browserPageId ? getDriverForBrowserPage(browserPageId) : IDLE_BROWSER_DRIVER } @@ -105,7 +106,7 @@ export function useBrowserMobileDrivenPageIds( } export function hydrateBrowserDrivers( - drivers: { browserPageId: string; driver: BrowserDriverState }[] + drivers: { browserPageId: string; driver: RuntimeBrowserDriverState }[] ): void { const affectedPageIds = new Set(driverByBrowserPageId.keys()) driverByBrowserPageId.clear() diff --git a/src/renderer/src/lib/pane-manager/mobile-driver-state.ts b/src/renderer/src/lib/pane-manager/mobile-driver-state.ts index 689b3813288..c136fe05933 100644 --- a/src/renderer/src/lib/pane-manager/mobile-driver-state.ts +++ b/src/renderer/src/lib/pane-manager/mobile-driver-state.ts @@ -11,13 +11,11 @@ import type { RuntimeTerminalDriverState } from '../../../../shared/runtime-types' -export type DriverState = RuntimeTerminalDriverState - -const driverByPtyId = new Map() +const driverByPtyId = new Map() type DriverChangeEvent = { ptyId: string - driver: DriverState + driver: RuntimeTerminalDriverState } type DriverChangeListener = (event: DriverChangeEvent) => void const changeListeners = new Set() @@ -33,7 +31,7 @@ function notifyChange(event: DriverChangeEvent): void { } } -export function setDriverForPty(ptyId: string, driver: DriverState): void { +export function setDriverForPty(ptyId: string, driver: RuntimeTerminalDriverState): void { if (driver.kind === 'idle') { driverByPtyId.delete(ptyId) } else { @@ -55,11 +53,11 @@ export function replaceDriverPtyId(replacedPtyId: string, ptyId: string): void { setDriverForPty(replacedPtyId, { kind: 'idle' }) } -export function getDriverForPty(ptyId: string): DriverState { +export function getDriverForPty(ptyId: string): RuntimeTerminalDriverState { return driverByPtyId.get(ptyId) ?? { kind: 'idle' } } -export function getAllDrivers(): Map { +export function getAllDrivers(): Map { return new Map(driverByPtyId) } @@ -67,7 +65,9 @@ export function isPtyLocked(ptyId: string): boolean { return driverByPtyId.get(ptyId)?.kind === 'mobile' } -export function hydrateDrivers(drivers: { ptyId: string; driver: DriverState }[]): void { +export function hydrateDrivers( + drivers: { ptyId: string; driver: RuntimeTerminalDriverState }[] +): void { const affectedPtyIds = new Set(driverByPtyId.keys()) driverByPtyId.clear() diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-foreground-queue-state.ts b/src/renderer/src/lib/pane-manager/pane-terminal-foreground-queue-state.ts index 6d9d7524e27..0306f16ce29 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-foreground-queue-state.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-foreground-queue-state.ts @@ -1,8 +1,8 @@ +import type { ForegroundTerminalOutputTarget } from './pane-terminal-foreground-render-settle' import { queuedByTerminal, scheduleDrain, type QueueEntry, - type TerminalOutputTarget, type WriteTerminalOutputOptions } from './pane-terminal-output-queue-registry' @@ -12,7 +12,7 @@ export const LATENCY_SENSITIVE_FOREGROUND_COALESCE_DELAY_MS = 16 export const LATENCY_SENSITIVE_FOREGROUND_HOLD_SAFETY_DELAY_MS = 32 export function createQueueEntry( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, options: WriteTerminalOutputOptions ): QueueEntry { return { diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-flusher.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-flusher.ts index cf6727bb7ff..91111027603 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-flusher.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-flusher.ts @@ -1,4 +1,7 @@ -import { writeForegroundTerminalChunk } from './pane-terminal-foreground-render-settle' +import { + writeForegroundTerminalChunk, + type ForegroundTerminalOutputTarget +} from './pane-terminal-foreground-render-settle' import { registerTerminalOutputAckCredits } from './pane-terminal-output-ack-credit' import { armTerminalWriteStallWatch, @@ -26,12 +29,11 @@ import { fireQueuedAckCredits, queuedByTerminal, requestRegisteredTerminalBacklogRecovery, - scheduleDrain, - type TerminalOutputTarget + scheduleDrain } from './pane-terminal-output-queue-registry' export function flushTerminalOutputImpl( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, options?: { maxChars?: number } ): void { exposeDebugApi() diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-pipeline.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-pipeline.ts index 06ba58568d2..6a2c03e0189 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-pipeline.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-pipeline.ts @@ -1,4 +1,7 @@ -import { writeForegroundTerminalChunk } from './pane-terminal-foreground-render-settle' +import { + writeForegroundTerminalChunk, + type ForegroundTerminalOutputTarget +} from './pane-terminal-foreground-render-settle' import { runGuardedWriteCompletionStep } from './xterm-write-callback-guard' import { registerTerminalOutputAckCredits } from './pane-terminal-output-ack-credit' import { @@ -19,8 +22,7 @@ import { queuedByTerminal, scheduleDrain, type QueueEntry, - type TerminalOutputParsedCallback, - type TerminalOutputTarget + type TerminalOutputParsedCallback } from './pane-terminal-output-queue-registry' import { discardDetachedQueueEntry, @@ -29,7 +31,7 @@ import { // Why no per-write scroll enforcement: xterm's BufferService.isUserScrolling owns live follow/pin; app-side enforcement is limited to structural ops xterm can't identify, like replay. export function writeBackgroundTerminalChunk( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, data: string, onParsed?: TerminalOutputParsedCallback, onWriteFailure?: () => void @@ -69,7 +71,7 @@ function makeParseClockPacer(): () => void { } export function composeParsedCallback( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, onParsed: TerminalOutputParsedCallback | undefined, ackCreditsParsed: (() => void) | undefined, pacer: (() => void) | undefined @@ -87,7 +89,7 @@ export function composeParsedCallback( } export function composeWriteFailureCallback( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, ackCreditsParsed: (() => void) | undefined ): () => void { return () => { diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-queue-registry.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-queue-registry.ts index a7e87f380de..5c59f767392 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-queue-registry.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-queue-registry.ts @@ -16,8 +16,6 @@ import { terminalOutputSchedulerDebugState as debugState } from './pane-terminal-output-scheduler-debug' -export type TerminalOutputTarget = ForegroundTerminalOutputTarget - export type TerminalOutputBeforeWrite = (data: string) => void type TerminalBacklogRecoveryRequest = () => boolean export type TerminalOutputParsedCallback = () => void @@ -66,7 +64,7 @@ export type QueuedWrite = { } export type QueueEntry = { - terminal: TerminalOutputTarget + terminal: ForegroundTerminalOutputTarget chunks: QueueChunk[] chunkIndex: number queuedChars: number @@ -113,10 +111,10 @@ export const FOREGROUND_BACKLOG_WARNING = '\x18\x1b[0m\r\n[Orca skipped a burst of terminal output because the backlog grew too large.]\r\n' export const ALWAYS_REFRESH_FOREGROUND_SYNCHRONOUSLY = (): boolean => true -export const queuedByTerminal = new Map() +export const queuedByTerminal = new Map() setTerminalOutputDebugQueueReader(() => queuedByTerminal.values()) const backlogRecoveryByTerminal = new WeakMap< - TerminalOutputTarget, + ForegroundTerminalOutputTarget, TerminalBacklogRecoveryRequest >() let drainTimer: ReturnType | null = null @@ -210,7 +208,9 @@ export function fireQueuedAckCredits(entry: QueueEntry): void { } } -export function requestRegisteredTerminalBacklogRecovery(terminal: TerminalOutputTarget): boolean { +export function requestRegisteredTerminalBacklogRecovery( + terminal: ForegroundTerminalOutputTarget +): boolean { const requestRecovery = backlogRecoveryByTerminal.get(terminal) if (!requestRecovery) { return false @@ -219,7 +219,7 @@ export function requestRegisteredTerminalBacklogRecovery(terminal: TerminalOutpu } export function registerTerminalBacklogRecovery( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, requestRecovery: TerminalBacklogRecoveryRequest ): () => void { backlogRecoveryByTerminal.set(terminal, requestRecovery) @@ -230,7 +230,7 @@ export function registerTerminalBacklogRecovery( } } -export function discardTerminalOutput(terminal: TerminalOutputTarget): void { +export function discardTerminalOutput(terminal: ForegroundTerminalOutputTarget): void { exposeDebugApi() const entry = queuedByTerminal.get(terminal) if (entry) { diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts index a181de7416e..b4ace464f95 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-scheduler.ts @@ -1,3 +1,4 @@ +import type { ForegroundTerminalOutputTarget } from './pane-terminal-foreground-render-settle' import { failTerminalWriteStallWatch, isTerminalWritePipelineCertifiedDead, @@ -8,7 +9,6 @@ import { flushTerminalOutputImpl } from './pane-terminal-output-flusher' import { writeTerminalOutputImpl } from './pane-terminal-output-writer' import { requestRegisteredTerminalBacklogRecovery, - type TerminalOutputTarget, type WriteTerminalOutputOptions } from './pane-terminal-output-queue-registry' // Why this bare import: pane-terminal-output-drain registers the drain runner that the registry's @@ -42,14 +42,14 @@ export { type QueuedWrite, type TerminalOutputBeforeWrite, type TerminalOutputParsedCallback, - type TerminalOutputTarget, type WriteTerminalOutputOptions } from './pane-terminal-output-queue-registry' +export type { ForegroundTerminalOutputTarget } from './pane-terminal-foreground-render-settle' const PARSE_SETTLE_TIMEOUT_MS = 250 export function writeTerminalOutput( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, data: string, options: WriteTerminalOutputOptions ): void { @@ -57,18 +57,20 @@ export function writeTerminalOutput( } export function flushTerminalOutput( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, options?: { maxChars?: number } ): void { flushTerminalOutputImpl(terminal, options) } -export function requestTerminalBacklogRecovery(terminal: TerminalOutputTarget): void { +export function requestTerminalBacklogRecovery(terminal: ForegroundTerminalOutputTarget): void { exposeDebugApi() requestRegisteredTerminalBacklogRecovery(terminal) } -export function waitForTerminalOutputParsed(terminal: TerminalOutputTarget): Promise { +export function waitForTerminalOutputParsed( + terminal: ForegroundTerminalOutputTarget +): Promise { flushTerminalOutput(terminal) if (isTerminalWritePipelineCertifiedDead(terminal)) { // Why: a dead pipeline cannot settle; recovery owns it and serializers must not enqueue probe writes during a pending remount retry. diff --git a/src/renderer/src/lib/pane-manager/pane-terminal-output-writer.ts b/src/renderer/src/lib/pane-manager/pane-terminal-output-writer.ts index 89c3ba13364..d890c5502fc 100644 --- a/src/renderer/src/lib/pane-manager/pane-terminal-output-writer.ts +++ b/src/renderer/src/lib/pane-manager/pane-terminal-output-writer.ts @@ -1,4 +1,7 @@ -import { writeForegroundTerminalChunk } from './pane-terminal-foreground-render-settle' +import { + writeForegroundTerminalChunk, + type ForegroundTerminalOutputTarget +} from './pane-terminal-foreground-render-settle' import { registerTerminalOutputAckCredits } from './pane-terminal-output-ack-credit' import { armTerminalWriteStallWatch, @@ -39,12 +42,11 @@ import { discardTerminalOutput, queuedByTerminal, scheduleDrain, - type TerminalOutputTarget, type WriteTerminalOutputOptions } from './pane-terminal-output-queue-registry' export function writeTerminalOutputImpl( - terminal: TerminalOutputTarget, + terminal: ForegroundTerminalOutputTarget, data: string, options: WriteTerminalOutputOptions ): void { diff --git a/src/renderer/src/lib/typing-latency/echo-instrumentation.ts b/src/renderer/src/lib/typing-latency/echo-instrumentation.ts index bc9268d0383..baa8b988e5e 100644 --- a/src/renderer/src/lib/typing-latency/echo-instrumentation.ts +++ b/src/renderer/src/lib/typing-latency/echo-instrumentation.ts @@ -45,20 +45,16 @@ export { drainTimedOutEchoCandidates } type Disposable = { dispose: () => void } -export type KeystrokeSource = TypingInputSource - export type PreventedKeystrokeDiscard = 'pending' | 'counted-unmatched' | null -type PendingKeystroke = EchoCandidate - export type InstrumentedPane = { pane: ProbePane | null - undispatched: PendingKeystroke[] - nextDispatch: PendingKeystroke | null - deferredNextDispatch: PendingKeystroke | null - ignoredDispatches: PendingKeystroke[] + undispatched: EchoCandidate[] + nextDispatch: EchoCandidate | null + deferredNextDispatch: EchoCandidate | null + ignoredDispatches: EchoCandidate[] ignoredDispatchOverflowedAt: number | null - awaitingEcho: PendingKeystroke[] + awaitingEcho: EchoCandidate[] attributionGap: boolean parsingBatch: EchoBatch | null parsedBatches: EchoBatch[] @@ -71,7 +67,7 @@ export type InstrumentedPane = { export function recordKeystroke( entry: InstrumentedPane, now: number, - source: KeystrokeSource, + source: TypingInputSource, text: string = '' ): RecordedKeystroke { const dropped = drainTimedOutEchoCandidates(entry, now) @@ -89,7 +85,7 @@ export function recordKeystroke( /** Removes a prevented routed commit only if it never reached terminal.onData. */ export function discardUndispatchedKeystroke( entry: InstrumentedPane, - candidate: PendingKeystroke + candidate: EchoCandidate ): PreventedKeystrokeDiscard { if (candidate.status === 'unmatched-undispatched') { const index = entry.ignoredDispatches.lastIndexOf(candidate) diff --git a/src/renderer/src/lib/typing-latency/input-source.ts b/src/renderer/src/lib/typing-latency/input-source.ts index 2f6720c7d25..0b090823aca 100644 --- a/src/renderer/src/lib/typing-latency/input-source.ts +++ b/src/renderer/src/lib/typing-latency/input-source.ts @@ -1,5 +1,6 @@ import { summarizeLatencySamples, type LatencyPercentiles } from './diagnostic-summary' -import type { EchoObservation, KeystrokeSource } from './echo-instrumentation' +import type { EchoObservation } from './echo-instrumentation' +import type { TypingInputSource } from './input-events' import { appendExactLatencySample, appendTypingLatencySample, @@ -26,12 +27,12 @@ export type InputSourceLatency = { outputWritesPerInput: LatencyPercentiles } -export type InputSourceBreakdown = Record & { +export type InputSourceBreakdown = Record & { imeCommitChars: LatencyPercentiles } export type InputSourceTally = { - recordInput: (source: KeystrokeSource, text: string) => void + recordInput: (source: TypingInputSource, text: string) => void addObservation: (observation: EchoObservation) => void breakdown: () => InputSourceBreakdown } @@ -60,7 +61,7 @@ function summarizeSource(tally: SourceTally): InputSourceLatency { } export function createInputSourceTally(): InputSourceTally { - const bySource: Record = { + const bySource: Record = { direct: emptySourceTally(), ime: emptySourceTally() } diff --git a/src/renderer/src/lib/windows-terminal-capabilities.ts b/src/renderer/src/lib/windows-terminal-capabilities.ts index 4bc5d6d7b6e..1a86e715b13 100644 --- a/src/renderer/src/lib/windows-terminal-capabilities.ts +++ b/src/renderer/src/lib/windows-terminal-capabilities.ts @@ -1,9 +1,7 @@ import { useEffect, useMemo, useState } from 'react' import { startWindowsTerminalCapabilityReprobe } from './windows-terminal-capability-reprobe' -import { - readWindowsTerminalCapabilities, - type WindowsTerminalCapabilityLoadTarget -} from './windows-terminal-capability-read' +import { readWindowsTerminalCapabilities } from './windows-terminal-capability-read' +import type { RuntimeClientTarget } from '@/runtime/runtime-rpc-client' export type WindowsTerminalCapabilities = { wslAvailable: boolean @@ -46,7 +44,7 @@ type WindowsTerminalCapabilityHookState = { function resolveWindowsTerminalCapabilityCacheKey(args: { ownerKey?: string - target?: WindowsTerminalCapabilityLoadTarget + target?: RuntimeClientTarget sshConnectionId?: string | null }): string { const explicitOwnerKey = args.ownerKey?.trim() @@ -128,7 +126,7 @@ export function loadWindowsTerminalCapabilities( force?: boolean now?: number ownerKey?: string - target?: WindowsTerminalCapabilityLoadTarget + target?: RuntimeClientTarget sshConnectionId?: string | null } = {} ): Promise { @@ -178,7 +176,7 @@ export function loadWindowsTerminalCapabilities( export function refreshWindowsTerminalCapabilities( ownerKey?: string, - target: WindowsTerminalCapabilityLoadTarget = { kind: 'local' }, + target: RuntimeClientTarget = { kind: 'local' }, sshConnectionId?: string | null ): Promise { return loadWindowsTerminalCapabilities({ force: true, ownerKey, target, sshConnectionId }) @@ -201,13 +199,13 @@ export function useWindowsTerminalCapabilities( enabled: boolean, forceRefreshOnMount = false, ownerKey?: string, - target: WindowsTerminalCapabilityLoadTarget = { kind: 'local' }, + target: RuntimeClientTarget = { kind: 'local' }, sshConnectionId?: string | null ): WindowsTerminalCapabilities { const targetKind = target.kind const targetEnvironmentId = target.kind === 'environment' ? target.environmentId : null const sshConnectionIdKey = sshConnectionId?.trim() || null - const resolvedTarget: WindowsTerminalCapabilityLoadTarget = useMemo( + const resolvedTarget: RuntimeClientTarget = useMemo( () => targetKind === 'environment' && targetEnvironmentId ? { kind: 'environment', environmentId: targetEnvironmentId } @@ -285,7 +283,7 @@ export function useWindowsTerminalCapabilities( export function isWindowsTerminalCapabilityHost(args: { isWindowsRenderer: boolean isWebClient: boolean - target: WindowsTerminalCapabilityLoadTarget + target: RuntimeClientTarget hostPlatform: NodeJS.Platform | null }): boolean { return ( diff --git a/src/renderer/src/lib/windows-terminal-capability-read.ts b/src/renderer/src/lib/windows-terminal-capability-read.ts index 9a77538cefc..1e791beead9 100644 --- a/src/renderer/src/lib/windows-terminal-capability-read.ts +++ b/src/renderer/src/lib/windows-terminal-capability-read.ts @@ -2,8 +2,6 @@ import { callRuntimeRpc, type RuntimeClientTarget } from '@/runtime/runtime-rpc- import type { RuntimeStatus } from '../../../shared/runtime-types' import type { WindowsTerminalCapabilities } from './windows-terminal-capabilities' -export type WindowsTerminalCapabilityLoadTarget = RuntimeClientTarget - async function reconcileWslAvailability( available: boolean, distros: string[], @@ -17,7 +15,7 @@ async function reconcileWslAvailability( } export async function readWindowsTerminalCapabilities( - target: WindowsTerminalCapabilityLoadTarget, + target: RuntimeClientTarget, sshConnectionId?: string | null ): Promise { if (sshConnectionId) { diff --git a/src/renderer/src/lib/workspace-emoji-shortcodes.ts b/src/renderer/src/lib/workspace-emoji-shortcodes.ts index 184207ffbd8..5078e6a6ef9 100644 --- a/src/renderer/src/lib/workspace-emoji-shortcodes.ts +++ b/src/renderer/src/lib/workspace-emoji-shortcodes.ts @@ -9,8 +9,6 @@ import { // empty catalog until it settled, so a `:wink:` submitted in that window persisted literally. setEmojiShortcodeDatasetLoader(() => emojiShortcodes) -export type WorkspaceEmojiSuggestion = StandardEmojiShortcodeEntry - export type ActiveWorkspaceEmojiShortcode = { end: number query: string @@ -24,9 +22,9 @@ export type WorkspaceEmojiReplacement = { // Lazy for the same reason as the shared catalog it indexes: nothing needs it // until a `:` shortcode is completed. -let exactShortcode: ReadonlyMap | null = null +let exactShortcode: ReadonlyMap | null = null -function exactShortcodeIndex(): ReadonlyMap { +function exactShortcodeIndex(): ReadonlyMap { exactShortcode ??= new Map( getStandardEmojiShortcodeEntries().map(({ emoji, shortcode }) => [ shortcode, @@ -53,7 +51,7 @@ function matchTier(shortcode: string, query: string): number | null { export function searchWorkspaceEmojiShortcodes( query: string, limit = 8 -): WorkspaceEmojiSuggestion[] { +): StandardEmojiShortcodeEntry[] { const normalizedQuery = query.trim().toLowerCase() if (!normalizedQuery || limit <= 0) { return [] @@ -71,7 +69,7 @@ export function searchWorkspaceEmojiShortcodes( left.shortcode.localeCompare(right.shortcode) ) const seenEmoji = new Set() - const suggestions: WorkspaceEmojiSuggestion[] = [] + const suggestions: StandardEmojiShortcodeEntry[] = [] for (const { emoji, shortcode } of matches) { if (seenEmoji.has(emoji)) { continue @@ -125,7 +123,7 @@ export function replaceCompletedWorkspaceEmojiShortcode( export function applyWorkspaceEmojiSuggestion( value: string, active: ActiveWorkspaceEmojiShortcode, - suggestion: WorkspaceEmojiSuggestion + suggestion: StandardEmojiShortcodeEntry ): WorkspaceEmojiReplacement { return replaceWorkspaceEmojiRange(value, active.start, active.end, suggestion.emoji, true) } diff --git a/src/renderer/src/lib/workspace-tab-palette-search.ts b/src/renderer/src/lib/workspace-tab-palette-search.ts index 8ec344f0544..2d2ea441a90 100644 --- a/src/renderer/src/lib/workspace-tab-palette-search.ts +++ b/src/renderer/src/lib/workspace-tab-palette-search.ts @@ -49,8 +49,6 @@ export type SearchableWorkspaceTab = { // secondary crowds the row. Keep these matchable so typing "terminal" still finds them. export const TERMINAL_TYPE_SEARCH_ALIASES = ['terminal tab', 'terminal'] as const -type WorkspaceTabPaletteActiveTabType = WorkspaceVisibleTabType - export type BuildSearchableWorkspaceTabsOptions = WorkspaceTabAgentMetadataState & { worktrees: readonly Worktree[] ownershipWorktrees?: readonly Pick[] @@ -64,12 +62,12 @@ export type BuildSearchableWorkspaceTabsOptions = WorkspaceTabAgentMetadataState groupsByWorktree: Record activeWorktreeId: string | null activeWorkspaceExecutionHostId?: ExecutionHostId | null - activeTabType: WorkspaceTabPaletteActiveTabType + activeTabType: WorkspaceVisibleTabType activeTabId: string | null activeTabIdByWorktree: Record activeFileId: string | null activeFileIdByWorktree: Record - activeTabTypeByWorktree: Record + activeTabTypeByWorktree: Record generatedTitlesEnabled: boolean terminalLayoutsByTabId?: Record paneForegroundAgentByPaneKey?: Record diff --git a/src/renderer/src/lib/workspace-terminal-host-authority.ts b/src/renderer/src/lib/workspace-terminal-host-authority.ts index ad535e94563..96b2ee706cf 100644 --- a/src/renderer/src/lib/workspace-terminal-host-authority.ts +++ b/src/renderer/src/lib/workspace-terminal-host-authority.ts @@ -1,18 +1,6 @@ -import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants' -import { parseExecutionHostId } from '../../../shared/execution-host' -import { parseWorkspaceKey } from '../../../shared/workspace-scope' -import type { HostLiveTerminalProbeVerdict } from '@/runtime/host-live-terminal-probe' -import type { RemoteWorkspaceSyncStatus } from '@/store/slices/ssh' -import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session' -import { - getExecutionHostIdForWorktree, - getRuntimeEnvironmentIdForWorktree, - type WorktreeRuntimeOwnerState -} from '@/lib/worktree-runtime-owner' - /** * Who holds a workspace's terminals right now, in the same three-verdict vocabulary the renderer - * already uses for host terminal inventory ({@link HostLiveTerminalProbeVerdict}) — aliased rather + * already uses for host terminal inventory ({@link HostLiveTerminalProbeVerdict}), reused rather * than restated so the two cannot drift: * * - `live` — a remote execution host owns terminal creation here. It supplies the surface itself. @@ -33,7 +21,18 @@ import { * * See docs/reference/ssh-execution-boundary.md. */ -export type WorkspaceTerminalHostAuthority = HostLiveTerminalProbeVerdict + +import { FLOATING_TERMINAL_WORKTREE_ID } from '../../../shared/constants' +import { parseExecutionHostId } from '../../../shared/execution-host' +import { parseWorkspaceKey } from '../../../shared/workspace-scope' +import type { HostLiveTerminalProbeVerdict } from '@/runtime/host-live-terminal-probe' +import type { RemoteWorkspaceSyncStatus } from '@/store/slices/ssh' +import { isWebRuntimeSessionActive } from '@/runtime/web-runtime-session' +import { + getExecutionHostIdForWorktree, + getRuntimeEnvironmentIdForWorktree, + type WorktreeRuntimeOwnerState +} from '@/lib/worktree-runtime-owner' export type WorkspaceTerminalHostAuthorityState = WorktreeRuntimeOwnerState & { remoteWorkspaceHydratedTargetIds?: ReadonlySet @@ -58,7 +57,7 @@ const TERMINATED_WITHOUT_ANSWER_PHASES = new Set(['offline', 'error']) function resolveDirectSshAuthority( state: WorkspaceTerminalHostAuthorityState, targetId: string -): WorkspaceTerminalHostAuthority { +): HostLiveTerminalProbeVerdict { const phase = state.remoteWorkspaceSyncStatusByTargetId?.[targetId]?.phase if (state.remoteWorkspaceHydratedTargetIds?.has(targetId)) { // Why: the same pair use-app-session-persistence.ts gates uploads on. A conflicting snapshot @@ -88,7 +87,7 @@ function resolveDirectSshAuthority( export function resolveWorkspaceTerminalHostAuthority( state: WorkspaceTerminalHostAuthorityState, worktreeId: string | null | undefined -): WorkspaceTerminalHostAuthority { +): HostLiveTerminalProbeVerdict { if (!worktreeId || worktreeId === FLOATING_TERMINAL_WORKTREE_ID) { return 'none' } @@ -158,9 +157,9 @@ function captureAuthorityInputs(state: WorkspaceTerminalHostAuthorityState): Aut export function createWorkspaceTerminalHostAuthoritySelector( worktreeId: string | null | undefined -): (state: WorkspaceTerminalHostAuthorityState) => WorkspaceTerminalHostAuthority { +): (state: WorkspaceTerminalHostAuthorityState) => HostLiveTerminalProbeVerdict { let previousInputs: AuthorityInputs | null = null - let previousResult: WorkspaceTerminalHostAuthority = 'none' + let previousResult: HostLiveTerminalProbeVerdict = 'none' return (state) => { const inputs = captureAuthorityInputs(state) if (previousInputs?.every((value, index) => value === inputs[index]) === true) { diff --git a/src/renderer/src/lib/worktree-live-terminal-surface-owners.ts b/src/renderer/src/lib/worktree-live-terminal-surface-owners.ts index d42c8e5ad4f..a28331e13a1 100644 --- a/src/renderer/src/lib/worktree-live-terminal-surface-owners.ts +++ b/src/renderer/src/lib/worktree-live-terminal-surface-owners.ts @@ -1,6 +1,6 @@ import { isTerminalLeafId, makePaneKey } from '../../../shared/stable-pane-id' import type { - RuntimeTerminalListHostScope, + RuntimeListingHostScope, RuntimeTerminalListResult, RuntimeTerminalSummary } from '../../../shared/runtime-types' @@ -32,7 +32,7 @@ const OWNER_LISTING_LIMIT = 200 /** A host that predates `hostScope` cannot say what it answered for, so it cannot be read. */ function isScopedTerminalListResult( value: unknown -): value is RuntimeTerminalListResult & { hostScope: RuntimeTerminalListHostScope } { +): value is RuntimeTerminalListResult & { hostScope: RuntimeListingHostScope } { if ( !value || typeof value !== 'object' || diff --git a/src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-generation-fence.ts b/src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-generation-fence.ts index 8ab68385f9e..4fd7bdae942 100644 --- a/src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-generation-fence.ts +++ b/src/renderer/src/runtime/local-structured-session-tabs-sync/inventory-generation-fence.ts @@ -1,4 +1,4 @@ -import type { SessionTabsPublicationEpochHistory } from '../web-session-tabs-sync/state' +import type { RetiredValueHistory } from '../web-session-tabs-sync/state' import type { StructuredSessionTabPublicationVersion } from '../local-structured-session-tab-retirement' // Everything a toggle-off must invalidate: which publisher instance the renderer @@ -13,10 +13,7 @@ export const localStructuredSessionVersionByWorktree = new Map< string, StructuredSessionTabPublicationVersion >() -export const localStructuredSessionEpochHistoryByWorktree = new Map< - string, - SessionTabsPublicationEpochHistory ->() +export const localStructuredSessionEpochHistoryByWorktree = new Map() export function localStructuredSessionGeneration(): number { return syncGeneration diff --git a/src/renderer/src/runtime/runtime-git-client-context.ts b/src/renderer/src/runtime/runtime-git-client-context.ts index 53751a51246..5c9e9032562 100644 --- a/src/renderer/src/runtime/runtime-git-client-context.ts +++ b/src/renderer/src/runtime/runtime-git-client-context.ts @@ -58,8 +58,6 @@ export type RuntimeGenerateCommitMessageOverrides = { agentCmdOverrides?: GlobalSettings['agentCmdOverrides'] } -export type RuntimeGeneratePullRequestFieldsOverrides = RuntimeGenerateCommitMessageOverrides - export function resolveLocalWorktreePath(context: RuntimeGitContext): string { return context.worktreeId ? (splitWorktreeIdForFilesystem(context.worktreeId)?.worktreePath ?? context.worktreePath) diff --git a/src/renderer/src/runtime/runtime-git-client.ts b/src/renderer/src/runtime/runtime-git-client.ts index 2020fa43bc9..732859fbcef 100644 --- a/src/renderer/src/runtime/runtime-git-client.ts +++ b/src/renderer/src/runtime/runtime-git-client.ts @@ -47,7 +47,6 @@ import { export type { RuntimeGenerateCommitMessageOverrides, RuntimeGenerateCommitMessageResult, - RuntimeGeneratePullRequestFieldsOverrides, RuntimeGeneratePullRequestFieldsResult, RuntimeGitContext, RuntimePullRequestGenerationInput diff --git a/src/renderer/src/runtime/runtime-git-generation-client.ts b/src/renderer/src/runtime/runtime-git-generation-client.ts index 2bf17b3bb3d..a5c681d10d4 100644 --- a/src/renderer/src/runtime/runtime-git-generation-client.ts +++ b/src/renderer/src/runtime/runtime-git-generation-client.ts @@ -5,7 +5,6 @@ import { type RuntimeDiscoverCommitMessageModelsResult, type RuntimeGenerateCommitMessageOverrides, type RuntimeGenerateCommitMessageResult, - type RuntimeGeneratePullRequestFieldsOverrides, type RuntimeGeneratePullRequestFieldsResult, type RuntimeGitContext, type RuntimePullRequestGenerationInput @@ -96,7 +95,7 @@ export async function cancelRuntimeGenerateCommitMessage( export async function generateRuntimePullRequestFields( context: RuntimeGitContext, input: RuntimePullRequestGenerationInput, - overrides?: RuntimeGeneratePullRequestFieldsOverrides + overrides?: RuntimeGenerateCommitMessageOverrides ): Promise { const target = getActiveRuntimeTarget(context.settings) if (target.kind === 'local' || !context.worktreeId) { diff --git a/src/renderer/src/runtime/runtime-terminal-inspection.ts b/src/renderer/src/runtime/runtime-terminal-inspection.ts index b34d75f4555..a0812430977 100644 --- a/src/renderer/src/runtime/runtime-terminal-inspection.ts +++ b/src/renderer/src/runtime/runtime-terminal-inspection.ts @@ -21,8 +21,6 @@ export type { ClientOnlyUnverifiableReason } from '../../../shared/terminal-process-inspection' -export type RuntimeTerminalProcessInspection = TerminalProcessInspection - const REMOTE_PTY_ID_PREFIX = 'remote:' const DESKTOP_RUNTIME_CLIENT = { id: 'orca-desktop', type: 'desktop' } as const type TerminalLayoutsByTabId = ReturnType['terminalLayoutsByTabId'] @@ -91,7 +89,7 @@ function isRemoteInspectionPtyId(ptyId: string): boolean { function normalizeInspectionResult( result: TerminalProcessInspection, remote: boolean -): RuntimeTerminalProcessInspection { +): TerminalProcessInspection { if (typeof result !== 'object' || result === null) { return clientOnlyUnverifiableInspection(remote ? 'old_host' : 'terminal_gone') } @@ -139,7 +137,7 @@ export async function inspectRuntimeTerminalProcess( settings: Pick | null | undefined, ptyId: string, options?: { expectedIncarnationId?: string; scanChildProcesses?: boolean; steadyState?: boolean } -): Promise { +): Promise { const ownerEnvironmentId = getRemoteRuntimePtyEnvironmentId(ptyId) const target = ownerEnvironmentId ? ({ kind: 'environment', environmentId: ownerEnvironmentId } as const) @@ -162,7 +160,7 @@ export async function inspectRuntimeTerminalProcess( } try { - const result = await callRuntimeRpc<{ process: RuntimeTerminalProcessInspection }>( + const result = await callRuntimeRpc<{ process: TerminalProcessInspection }>( target, 'terminal.inspectProcess', { diff --git a/src/renderer/src/runtime/web-session-tabs-sync/agent-status-patch.ts b/src/renderer/src/runtime/web-session-tabs-sync/agent-status-patch.ts index 4b29c800ced..51636f17959 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/agent-status-patch.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/agent-status-patch.ts @@ -8,10 +8,10 @@ import { isWebTerminalSurfaceTabId, toWebTerminalSurfaceTabId } from '../web-run import type { TerminalTab } from '../../../../shared/terminal-tab-types' import type { MirroredTerminalTab, - TerminalSurface, WebSessionTabsBatchContext, WebSessionTabsSyncState } from './state' +import type { RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' import { isClientOwnedAgentStatus, isFencedClientAgentStatus, @@ -58,7 +58,7 @@ function withMirroredEvidenceReceipt( export function buildMirroredAgentStatusPatch( state: WebSessionTabsSyncState, currentTerminalTabs: readonly TerminalTab[], - terminalSurfaceTabs: readonly TerminalSurface[], + terminalSurfaceTabs: readonly RuntimeMobileSessionTerminalClientTab[], mirroredTerminalTabs: readonly MirroredTerminalTab[], now: number, batchContext?: WebSessionTabsBatchContext @@ -78,7 +78,7 @@ export function buildMirroredAgentStatusPatch( } let retainedSurfaceByHostTabAndPrunedLeafId: - | Map> + | Map> | undefined for (const entry of mirroredTerminalTabs) { if (entry.retainedSurfaceByPrunedLeafId) { diff --git a/src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts b/src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts index f0eb938f00e..d5893ddf5d6 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/agent-status-primitives.ts @@ -4,14 +4,15 @@ import { isClientAuthoritativeAgentStatusPane } from '@/components/terminal-pane import { normalizeCompatibleAgentStatusEntryForOwner } from '../../../../shared/agent-title-owner' import { resolvePaneAgentOwnerRecord } from '../../../../shared/pane-agent-owner' import { toWebTerminalSurfaceTabId } from '../web-runtime-session' -import type { TerminalSurface, WebSessionTabsBatchContext, WebSessionTabsSyncState } from './state' +import type { WebSessionTabsBatchContext, WebSessionTabsSyncState } from './state' +import type { RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' import type { RetiredTerminalTabSweepState } from '../../store/slices/retired-terminal-tab-state-sweep' import { buildRetiredTerminalTabStateSweepPatch } from '../../store/slices/retired-terminal-tab-state-sweep' import { isMirroredTerminalSurfaceId } from './terminal-surfaces' import { isAgentStatusFresh } from './state-equality-core' export function toMirroredPaneKey( - surface: TerminalSurface, + surface: RuntimeMobileSessionTerminalClientTab, leafId = surface.leafId ): string | null { if (!isTerminalLeafId(leafId)) { @@ -22,8 +23,8 @@ export function toMirroredPaneKey( /** Normalises and mirrors agent status updates from the host payload, preserving ownership metadata. */ export function remapHostAgentStatus( - surface: TerminalSurface, - retainedSurface?: TerminalSurface + surface: RuntimeMobileSessionTerminalClientTab, + retainedSurface?: RuntimeMobileSessionTerminalClientTab ): AgentStatusEntry | null { if (!surface.agentStatus) { return null diff --git a/src/renderer/src/runtime/web-session-tabs-sync/layout-groups.ts b/src/renderer/src/runtime/web-session-tabs-sync/layout-groups.ts index d85b7c6954a..9aada90c02a 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/layout-groups.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/layout-groups.ts @@ -1,12 +1,8 @@ import type { RuntimeMobileSessionTabGroup } from '../../../../shared/runtime-types' import type { TabGroup } from '../../../../shared/tab-types' import type { TerminalTab } from '../../../../shared/terminal-tab-types' -import type { - MirroredAgentTab, - MirroredBrowserTab, - MirroredEditorTab, - TerminalSurface -} from './state' +import type { MirroredAgentTab, MirroredBrowserTab, MirroredEditorTab } from './state' +import type { RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' import { toWebTerminalSurfaceTabId } from '../web-runtime-session' import { clearHostSessionTabIdMappings, setHostSessionTabIdMapping } from './tracking-mappings' import { isWebSessionBrowserPlacementGroupReserved } from '../web-session-browser-placement' @@ -21,7 +17,7 @@ export function buildHostToLocalTabIdMap({ editorTabs, agentTabs }: { - terminalSurfaces: readonly TerminalSurface[] + terminalSurfaces: readonly RuntimeMobileSessionTerminalClientTab[] terminalTabs: readonly TerminalTab[] browserTabs: readonly MirroredBrowserTab[] editorTabs: readonly MirroredEditorTab[] @@ -52,7 +48,7 @@ export function buildHostToLocalTabIdMap({ export function updateHostSessionTabIdMappings(args: { environmentId: string worktreeId: string - terminalSurfaces: readonly TerminalSurface[] + terminalSurfaces: readonly RuntimeMobileSessionTerminalClientTab[] terminalTabs: readonly TerminalTab[] browserTabs: readonly MirroredBrowserTab[] editorTabs: readonly MirroredEditorTab[] diff --git a/src/renderer/src/runtime/web-session-tabs-sync/publisher-identity-fences.ts b/src/renderer/src/runtime/web-session-tabs-sync/publisher-identity-fences.ts index fbab01b591b..5be49012654 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/publisher-identity-fences.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/publisher-identity-fences.ts @@ -3,9 +3,7 @@ import { latestReceivedSessionTabsFrameByEnvironment, sessionTabsPublicationEpochHistoryByWorktree, sessionTabsRuntimeHistoryByEnvironment, - type RetiredValueHistory, - type SessionTabsPublicationEpochHistory, - type SessionTabsRuntimeHistory + type RetiredValueHistory } from './state' const SESSION_TABS_RETIRED_EPOCH_LIMIT = 8 @@ -77,10 +75,7 @@ export function isRetiredSessionTabsRuntimeId(environmentId: string, runtimeId: return hasRetiredValue(sessionTabsRuntimeHistoryByEnvironment.get(environmentId), runtimeId) } -function noteSessionTabsRuntimeId( - environmentId: string, - runtimeId: string -): SessionTabsRuntimeHistory { +function noteSessionTabsRuntimeId(environmentId: string, runtimeId: string): RetiredValueHistory { const history = noteRetiredValue( sessionTabsRuntimeHistoryByEnvironment.get(environmentId), runtimeId, @@ -157,7 +152,7 @@ export function isHeadlessMergeSessionTabsPublication(publicationEpoch: string): export function noteSessionTabsPublicationEpoch( key: string, publicationEpoch: string -): SessionTabsPublicationEpochHistory { +): RetiredValueHistory { const history = noteRetiredValue( sessionTabsPublicationEpochHistoryByWorktree.get(key), publicationEpoch, diff --git a/src/renderer/src/runtime/web-session-tabs-sync/state.ts b/src/renderer/src/runtime/web-session-tabs-sync/state.ts index f44d4a3d18c..eaa7874205c 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/state.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/state.ts @@ -1,11 +1,8 @@ import type { AppState } from '../../store' import type { - RuntimeMobileSessionAgentTab, RuntimeMobileSessionBrowserTab, RuntimeMobileSessionFileTab, RuntimeMobileSessionMarkdownTab, - RuntimeMobileSessionTabGroup, - RuntimeMobileSessionTabsRemovedResult, RuntimeMobileSessionTabsResult, RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' @@ -54,15 +51,6 @@ export type RetiredValueHistory = { retired: string[] } -export type SessionTabsRuntimeHistory = RetiredValueHistory - -/** - * A host restart changes the publication epoch, but frames from the previous - * epoch can still be queued on a sibling subscription. Keep a small history - * of epochs that have already been superseded so those delayed frames cannot - * roll the mirror back after the replacement epoch is accepted. - */ -export type SessionTabsPublicationEpochHistory = RetiredValueHistory export type SessionTabsRecoveryState = { pendingCount: number } export type SessionTabsRemovalFence = { receivedFrame: number @@ -95,11 +83,14 @@ export const latestReceivedSessionTabsSnapshotByWorktree = new Map< string, ReceivedSessionTabsSnapshot >() -export const sessionTabsRuntimeHistoryByEnvironment = new Map() -export const sessionTabsPublicationEpochHistoryByWorktree = new Map< - string, - SessionTabsPublicationEpochHistory ->() +export const sessionTabsRuntimeHistoryByEnvironment = new Map() +/** + * A host restart changes the publication epoch, but frames from the previous + * epoch can still be queued on a sibling subscription. Keep a small history + * of epochs that have already been superseded so those delayed frames cannot + * roll the mirror back after the replacement epoch is accepted. + */ +export const sessionTabsPublicationEpochHistoryByWorktree = new Map() export const latestReceivedSessionTabsFrameByEnvironment = new Map() export const latestReceivedSessionTabsInventoryFrameByEnvironment = new Map() export const latestSessionTabsRemovalFenceByWorktree = new Map() @@ -141,7 +132,6 @@ export function resetReceivedSessionTabsFrameSequence(): void { receivedSessionTabsFrameSequence = 0 } -export type TerminalSurface = RuntimeMobileSessionTerminalClientTab export type ReadyTerminalSurface = RuntimeMobileSessionTerminalClientTab & { status: 'ready' } export type ReadyBrowserSurface = RuntimeMobileSessionBrowserTab & { browserPageId: string } export type ReadyEditorSurface = RuntimeMobileSessionMarkdownTab | RuntimeMobileSessionFileTab @@ -152,7 +142,7 @@ export type MirroredTerminalTab = { hostTabId: string ptyIds: string[] layout: TerminalLayoutSnapshot - retainedSurfaceByPrunedLeafId?: ReadonlyMap + retainedSurfaceByPrunedLeafId?: ReadonlyMap } export type MirroredBrowserTab = { workspace: BrowserWorkspace @@ -244,7 +234,3 @@ export type WebSessionTabsBatchContext = { changedRecords: Set openFilesIndex: WebSessionOpenFilesIndex | null } - -export type AgentTab = RuntimeMobileSessionAgentTab -export type TabGroupSnapshot = RuntimeMobileSessionTabGroup -export type RemovedTabsResult = RuntimeMobileSessionTabsRemovedResult diff --git a/src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts b/src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts index aacb445bbe3..45de01c2c54 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts @@ -5,7 +5,8 @@ import { resolvePaneAgentOwnerRecord } from '../../../../shared/pane-agent-owner import { normalizeCompatibleAgentTitleForOwner } from '../../../../shared/agent-title-owner' import { getRemoteRuntimePtyEnvironmentId, toRemoteRuntimePtyId } from '../runtime-terminal-stream' import { toWebTerminalSurfaceTabId } from '../web-runtime-session' -import type { MirroredTerminalTab, TerminalSurface, ReadyTerminalSurface } from './state' +import type { MirroredTerminalTab, ReadyTerminalSurface } from './state' +import type { RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' import { chooseRemoteTerminalLayout, isTerminalSurfaceTab } from './terminal-surfaces' function pendingBindingBelongsToEnvironment( @@ -21,7 +22,7 @@ function pendingBindingBelongsToEnvironment( /** Keep a known pane binding while the host briefly publishes its surface as pending. */ function retainPendingTerminalBindings( - surfaces: readonly TerminalSurface[], + surfaces: readonly RuntimeMobileSessionTerminalClientTab[], existingLayout: TerminalLayoutSnapshot | undefined, ptyIdsByLeafId: Record, environmentId: string, @@ -62,7 +63,7 @@ export function buildMirroredTerminalTabs( focusTarget?: { parentTabId: string; leafId: string }, terminalPtyMode: 'local' | 'remote' = 'remote' ): MirroredTerminalTab[] { - const groups = new Map() + const groups = new Map() for (const tab of snapshot.tabs.filter(isTerminalSurfaceTab)) { const group = groups.get(tab.parentTabId) ?? [] group.push(tab) @@ -102,7 +103,9 @@ export function buildMirroredTerminalTabs( ).snapshot const layoutPtyEntries = Object.entries(layout.ptyIdsByLeafId ?? {}) const ptyIds = layoutPtyEntries.map(([, ptyId]) => ptyId) - let retainedSurfaceByPrunedLeafId: Map | undefined + let retainedSurfaceByPrunedLeafId: + | Map + | undefined if (layoutPtyEntries.length < Object.keys(ptyIdsByLeafId).length) { const retainedLeafIdByPtyId = new Map( layoutPtyEntries.map(([leafId, ptyId]) => [ptyId, leafId]) diff --git a/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts b/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts index 50a1558533c..07985942f6e 100644 --- a/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts +++ b/src/renderer/src/runtime/web-session-tabs-sync/terminal-surfaces.ts @@ -16,9 +16,9 @@ import type { ReadyBrowserSurface, ReadyEditorSurface, ReadyTerminalSurface, - TerminalSurface, MirroredAgentTab } from './state' +import type { RuntimeMobileSessionTerminalClientTab } from '../../../../shared/runtime-types' import type { Tab } from '../../../../shared/tab-types' import { structuredAgentSessionTabId } from '../../../../shared/structured-agent-session-projection' @@ -30,7 +30,7 @@ export function isReadyTerminalTab( export function isTerminalSurfaceTab( tab: RuntimeMobileSessionTabsResult['tabs'][number] -): tab is TerminalSurface { +): tab is RuntimeMobileSessionTerminalClientTab { return tab.type === 'terminal' } @@ -158,7 +158,7 @@ export function isMirroredTerminalSurfaceId(tabId: string): boolean { } export function chooseRemoteTerminalLayout( - surfaces: readonly TerminalSurface[], + surfaces: readonly RuntimeMobileSessionTerminalClientTab[], ptyIdsByLeafId: Record, existingLayout?: TerminalLayoutSnapshot, requestedActiveLeafId?: string diff --git a/src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface.ts b/src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface.ts index e54b9329bd4..ab23f43f4b0 100644 --- a/src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface.ts +++ b/src/renderer/src/runtime/web-session-terminal-orphan-recovery-surface.ts @@ -30,7 +30,6 @@ export type TerminalOrphanRecoveryState = WebTerminalOrphanTopologyState & { tabsByWorktree: Record } -export type TerminalSurface = RuntimeMobileSessionTerminalClientTab export type RecoveryDisposition = 'claim' | 'retain' | 'remove' type RecoverySurfaceCoordinates = { @@ -38,7 +37,7 @@ type RecoverySurfaceCoordinates = { leafId: string surfaceKey: string localTab: TerminalTab - incoming?: TerminalSurface + incoming?: RuntimeMobileSessionTerminalClientTab pending: boolean expectedPtyId: string | null locallyActive: boolean @@ -166,7 +165,9 @@ export function prepareTerminalOrphanRecovery( return { candidates, unresolved, observed, retained } } -export function buildRetainedTerminalSurface(surface: AnyRecoverySurface): TerminalSurface { +export function buildRetainedTerminalSurface( + surface: AnyRecoverySurface +): RuntimeMobileSessionTerminalClientTab { const incoming = surface.incoming const localTitle = typeof surface.localTab.title === 'string' ? surface.localTab.title.trim() : '' if (!surface.handle) { @@ -189,7 +190,7 @@ export function buildRetainedTerminalSurface(surface: AnyRecoverySurface): Termi terminal: null } } - const base: TerminalSurface = incoming ?? { + const base: RuntimeMobileSessionTerminalClientTab = incoming ?? { type: 'terminal', id: `${surface.tabId}::${surface.leafId}`, parentTabId: surface.tabId, diff --git a/src/renderer/src/store/slices/browser/browser-cookie-import-actions.ts b/src/renderer/src/store/slices/browser/browser-cookie-import-actions.ts index 99ad8a753e3..5979966bdd0 100644 --- a/src/renderer/src/store/slices/browser/browser-cookie-import-actions.ts +++ b/src/renderer/src/store/slices/browser/browser-cookie-import-actions.ts @@ -1,9 +1,6 @@ import type { BrowserSlice, BrowserSliceGet, BrowserSliceSet } from './browser-slice-contract' import type { BrowserCookieImportResult } from '../../../../../shared/browser-workspace-types' -import type { - BrowserProfileImportFromBrowserResult, - BrowserProfileClearDefaultCookiesResult -} from '../../../../../shared/runtime-types' +import type { BrowserProfileClearDefaultCookiesResult } from '../../../../../shared/runtime-types' import { callRuntimeRpc } from '@/runtime/runtime-rpc-client' import { selectExecutionHostDisplayLabel } from '@/lib/execution-host-display-label' import { @@ -46,7 +43,7 @@ export function createBrowserCookieImportActions( ranOnClient = clientHostResult != null const result = clientHostResult ?? - (await callRuntimeRpc( + (await callRuntimeRpc( { kind: 'environment', environmentId: runtimeEnvironmentId }, 'browser.profileImportFromBrowser', { profileId, browserFamily, browserProfile, supportsPartitionSkippedCookies: true }, diff --git a/src/renderer/src/store/slices/degraded-repo-hydration.test.ts b/src/renderer/src/store/slices/degraded-repo-hydration.test.ts index b8063a48408..fde17abe8c4 100644 --- a/src/renderer/src/store/slices/degraded-repo-hydration.test.ts +++ b/src/renderer/src/store/slices/degraded-repo-hydration.test.ts @@ -1,6 +1,6 @@ import { expect, it, vi } from 'vitest' import type * as AgentStatusModule from '@/lib/agent-status' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' import type { WorkspaceSessionState } from '../../../../shared/workspace-session-state-types' import { getDefaultWorkspaceSession } from '../../../../shared/constants' @@ -25,7 +25,7 @@ const EDITOR_FILE_ID = '/path/degraded/src/App.tsx' const BROWSER_ID = 'browser-degraded' const GROUP_ID = 'group-degraded' -function makeBrowserTab(): BrowserTab { +function makeBrowserTab(): BrowserWorkspace { return { id: BROWSER_ID, worktreeId: WORKTREE_ID, diff --git a/src/renderer/src/store/slices/editor/types/editor-git-slice.ts b/src/renderer/src/store/slices/editor/types/editor-git-slice.ts index a74763c9c6e..acd83ee1b9c 100644 --- a/src/renderer/src/store/slices/editor/types/editor-git-slice.ts +++ b/src/renderer/src/store/slices/editor/types/editor-git-slice.ts @@ -1,4 +1,4 @@ -import type { RemoteOpKind } from '@/components/right-sidebar/source-control-primary-action' +import type { SourceControlRemoteOpKind } from '@/components/right-sidebar/source-control-primary-action' import type { WorkspaceSessionHydrationOptions } from '@/lib/workspace-session-hydration-keys' import type { GitBranchChangeEntry, @@ -43,8 +43,8 @@ export type EditorGitSlice = { isRemoteOperationActive: boolean remoteOperationDepth: number // Why: which remote op the user triggered, so the primary button mirrors its label+spinner; cleared at depth 0. - inFlightRemoteOpKind: RemoteOpKind | null - beginRemoteOperation: (kind?: RemoteOpKind) => void + inFlightRemoteOpKind: SourceControlRemoteOpKind | null + beginRemoteOperation: (kind?: SourceControlRemoteOpKind) => void endRemoteOperation: () => void fetchUpstreamStatus: ( worktreeId: string, diff --git a/src/renderer/src/store/slices/github.ts b/src/renderer/src/store/slices/github.ts index 126c75710af..e1b7efe8ca6 100644 --- a/src/renderer/src/store/slices/github.ts +++ b/src/renderer/src/store/slices/github.ts @@ -1,6 +1,6 @@ import type { StateCreator } from 'zustand' import type { AppState } from '../types' -import type { GitHubSlice as GitHubSliceContract } from '../github/slice-types' +import type { GitHubSlice } from '../github/slice-types' import { ACTIVE_PR_REFRESH_STATUSES, getEffectiveGitHubPRRefreshState, @@ -23,7 +23,6 @@ import { createStaleWorktreeRefreshActions } from '../github/stale-worktree-refr import { createRefreshRoutingActions } from '../github/refresh-routing-actions' import { createRefreshEventActions } from '../github/refresh-event-actions' import { createRefreshSweepActions } from '../github/refresh-sweep-actions' -export type GitHubSlice = GitHubSliceContract export const createGitHubSlice: StateCreator = (set, get) => ({ prCache: {}, diff --git a/src/renderer/src/store/slices/hosted-review-cache-state.ts b/src/renderer/src/store/slices/hosted-review-cache-state.ts index 70b2b4d866b..a74fde7c1ec 100644 --- a/src/renderer/src/store/slices/hosted-review-cache-state.ts +++ b/src/renderer/src/store/slices/hosted-review-cache-state.ts @@ -1,6 +1,5 @@ import type { CreateHostedReviewInput, - CreateStackedHostedReviewInput, HostedReviewCreationEligibility, HostedReviewInfo } from '../../../../shared/hosted-review' @@ -30,7 +29,7 @@ export type HostedReviewFetchOptions = { repoOwnerExecutionHostId?: string } export type CreateHostedReviewStoreInput = CreateHostedReviewInput & { repoId?: string | null } -export type CreateStackedHostedReviewStoreInput = CreateStackedHostedReviewInput & { +export type CreateStackedHostedReviewStoreInput = CreateHostedReviewInput & { repoId?: string | null } diff --git a/src/renderer/src/store/slices/new-markdown.test.ts b/src/renderer/src/store/slices/new-markdown.test.ts index fe592dd7bf9..06fc97300ec 100644 --- a/src/renderer/src/store/slices/new-markdown.test.ts +++ b/src/renderer/src/store/slices/new-markdown.test.ts @@ -2,7 +2,7 @@ import { createStore, type StoreApi } from 'zustand/vanilla' import { describe, expect, it } from 'vitest' import { createEditorSlice } from './editor' import type { AppState } from '../types' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' import type { Tab, TabContentType, TabGroup } from '../../../../shared/tab-types' function createEditorStore(overrides?: Partial): StoreApi { @@ -19,7 +19,7 @@ function createEditorStore(overrides?: Partial): StoreApi { })) as unknown as StoreApi } -function makeBrowserTab(id: string): BrowserTab { +function makeBrowserTab(id: string): BrowserWorkspace { return { id, worktreeId: 'wt-1', diff --git a/src/renderer/src/store/slices/store-session-test-harness.ts b/src/renderer/src/store/slices/store-session-test-harness.ts index 6ef35f6360e..d0ab1937774 100644 --- a/src/renderer/src/store/slices/store-session-test-harness.ts +++ b/src/renderer/src/store/slices/store-session-test-harness.ts @@ -1,5 +1,5 @@ import { vi, type Mock } from 'vitest' -import type { BrowserTab } from '../../../../shared/browser-workspace-types' +import type { BrowserWorkspace } from '../../../../shared/browser-workspace-types' /** Shape shared by the Claude/Codex/OpenCode usage-scanner namespaces. */ type UsageScannerMocks = { @@ -115,8 +115,8 @@ export function createStoreSessionMockApi(): StoreSessionMockApi { } export function makeBrowserTab( - overrides: Partial & { id: string; worktreeId: string; url: string } -): BrowserTab { + overrides: Partial & { id: string; worktreeId: string; url: string } +): BrowserWorkspace { return { title: overrides.url, loading: false, diff --git a/src/renderer/src/store/types.ts b/src/renderer/src/store/types.ts index 491634b253b..797f09cd79b 100644 --- a/src/renderer/src/store/types.ts +++ b/src/renderer/src/store/types.ts @@ -6,7 +6,7 @@ import type { TabsSlice } from './slices/tabs' import type { UISlice } from './slices/ui' import type { SettingsSlice } from './slices/settings' import type { KeybindingsSlice } from './slices/keybindings' -import type { GitHubSlice } from './slices/github' +import type { GitHubSlice } from './github/slice-types' import type { HostedReviewSlice } from './slices/hosted-review' import type { LinearSlice } from './slices/linear' import type { PreflightSlice } from './slices/preflight' diff --git a/src/renderer/src/web/web-runtime-client-export-parity.test.ts b/src/renderer/src/web/web-runtime-client-export-parity.test.ts index 6ba87eaad36..a399992a295 100644 --- a/src/renderer/src/web/web-runtime-client-export-parity.test.ts +++ b/src/renderer/src/web/web-runtime-client-export-parity.test.ts @@ -1,9 +1,10 @@ import { expect, expectTypeOf, it } from 'vitest' import type { WebPairingOffer } from './web-pairing' +import type { WebRuntimeSubscribeOptions } from './web-runtime-subscription-contract' import * as WebClient from './web-runtime-client' it('keeps the paired-web client public export surface exact', () => { - expectTypeOf().toEqualTypeOf() + expectTypeOf().toEqualTypeOf() expectTypeOf().toEqualTypeOf() expectTypeOf>().toEqualTypeOf< [ diff --git a/src/renderer/src/web/web-runtime-client.ts b/src/renderer/src/web/web-runtime-client.ts index 8a3b944f701..d5d0bc28352 100644 --- a/src/renderer/src/web/web-runtime-client.ts +++ b/src/renderer/src/web/web-runtime-client.ts @@ -20,7 +20,7 @@ export type WebRuntimeSubscriptionHandle = { sendBinary: (bytes: Uint8Array) => void } -export type SubscribeOptions = WebRuntimeSubscribeOptions +export type { WebRuntimeSubscribeOptions } const SHARED_CONNECTION_SUBSCRIPTION_METHODS = new Set(['files.watch']) @@ -101,7 +101,7 @@ export class WebRuntimeClient { method: string, params: unknown, callbacks: WebRuntimeSubscriptionCallbacks, - options?: SubscribeOptions + options?: WebRuntimeSubscribeOptions ): Promise { if (SHARED_CONNECTION_SUBSCRIPTION_METHODS.has(method)) { return subscribeWebRuntimeFileWatch({ @@ -166,7 +166,7 @@ export class WebRuntimeClient { method: string, params: unknown, callbacks: WebRuntimeSubscriptionCallbacks, - options?: SubscribeOptions + options?: WebRuntimeSubscribeOptions ): Promise { await this.waitForConnected(options?.timeoutMs) const id = this.nextId() diff --git a/src/shared/agent-status-run-alias-index.test.ts b/src/shared/agent-status-run-alias-index.test.ts index ec1b82a3bd0..20dc2ee9003 100644 --- a/src/shared/agent-status-run-alias-index.test.ts +++ b/src/shared/agent-status-run-alias-index.test.ts @@ -1,3 +1,4 @@ +import type { AgentSessionExecutionLocation } from './agent-session-record' import { describe, expect, it, vi } from 'vitest' import { deserializeAgentStatusProviderAliasKey, @@ -8,9 +9,10 @@ import { type AgentStatusScopedProviderAlias, type AgentStatusRunAliasIndex } from './agent-status-run-alias-index' -import type { AgentStatusExecutionScope } from './agent-status-subject' -function scope(overrides: Partial = {}): AgentStatusExecutionScope { +function scope( + overrides: Partial = {} +): AgentSessionExecutionLocation { return { executionHostId: 'local', wslDistro: null, diff --git a/src/shared/agent-status-run-alias-index.ts b/src/shared/agent-status-run-alias-index.ts index 7f3006f0437..90ecfd70d9f 100644 --- a/src/shared/agent-status-run-alias-index.ts +++ b/src/shared/agent-status-run-alias-index.ts @@ -1,13 +1,11 @@ +import type { AgentSessionExecutionLocation } from './agent-session-record' import { parseAgentStatusProviderAlias, isAgentStatusRunId, type AgentStatusProviderAlias, type AgentStatusRunId } from './agent-status-run' -import { - parseAgentStatusExecutionScope, - type AgentStatusExecutionScope -} from './agent-status-subject' +import { parseAgentStatusExecutionScope } from './agent-status-subject' import { assertJsonTextStructureWithinLimits } from './json-text-structure-limit' import { measureUtf8ByteLength } from './utf8-byte-limits' @@ -21,7 +19,8 @@ const ALIAS_INDEX_JSON_STRUCTURE_LIMITS = { nestingDepth: 3 } as const -export type AgentStatusScopedProviderAlias = AgentStatusExecutionScope & AgentStatusProviderAlias +export type AgentStatusScopedProviderAlias = AgentSessionExecutionLocation & + AgentStatusProviderAlias export type AgentStatusProviderAliasKey = string /** One provider tuple can resolve to multiple concurrently live run ids. */ @@ -31,7 +30,7 @@ type ProviderAliasKeyTuple = readonly [ executionHostId: string, wslDistro: string | null, workspaceId: string, - workspaceKind: AgentStatusExecutionScope['workspaceKind'], + workspaceKind: AgentSessionExecutionLocation['workspaceKind'], provider: AgentStatusProviderAlias['provider'], sessionKeyKind: AgentStatusProviderAlias['sessionKeyKind'], providerId: string diff --git a/src/shared/agent-status-subject.test.ts b/src/shared/agent-status-subject.test.ts index be28dbcff83..6277e9c1223 100644 --- a/src/shared/agent-status-subject.test.ts +++ b/src/shared/agent-status-subject.test.ts @@ -1,3 +1,4 @@ +import type { AgentSessionExecutionLocation } from './agent-session-record' import { describe, expect, it } from 'vitest' import { agentStatusSubjectsEqual, @@ -6,15 +7,16 @@ import { makePtyRunAgentStatusSubject, makeStructuredAgentStatusSubject, parseAgentStatusSubject, - serializeAgentStatusSubject, - type AgentStatusExecutionScope + serializeAgentStatusSubject } from './agent-status-subject' const RUN_ID = 'run_11111111-1111-4111-8111-111111111111' const PANE_KEY = 'tab-1:11111111-1111-4111-8111-111111111111' const SESSION_ID = 'session_11111111-1111-4111-8111-111111111111' -function scope(overrides: Partial = {}): AgentStatusExecutionScope { +function scope( + overrides: Partial = {} +): AgentSessionExecutionLocation { return { executionHostId: 'local', wslDistro: null, diff --git a/src/shared/agent-status-subject.ts b/src/shared/agent-status-subject.ts index 760fa3dfe32..5edad063450 100644 --- a/src/shared/agent-status-subject.ts +++ b/src/shared/agent-status-subject.ts @@ -10,19 +10,17 @@ const SUBJECT_KEY_PREFIX = 'agent-status-subject-v1:' const MAX_SCOPE_PART_LENGTH = 512 const MAX_PANE_KEY_LENGTH = 512 -export type AgentStatusExecutionScope = AgentSessionExecutionLocation - -export type AgentStatusPtyRunSubject = AgentStatusExecutionScope & { +export type AgentStatusPtyRunSubject = AgentSessionExecutionLocation & { kind: 'pty-run' runId: AgentStatusRunId } -export type AgentStatusPtySubject = AgentStatusExecutionScope & { +export type AgentStatusPtySubject = AgentSessionExecutionLocation & { kind: 'pty' paneKey: string } -export type AgentStatusStructuredSessionSubject = AgentStatusExecutionScope & { +export type AgentStatusStructuredSessionSubject = AgentSessionExecutionLocation & { kind: 'structured-session' sessionId: string } @@ -37,7 +35,7 @@ type AgentStatusSubjectKeyTuple = readonly [ executionHostId: string, wslDistro: string | null, workspaceId: string, - workspaceKind: AgentStatusExecutionScope['workspaceKind'], + workspaceKind: AgentSessionExecutionLocation['workspaceKind'], identity: string ] @@ -60,7 +58,9 @@ function isBoundedIdentity(value: unknown, maxLength: number): value is string { ) } -function parseExecutionScope(record: Record): AgentStatusExecutionScope | null { +function parseExecutionScope( + record: Record +): AgentSessionExecutionLocation | null { if (!isBoundedIdentity(record.executionHostId, MAX_SCOPE_PART_LENGTH)) { return null } @@ -89,7 +89,9 @@ function parseExecutionScope(record: Record): AgentStatusExecut } } -export function parseAgentStatusExecutionScope(value: unknown): AgentStatusExecutionScope | null { +export function parseAgentStatusExecutionScope( + value: unknown +): AgentSessionExecutionLocation | null { if ( !isRecord(value) || !hasExactKeys(value, ['executionHostId', 'wslDistro', 'workspaceId', 'workspaceKind']) @@ -241,7 +243,7 @@ export function agentStatusSubjectsEqual( } export function makePtyRunAgentStatusSubject( - scope: AgentStatusExecutionScope, + scope: AgentSessionExecutionLocation, runId: AgentStatusRunId ): AgentStatusPtyRunSubject { const subject = parseAgentStatusSubject({ ...scope, kind: 'pty-run', runId }) @@ -252,7 +254,7 @@ export function makePtyRunAgentStatusSubject( } export function makePtyAgentStatusSubject( - scope: AgentStatusExecutionScope, + scope: AgentSessionExecutionLocation, paneKey: string ): AgentStatusPtySubject { const subject = parseAgentStatusSubject({ ...scope, kind: 'pty', paneKey }) @@ -263,7 +265,7 @@ export function makePtyAgentStatusSubject( } export function makeStructuredAgentStatusSubject( - scope: AgentStatusExecutionScope, + scope: AgentSessionExecutionLocation, sessionId: string ): AgentStatusStructuredSessionSubject { const subject = parseAgentStatusSubject({ ...scope, kind: 'structured-session', sessionId }) diff --git a/src/shared/browser-workspace-types.ts b/src/shared/browser-workspace-types.ts index ecab57c8783..e2b254a4fce 100644 --- a/src/shared/browser-workspace-types.ts +++ b/src/shared/browser-workspace-types.ts @@ -152,8 +152,6 @@ export type BrowserWorkspace = { docLocation?: BrowserPageDocLocation | null } -export type BrowserTab = BrowserWorkspace - export type BrowserSessionProfileScope = 'default' | 'isolated' | 'imported' export type BrowserSessionUserAgentMode = 'clean' | 'native' diff --git a/src/shared/child-process/process-spec.ts b/src/shared/child-process/process-spec.ts index 2acfe82d61d..ab983d7bec9 100644 --- a/src/shared/child-process/process-spec.ts +++ b/src/shared/child-process/process-spec.ts @@ -3,10 +3,6 @@ // functions from run-process, which re-exports everything here. import type { ChildProcess, SpawnOptions as NodeSpawnOptions } from 'node:child_process' -export type ChildProcessHandle = ChildProcess - -export type SpawnedProcess = ChildProcess - /** * The single place Orca starts a child process. * diff --git a/src/shared/child-process/run-process.ts b/src/shared/child-process/run-process.ts index 736b093a56f..0a44db0f0be 100644 --- a/src/shared/child-process/run-process.ts +++ b/src/shared/child-process/run-process.ts @@ -10,13 +10,7 @@ import { forceTerminateProcessTree, signalProcessTree } from './process-tree-ter import { createOutputSink } from './bounded-output-sink' import { createChildTerminationReporter } from './child-termination-reporter' -export type { - ChildProcessHandle, - SpawnedProcess, - ProcessSpec, - ProcessTerminationBarrier, - ProcessResult -} from './process-spec' +export type { ProcessSpec, ProcessTerminationBarrier, ProcessResult } from './process-spec' export { DEFAULT_PROCESS_TIMEOUT_MS, DEFAULT_MAX_OUTPUT_BYTES } from './process-spec' export { resolveSpawn, type ResolvedSpawn } from './spawn-resolution' import type { ProcessSpec, ProcessResult } from './process-spec' diff --git a/src/shared/claimed-agent-pty-owner.ts b/src/shared/claimed-agent-pty-owner.ts index d5449814696..1f343d328a6 100644 --- a/src/shared/claimed-agent-pty-owner.ts +++ b/src/shared/claimed-agent-pty-owner.ts @@ -31,8 +31,6 @@ type ReservedOwner = { promise: Promise } -type LiveOwner = LiveAgentSessionOwner - function cloneClaim(claim: AgentSessionExecutionClaim): AgentSessionExecutionClaim { return cloneAgentSessionClaim(claim) } @@ -41,14 +39,14 @@ function cloneSurface(surface: AgentSessionSurfaceBinding): AgentSessionSurfaceB return cloneAgentSessionSurface(surface) } -function cloneOwner(owner: LiveOwner): LiveOwner { +function cloneOwner(owner: LiveAgentSessionOwner): LiveAgentSessionOwner { return cloneAgentSessionOwner(owner) } export class ClaimedAgentPtyOwnerRegistry { private readonly reserved = new Map() - private readonly live = new Map() - private readonly conflicts = new Map() + private readonly live = new Map() + private readonly conflicts = new Map() private keysByPtyId = new Map>() async ensure(args: { @@ -93,7 +91,7 @@ export class ClaimedAgentPtyOwnerRegistry { throw new Error('agent_session_conflict') } const result = await reserved.promise - return { disposition: 'adopted', owner: cloneOwner(result.owner as LiveOwner) } + return { disposition: 'adopted', owner: cloneOwner(result.owner as LiveAgentSessionOwner) } } this.assertCapacityForNewOwner() @@ -115,10 +113,10 @@ export class ClaimedAgentPtyOwnerRegistry { promise }) - let promotedOwner: LiveOwner | null = null + let promotedOwner: LiveAgentSessionOwner | null = null try { const spawned = await args.spawn({ generation }) - const owner: LiveOwner = spawned.owner + const owner: LiveAgentSessionOwner = spawned.owner ? { claim: cloneClaim(spawned.owner.claim), generation: spawned.owner.generation, @@ -277,7 +275,7 @@ export class ClaimedAgentPtyOwnerRegistry { } return [...keys] .map((key) => this.live.get(key)) - .filter((owner): owner is LiveOwner => owner !== undefined) + .filter((owner): owner is LiveAgentSessionOwner => owner !== undefined) .map(cloneOwner) } @@ -300,8 +298,8 @@ export class ClaimedAgentPtyOwnerRegistry { } private countOwners( - live: ReadonlyMap, - conflicts: ReadonlyMap + live: ReadonlyMap, + conflicts: ReadonlyMap ): number { let count = live.size for (const owners of conflicts.values()) { diff --git a/src/shared/clipboard-text.ts b/src/shared/clipboard-text.ts index c5d641e93d6..bf901eab08a 100644 --- a/src/shared/clipboard-text.ts +++ b/src/shared/clipboard-text.ts @@ -20,12 +20,10 @@ export type WriteClipboardTextOptions = { maxBytes?: number } -export type ClipboardTextByteLengthMeasurement = Utf8ByteLengthMeasurement - export function measureClipboardTextByteLength( text: string, options: { stopAfterBytes?: number } = {} -): ClipboardTextByteLengthMeasurement { +): Utf8ByteLengthMeasurement { return measureUtf8ByteLength(text, options) } @@ -40,7 +38,7 @@ export async function measureClipboardTextByteLengthWithYield( yieldAfterCodeUnits?: number yieldToEventLoop?: () => Promise } = {} -): Promise { +): Promise { const stopAfterBytes = options.stopAfterBytes const yieldAfterCodeUnits = Math.max( 1, diff --git a/src/shared/editor-save-events.ts b/src/shared/editor-save-events.ts index 9b6570d6706..a2387b0fbe3 100644 --- a/src/shared/editor-save-events.ts +++ b/src/shared/editor-save-events.ts @@ -6,5 +6,3 @@ export type EditorSaveDirtyFilesDetail = { resolve: () => void reject: (message: string) => void } - -export type EditorPrepareHotExitDetail = EditorSaveDirtyFilesDetail diff --git a/src/shared/ephemeral-vm-recipe-runner.ts b/src/shared/ephemeral-vm-recipe-runner.ts index 9968d106018..5f197cd7c14 100644 --- a/src/shared/ephemeral-vm-recipe-runner.ts +++ b/src/shared/ephemeral-vm-recipe-runner.ts @@ -82,8 +82,6 @@ export type EphemeralVmRecipeCleanupArgs = { spawnCommand?: typeof spawn } -export type EphemeralVmRecipeLifecycleArgs = EphemeralVmRecipeCleanupArgs - export type EphemeralVmRecipeCleanupResult = { ok: boolean skipped: boolean @@ -195,7 +193,7 @@ export async function runEphemeralVmRecipeCleanup( } export async function runEphemeralVmRecipeSuspend( - args: EphemeralVmRecipeLifecycleArgs + args: EphemeralVmRecipeCleanupArgs ): Promise { validateRepoPath(args.repoPath) if (!args.recipe.suspend) { @@ -231,7 +229,7 @@ export async function runEphemeralVmRecipeSuspend( } export async function runEphemeralVmRecipeResume( - args: EphemeralVmRecipeLifecycleArgs + args: EphemeralVmRecipeCleanupArgs ): Promise { validateRepoPath(args.repoPath) if (!args.recipe.resume) { diff --git a/src/shared/folder-workspace-types.ts b/src/shared/folder-workspace-types.ts index 81e8dcf9f98..dc927793124 100644 --- a/src/shared/folder-workspace-types.ts +++ b/src/shared/folder-workspace-types.ts @@ -43,5 +43,3 @@ export type FolderWorkspace = { updatedAt: number diffComments?: DiffComment[] } - -export type FolderWorkspaceLinkedTask = WorkspaceLinkedItem diff --git a/src/shared/git-status-types.ts b/src/shared/git-status-types.ts index 23b57aebbc1..f4573e57b3c 100644 --- a/src/shared/git-status-types.ts +++ b/src/shared/git-status-types.ts @@ -29,7 +29,7 @@ export type GitSubmoduleStatus = { // // `conflictStatusSource` is never set by the main process. The renderer stamps // 'git' for live u-records and 'session' for Resolved locally state. -export type GitUncommittedEntry = { +export type GitStatusEntry = { path: string status: GitFileStatus area: GitStagingArea @@ -50,8 +50,6 @@ export type GitUncommittedEntry = { removed?: number } -export type GitStatusEntry = GitUncommittedEntry - // `mergeBase(base, HEAD) → working tree`, deduplicated, so committing doesn't move it. // Matches the per-file rows rather than git: binary and >2MB untracked count zero. // `mergeBase` is echoed so a renderer can drop a moved fork point. diff --git a/src/shared/github/pull-request-refresh-types.ts b/src/shared/github/pull-request-refresh-types.ts index 2456b59be60..c23e41a968b 100644 --- a/src/shared/github/pull-request-refresh-types.ts +++ b/src/shared/github/pull-request-refresh-types.ts @@ -17,9 +17,6 @@ export type PRRefreshErrorType = | 'server_error' | 'unknown' -// Backward-compatible name used by outage-copy consumers added on main. -export type PRRefreshUpstreamErrorType = PRRefreshErrorType - export type PRRefreshOutcome = | { kind: 'found'; pr: PRInfo; fetchedAt: number } | { kind: 'no-pr'; fetchedAt: number } diff --git a/src/shared/github/pull-request-types.ts b/src/shared/github/pull-request-types.ts index 6024d702bf3..e55fb35bd05 100644 --- a/src/shared/github/pull-request-types.ts +++ b/src/shared/github/pull-request-types.ts @@ -150,8 +150,7 @@ export type GitHubPRFileContents = { modifiedTooLarge?: boolean } -// Why: declared here as a shared shape so IPC return envelopes and renderer -// slices can reference the same structural type without importing from main. -// Aliased as `OwnerRepo` in `src/main/github/gh-utils.ts` so main call sites -// can continue using the short local name. -export type GitHubOwnerRepo = GitHubRepositoryIdentity +// Why: `GitHubOwnerRepo` is the long-standing name for this shape across main, +// renderer, shared, and mobile call sites; both names stay exported so neither +// side has to import the other's spelling. +export type { GitHubRepositoryIdentity as GitHubOwnerRepo } diff --git a/src/shared/hosted-review.ts b/src/shared/hosted-review.ts index 719c6d27fa5..4e9b49dafdf 100644 --- a/src/shared/hosted-review.ts +++ b/src/shared/hosted-review.ts @@ -97,9 +97,7 @@ export type CreateHostedReviewArgs = CreateHostedReviewInput & { connectionId?: string | null } -export type CreateStackedHostedReviewInput = CreateHostedReviewInput - -export type CreateStackedHostedReviewArgs = CreateStackedHostedReviewInput & { +export type CreateStackedHostedReviewArgs = CreateHostedReviewInput & { repoPath: string repoId?: string connectionId?: string | null diff --git a/src/shared/new-workspace/workspace-source.ts b/src/shared/new-workspace/workspace-source.ts index 42af0804915..8cfd4aab4d3 100644 --- a/src/shared/new-workspace/workspace-source.ts +++ b/src/shared/new-workspace/workspace-source.ts @@ -1,5 +1,5 @@ import { getLinearOrganizationUrlKeyFromIssueUrl } from '../linear/links' -import type { FolderWorkspaceLinkedTask } from '../folder-workspace-types' +import type { WorkspaceLinkedItem } from '../worktree/types' import type { JiraIssue } from '../jira-types' import type { LinearIssue } from '../linear/issue-types' import { @@ -9,9 +9,9 @@ import { } from '../workspace-name' import { isWorkItemLookupText } from './work-item-lookup-text' -export type WorkspaceSourceProvider = FolderWorkspaceLinkedTask['provider'] +export type WorkspaceSourceProvider = WorkspaceLinkedItem['provider'] -export type WorkspaceSourceLinkedItem = FolderWorkspaceLinkedTask & { +export type WorkspaceSourceLinkedItem = WorkspaceLinkedItem & { linearWorkspaceId?: string linearOrganizationUrlKey?: string linearBranchName?: string diff --git a/src/shared/renderer-restart-preparation.ts b/src/shared/renderer-restart-preparation.ts index f7a0e8fda3f..392fcdde152 100644 --- a/src/shared/renderer-restart-preparation.ts +++ b/src/shared/renderer-restart-preparation.ts @@ -1,6 +1,6 @@ import { ORCA_EDITOR_PREPARE_HOT_EXIT_EVENT, - type EditorPrepareHotExitDetail + type EditorSaveDirtyFilesDetail } from './editor-save-events' import { consumeShutdownCheckpointFailureReason, @@ -20,7 +20,7 @@ function requestEditorHotExitBackup(eventTarget: EventTarget): Promise { return new Promise((resolve, reject) => { let claimed = false eventTarget.dispatchEvent( - new CustomEvent(ORCA_EDITOR_PREPARE_HOT_EXIT_EVENT, { + new CustomEvent(ORCA_EDITOR_PREPARE_HOT_EXIT_EVENT, { detail: { claim: () => { claimed = true diff --git a/src/shared/runtime-browser-contracts.ts b/src/shared/runtime-browser-contracts.ts index a259e24c48c..a41ef71d144 100644 --- a/src/shared/runtime-browser-contracts.ts +++ b/src/shared/runtime-browser-contracts.ts @@ -1,6 +1,5 @@ import type { BrowserCertificateFailure, - BrowserCookieImportResult, BrowserLoadError, BrowserSessionProfile, BrowserSessionProfileSource @@ -101,7 +100,6 @@ export type BrowserDetectedInfo = { } export type BrowserDetectProfilesResult = { browsers: BrowserDetectedInfo[] } -export type BrowserProfileImportFromBrowserResult = BrowserCookieImportResult export type BrowserProfileClearDefaultCookiesResult = { cleared: boolean } export type BrowserHoverResult = { hovered: string } export type BrowserDragResult = { dragged: { from: string; to: string } } diff --git a/src/shared/runtime-client-export-parity.test.ts b/src/shared/runtime-client-export-parity.test.ts index 2b86d2e6e99..4d628437f65 100644 --- a/src/shared/runtime-client-export-parity.test.ts +++ b/src/shared/runtime-client-export-parity.test.ts @@ -39,7 +39,6 @@ type RuntimeTypeInventory = [ Runtime.BrowserProfileClearDefaultCookiesResult, Runtime.BrowserProfileCreateResult, Runtime.BrowserProfileDeleteResult, - Runtime.BrowserProfileImportFromBrowserResult, Runtime.BrowserProfileListResult, Runtime.BrowserReloadResult, Runtime.BrowserScreencastDialogClosedResult, @@ -99,6 +98,7 @@ type RuntimeTypeInventory = [ Runtime.RuntimeGitCheckoutResult, Runtime.RuntimeGitLocalBranches, Runtime.RuntimeGraphStatus, + Runtime.RuntimeListingHostScope, Runtime.RuntimeMarkdownReadTabResult, Runtime.RuntimeMarkdownSaveTabResult, Runtime.RuntimeMobileSessionBrowserTab, @@ -139,7 +139,6 @@ type RuntimeTypeInventory = [ Runtime.RuntimeTerminalFocus, Runtime.RuntimeTerminalInteractiveWait, Runtime.RuntimeTerminalInteractiveWaitSource, - Runtime.RuntimeTerminalListHostScope, Runtime.RuntimeTerminalListResult, Runtime.RuntimeTerminalOrphanAdoptionClaim, Runtime.RuntimeTerminalOrphanAdoptionRequest, diff --git a/src/shared/runtime-session-contracts.ts b/src/shared/runtime-session-contracts.ts index 99b4fbe4d6f..45d29ae2771 100644 --- a/src/shared/runtime-session-contracts.ts +++ b/src/shared/runtime-session-contracts.ts @@ -25,12 +25,23 @@ export const HEADLESS_RUNTIME_WINDOW_ID = 0 export type DeviceScope = 'mobile' | 'runtime' +// Why: presence-based driver state for the mobile-presence lock. Exactly one +// driver per PTY at any moment. See docs/mobile-presence-lock.md. +// - `idle`: no mobile subscribers; desktop input flows freely +// - `desktop`: at least one mobile client subscribed but desktop reclaimed +// (or all mobile clients are passive `desktop`-mode watchers); desktop +// input flows freely +// - `mobile{clientId}`: a mobile client is the active driver; desktop +// input/resize are dropped server-side and the lock banner is mounted. +// `clientId` is the most recent mobile actor for this PTY. export type RuntimeTerminalDriverState = | { kind: 'idle' } | { kind: 'desktop' } | { kind: 'mobile'; clientId: string } -export type RuntimeBrowserDriverState = RuntimeTerminalDriverState +// Why: browser pages carry the same idle/desktop/mobile driver states as terminals, and both +// names are part of the enumerated runtime client export surface (runtime-client-export-parity). +export type { RuntimeTerminalDriverState as RuntimeBrowserDriverState } export const BROWSER_UNAVAILABLE_ERROR_CODE = 'browser_unavailable' as const diff --git a/src/shared/runtime-terminal-contracts.ts b/src/shared/runtime-terminal-contracts.ts index ad392a2b9a0..302c3ba8525 100644 --- a/src/shared/runtime-terminal-contracts.ts +++ b/src/shared/runtime-terminal-contracts.ts @@ -84,9 +84,6 @@ export type RuntimeTerminalVisualLayout = { root: RuntimeTerminalVisualLayoutNode } -/** The shared listing-scope shape, kept under its incumbent name for existing consumers. */ -export type RuntimeTerminalListHostScope = RuntimeListingHostScope - export type RuntimeTerminalListResult = { terminals: RuntimeTerminalSummary[] visualLayouts?: RuntimeTerminalVisualLayout[] @@ -94,7 +91,7 @@ export type RuntimeTerminalListResult = { totalCount: number truncated: boolean /** Absent from hosts that predate the field; treat that scope as unverifiable. */ - hostScope?: RuntimeTerminalListHostScope + hostScope?: RuntimeListingHostScope } export type RuntimeTerminalOrphanAdoptionClaim = { diff --git a/src/shared/runtime-types.ts b/src/shared/runtime-types.ts index b236308438d..a01967d691c 100644 --- a/src/shared/runtime-types.ts +++ b/src/shared/runtime-types.ts @@ -51,7 +51,6 @@ export type { BrowserProfileClearDefaultCookiesResult, BrowserProfileCreateResult, BrowserProfileDeleteResult, - BrowserProfileImportFromBrowserResult, BrowserProfileListResult, BrowserReloadResult, BrowserScreencastDialogClosedResult, @@ -151,7 +150,6 @@ export type { RuntimeTerminalFocus, RuntimeTerminalInteractiveWait, RuntimeTerminalInteractiveWaitSource, - RuntimeTerminalListHostScope, RuntimeTerminalListResult, RuntimeTerminalOrphanAdoptionClaim, RuntimeTerminalOrphanAdoptionRequest, @@ -182,6 +180,7 @@ export type { RuntimeWorktreeTerminalCloseResult, RuntimeWorktreeTerminalSleepResult } from './runtime-terminal-contracts' +export type { RuntimeListingHostScope } from './runtime-listing-host-scope' export type { RuntimeGitCheckoutResult, RuntimeGitLocalBranches, diff --git a/src/shared/skill-bundle-install-contract.ts b/src/shared/skill-bundle-install-contract.ts index 2269c3e5291..d8dad63662e 100644 --- a/src/shared/skill-bundle-install-contract.ts +++ b/src/shared/skill-bundle-install-contract.ts @@ -1,6 +1,6 @@ import { z } from 'zod' import { SkillInstallDestinationSchema } from './skill-install-contract' -import { SkillInstallFailureSchema, type SkillInstallFailure } from './skill-install-failure' +import { SkillInstallFailureSchema } from './skill-install-failure' const DIGEST_PATTERN = /^[a-f0-9]{64}$/ const ID_SCHEMA = z.string().regex(/^[A-Za-z0-9_-]{1,128}$/) @@ -150,7 +150,6 @@ export type SkillBundleInstallPreview = z.infer export type SkillBundleSkillResult = SkillBundleInstallResult['skills'][number] export type SkillBundlePlacementResult = SkillBundleSkillResult['placements'][number] -export type SkillBundleInstallFailure = SkillInstallFailure export const SkillBundleInstallProgressSchema = z .object({ diff --git a/src/shared/source-control-ai-types.ts b/src/shared/source-control-ai-types.ts index 888f80d38a0..783786e74ca 100644 --- a/src/shared/source-control-ai-types.ts +++ b/src/shared/source-control-ai-types.ts @@ -7,7 +7,9 @@ import type { SourceControlTextActionId } from './source-control-ai-actions' -export type SourceControlAiOperation = SourceControlTextActionId +// Why: settings and generation call sites spell this "operation"; the action registry spells the +// same three ids "text action". Both names stay exported rather than forcing one vocabulary. +export type { SourceControlTextActionId as SourceControlAiOperation } export type SourceControlAiModelChoice = { selectedModelByAgent?: Partial> @@ -34,8 +36,8 @@ export type SourceControlAiSettings = { > selectedThinkingByModel: Record customAgentCommand: string - instructionsByOperation: Partial> - modelOverridesByOperation?: Partial> + instructionsByOperation: Partial> + modelOverridesByOperation?: Partial> prCreationDefaults?: SourceControlAiPrCreationDefaults /** @deprecated use actions instead. Kept for automatic migration and rollback compatibility. */ launchActionDefaults?: SourceControlAiActionDefaults @@ -48,8 +50,8 @@ export type SourceControlAiSettingsPatch = export type RepoSourceControlAiOverrides = { enabled?: boolean customAgentCommand?: string - modelOverridesByOperation?: Partial> - instructionsByOperation?: Partial> + modelOverridesByOperation?: Partial> + instructionsByOperation?: Partial> actionOverrides?: Partial< Record< SourceControlActionId, @@ -77,8 +79,8 @@ export type CompleteSourceControlActionRecipe = { export type WritableRepoSourceControlAiOverrides = { enabled?: boolean customAgentCommand?: string - modelOverridesByOperation?: Partial> - instructionsByOperation?: Partial> + modelOverridesByOperation?: Partial> + instructionsByOperation?: Partial> actionOverrides?: Partial> prCreationDefaults?: SourceControlAiPrCreationDefaults } diff --git a/src/shared/window-shortcut-policy.ts b/src/shared/window-shortcut-policy.ts index b63a9ae0923..79bd2a63b3a 100644 --- a/src/shared/window-shortcut-policy.ts +++ b/src/shared/window-shortcut-policy.ts @@ -48,8 +48,6 @@ export type WindowShortcutAction = | { type: 'worktreeHistoryNavigate'; direction: 'back' | 'forward' } | { type: 'dictationKeyDown' } -type WindowShortcutResolveOptions = KeybindingMatchOptions - function platformPrimaryModifier( input: Pick, platform: NodeJS.Platform @@ -68,7 +66,7 @@ export function matchesRecentTabSwitcherChord( input: WindowShortcutInput, platform: NodeJS.Platform, keybindings?: KeybindingOverrides, - options: WindowShortcutResolveOptions = {} + options: KeybindingMatchOptions = {} ): boolean { const control = Boolean(input.control ?? input.ctrlKey) const meta = Boolean(input.meta ?? input.metaKey) @@ -129,7 +127,7 @@ function actionMatches( input: WindowShortcutInput, platform: NodeJS.Platform, keybindings: KeybindingOverrides | undefined, - options: WindowShortcutResolveOptions + options: KeybindingMatchOptions ): boolean { return keybindingMatchesAction(actionId, input, platform, keybindings, options) } @@ -170,7 +168,7 @@ export function resolveWindowShortcutAction( input: WindowShortcutInput, platform: NodeJS.Platform, keybindings?: KeybindingOverrides, - options: WindowShortcutResolveOptions = {} + options: KeybindingMatchOptions = {} ): WindowShortcutAction | null { if (actionMatches('worktree.history.back', input, platform, keybindings, options)) { return {