diff --git a/.gitattributes b/.gitattributes
index 8f4f884295d6..736d59473f61 100644
--- a/.gitattributes
+++ b/.gitattributes
@@ -4,6 +4,7 @@
/config/scripts/**/*.mjs text eol=lf
/skill-guides/*.md text eol=lf
/skill-stubs/*.md text eol=lf
+/skill-stubs/_shared/*.md text eol=lf
/skills/*/SKILL.md text eol=lf
/src/cli/bundled-skill-guides.ts text eol=lf
# Bundled plugin trees are byte-hashed; CRLF checkout would break the pinned hash.
diff --git a/.github/workflows/cloud-deploy-relay-production-director.yml b/.github/workflows/cloud-deploy-relay-production-director.yml
index 97abe2d227b0..4489d67b8456 100644
--- a/.github/workflows/cloud-deploy-relay-production-director.yml
+++ b/.github/workflows/cloud-deploy-relay-production-director.yml
@@ -4,7 +4,7 @@ on:
workflow_dispatch:
inputs:
image-digest:
- description: "Immutable relay image digest (sha256: plus 64 lowercase hex characters)"
+ description: 'Immutable relay image digest (sha256: plus 64 lowercase hex characters)'
required: true
type: string
regional-placement-mode:
diff --git a/.github/workflows/cloud-operate-relay-production-rehome-job.yml b/.github/workflows/cloud-operate-relay-production-rehome-job.yml
index fdb1aca45e06..a34552b898fe 100644
--- a/.github/workflows/cloud-operate-relay-production-rehome-job.yml
+++ b/.github/workflows/cloud-operate-relay-production-rehome-job.yml
@@ -14,6 +14,7 @@ on:
not-before: { required: true, type: string }
rate-per-minute: { required: true, type: string }
preference-max-age-ms: { required: true, type: string }
+ host-cooldown-ms: { required: true, type: string }
drain-grace-ms: { required: true, type: string }
confirmation: { required: true, type: string }
monitor-run-id: { required: true, type: string }
@@ -54,6 +55,7 @@ jobs:
NOT_BEFORE: ${{ inputs.not-before }}
RATE_PER_MINUTE: ${{ inputs.rate-per-minute }}
PREFERENCE_MAX_AGE_MS: ${{ inputs.preference-max-age-ms }}
+ HOST_COOLDOWN_MS: ${{ inputs.host-cooldown-ms }}
DRAIN_GRACE_MS: ${{ inputs.drain-grace-ms }}
CONFIRMATION: ${{ inputs.confirmation }}
MONITOR_RUN_ID: ${{ inputs.monitor-run-id }}
@@ -128,6 +130,7 @@ jobs:
--expected-control-generation "${EXPECTED_CONTROL_GENERATION}" \
--not-before "${NOT_BEFORE}" --rate-per-minute "${RATE_PER_MINUTE}" \
--preference-max-age-ms "${PREFERENCE_MAX_AGE_MS}" \
+ --host-cooldown-ms "${HOST_COOLDOWN_MS}" \
--drain-grace-ms "${DRAIN_GRACE_MS}" --confirmation "${CONFIRMATION}" \
| tee "${RUNNER_TEMP}/relay-rehome-control.json"
@@ -299,6 +302,7 @@ jobs:
--expected-control-generation "${EXPECTED_CONTROL_GENERATION}" \
--not-before "${NOT_BEFORE}" --rate-per-minute "${RATE_PER_MINUTE}" \
--preference-max-age-ms "${PREFERENCE_MAX_AGE_MS}" \
+ --host-cooldown-ms "${HOST_COOLDOWN_MS}" \
--drain-grace-ms "${DRAIN_GRACE_MS}" --confirmation "${CONFIRMATION}" \
| tee "${RUNNER_TEMP}/relay-rehome-control.json"
diff --git a/.github/workflows/cloud-operate-relay-production-rehome.yml b/.github/workflows/cloud-operate-relay-production-rehome.yml
index 40bf5ebbd4f5..0615b197c11b 100644
--- a/.github/workflows/cloud-operate-relay-production-rehome.yml
+++ b/.github/workflows/cloud-operate-relay-production-rehome.yml
@@ -52,6 +52,11 @@ on:
required: true
default: '86400000'
type: string
+ host-cooldown-ms:
+ description: Minimum gap between two rehomes of the same host
+ required: true
+ default: '604800000'
+ type: string
drain-grace-ms:
description: Per-host source drain grace
required: true
@@ -99,6 +104,7 @@ jobs:
not-before: ${{ inputs.not-before }}
rate-per-minute: ${{ inputs.rate-per-minute }}
preference-max-age-ms: ${{ inputs.preference-max-age-ms }}
+ host-cooldown-ms: ${{ inputs.host-cooldown-ms }}
drain-grace-ms: ${{ inputs.drain-grace-ms }}
confirmation: ${{ inputs.confirmation }}
monitor-run-id: ${{ inputs.monitor-run-id }}
diff --git a/.github/workflows/cloud-push-deploy.yml b/.github/workflows/cloud-push-deploy.yml
new file mode 100644
index 000000000000..ea19a589bb25
--- /dev/null
+++ b/.github/workflows/cloud-push-deploy.yml
@@ -0,0 +1,364 @@
+name: Deploy Push Gateway Production
+
+on:
+ workflow_dispatch:
+ inputs:
+ source_sha:
+ description: Full reviewed commit SHA to build (feature may remain unmerged)
+ required: true
+ type: string
+ confirmation:
+ description: Enter DEPLOY_PUSH_GATEWAY to shift production traffic
+ required: true
+ type: string
+
+permissions:
+ contents: read
+ id-token: write
+
+# The gateway applies its own schema at startup against the shared Cloud SQL instance, so a
+# deploy is a connection-budget rollout and belongs in the same serialized group as the relay.
+concurrency:
+ group: production-cloud-sql-rollout
+ cancel-in-progress: false
+
+defaults:
+ run:
+ working-directory: cloud
+
+jobs:
+ deploy:
+ if: >-
+ ${{ vars.ORCA_CLOUD_OPERATIONS_ENABLED == 'true' &&
+ github.ref == 'refs/heads/main' }}
+ runs-on: blacksmith-2vcpu-ubuntu-2204
+ environment: production
+ env:
+ GCP_PROJECT_ID: onorca-cloud
+ GCP_REGION: ${{ vars.PRODUCTION_GCP_REGION }}
+ SERVICE_NAME: orca-cloud-push
+ REPOSITORY_ID: orca-cloud
+ IMAGE_NAME: push
+ PUSH_ORIGIN: https://push.onorca.dev
+ PUSH_RUNTIME_SERVICE_ACCOUNT: orca-cloud-push@onorca-cloud.iam.gserviceaccount.com
+ # Scaling the serving revision must already hold, matching push_min_instances and
+ # push_max_instances. Terraform owns both, and the candidate inherits them from the
+ # service, so this deploy never passes a scaling flag: doing so would write a
+ # Terraform-owned field that `lifecycle.ignore_changes` does not cover, and a later
+ # `push_max_instances` raise would then be reverted by every deploy. These two values
+ # are the expected shape, asserted before the candidate is created and again on the
+ # candidate itself, so a deploy that would change the gateway's Cloud SQL draw fails.
+ PUSH_MIN_INSTANCES: 1
+ PUSH_MAX_INSTANCES: 2
+ CONFIRMATION: ${{ inputs.confirmation }}
+ SOURCE_SHA: ${{ inputs.source_sha }}
+ steps:
+ - uses: actions/checkout@v4
+
+ - name: Require the explicit deploy confirmation
+ shell: bash
+ run: |
+ set -euo pipefail
+ test "${CONFIRMATION}" = DEPLOY_PUSH_GATEWAY
+ [[ "${SOURCE_SHA}" =~ ^[a-f0-9]{40}$ ]]
+
+ # Keep the workflow and rollout lease on main; only the Docker build uses candidate code.
+ - name: Fetch the immutable gateway source
+ shell: bash
+ run: |
+ set -euo pipefail
+ git fetch --no-tags origin "${SOURCE_SHA}"
+ test "$(git rev-parse FETCH_HEAD)" = "${SOURCE_SHA}"
+ mkdir -p "${RUNNER_TEMP}/push-source"
+ git -C "${GITHUB_WORKSPACE}" archive "${SOURCE_SHA}" cloud \
+ | tar -x -C "${RUNNER_TEMP}/push-source"
+
+ - uses: google-github-actions/auth@v2
+ with:
+ workload_identity_provider: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_WORKLOAD_IDENTITY_PROVIDER }}
+ service_account: ${{ vars.PRODUCTION_GCP_RELAY_DEPLOY_SERVICE_ACCOUNT }}
+
+ - uses: google-github-actions/setup-gcloud@v2
+
+ - uses: docker/setup-buildx-action@v3
+
+ - name: Configure Docker auth
+ run: gcloud auth configure-docker "${GCP_REGION}-docker.pkg.dev" --quiet
+
+ # Why: the build runs before the lease. Artifact Registry is not the Cloud SQL instance,
+ # and a multi-minute image build inside the lease blocks every relay deploy and rehome for
+ # its duration. The lease below covers exactly the connection-budget window: deploy, probe,
+ # shift.
+ - name: Build and publish the immutable gateway image
+ shell: bash
+ run: |
+ set -euo pipefail
+ image_tag="${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${REPOSITORY_ID}/${IMAGE_NAME}:sha-${SOURCE_SHA}"
+ docker build -f "${RUNNER_TEMP}/push-source/cloud/apps/push/Dockerfile" \
+ -t "${image_tag}" "${RUNNER_TEMP}/push-source/cloud"
+ docker push "${image_tag}"
+ digest="$(gcloud artifacts docker images describe "${image_tag}" \
+ --format='value(image_summary.digest)')"
+ [[ "${digest}" =~ ^sha256:[a-f0-9]{64}$ ]]
+ echo "IMAGE=${GCP_REGION}-docker.pkg.dev/${GCP_PROJECT_ID}/${REPOSITORY_ID}/${IMAGE_NAME}@${digest}" \
+ >> "${GITHUB_ENV}"
+ echo "IMAGE_DIGEST=${digest}" >> "${GITHUB_ENV}"
+
+ # Held across the deploy, not just a separate schema step: the gateway opens its pool and
+ # applies its schema while the new revision starts, so the revision is the schema step.
+ - uses: ./.github/actions/cloud-sql-rollout-lease
+ with:
+ bucket: onorca-cloud-terraform-state
+ object: terraform/state/cloud-sql-rollout/production.lock
+
+ # Why: the candidate inherits the serving revision's scaling. A serving revision that has
+ # drifted below the floor would hand the candidate a cold start on every notification, and
+ # one that has drifted above the ceiling would hand it a larger Cloud SQL draw than the
+ # rollout lease was taken for. Refuse to inherit either rather than latch it.
+ - name: Record the serving revision and require its Terraform-owned scaling
+ shell: bash
+ run: |
+ set -euo pipefail
+ serving="$(gcloud run services describe "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" --format=json \
+ | jq -r '[.status.traffic[] | select((.percent // 0) > 0)]
+ | if length == 1 and .[0].percent == 100 then .[0].revisionName else empty end')"
+ test -n "${serving}"
+ floor="$(gcloud run revisions describe "${serving}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" \
+ --format="value(metadata.annotations['autoscaling.knative.dev/minScale'])")"
+ if [[ "${floor:-0}" -lt "${PUSH_MIN_INSTANCES}" ]]; then
+ echo "serving revision ${serving} holds ${floor:-0} minimum instances," \
+ "below ${PUSH_MIN_INSTANCES}; deploying would inherit and latch it." >&2
+ echo "Restore the floor first: gcloud run services update ${SERVICE_NAME}" \
+ "--region ${GCP_REGION} --min-instances=${PUSH_MIN_INSTANCES}" >&2
+ exit 1
+ fi
+ ceiling="$(gcloud run revisions describe "${serving}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" \
+ --format="value(metadata.annotations['autoscaling.knative.dev/maxScale'])")"
+ test "${ceiling}" = "${PUSH_MAX_INSTANCES}"
+ echo "serving revision ${serving} holds ${floor} minimum and ${ceiling} maximum instances"
+ echo "ROLLBACK_REVISION=${serving}" >> "${GITHUB_ENV}"
+
+ # No traffic and a per-revision tag: the candidate boots, applies schema, and is probed on
+ # its own URL while every phone and desktop still reaches the previous revision.
+ - name: Deploy the candidate revision with no traffic
+ shell: bash
+ run: |
+ set -euo pipefail
+ tag="c${GITHUB_RUN_ID}-${GITHUB_RUN_ATTEMPT}"
+ echo "CANDIDATE_TAG=${tag}" >> "${GITHUB_ENV}"
+ echo "CANDIDATE_REVISION=${SERVICE_NAME}-${tag}" >> "${GITHUB_ENV}"
+ gcloud run deploy "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --image "${IMAGE}" \
+ --tag "${tag}" \
+ --revision-suffix "${tag}" \
+ --no-traffic \
+ --quiet
+ candidate="$(gcloud run services describe "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" --format=json \
+ | jq -er --arg tag "${tag}" \
+ '[.status.traffic[] | select(.tag == $tag)]
+ | if length == 1 then .[0] else error("tagged candidate is not unique") end')"
+ test "$(jq -r '.revisionName' <<< "${candidate}")" = "${SERVICE_NAME}-${tag}"
+ echo "CANDIDATE_URL=$(jq -r '.url' <<< "${candidate}")" >> "${GITHUB_ENV}"
+
+ # A tagged revision is directly addressable and sits outside the service-wide cap, so the
+ # candidate and the serving revision each draw up to the ceiling during the probe window.
+ # The lease is taken for exactly that doubling; a candidate that inherited a wider ceiling
+ # would exceed it, so the inherited scaling is asserted here too.
+ - name: Require the candidate to serve the exact image and inherited scaling
+ shell: bash
+ run: |
+ set -euo pipefail
+ served="$(gcloud run revisions describe "${CANDIDATE_REVISION}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" \
+ --format='value(spec.containers[0].image)')"
+ test "${served}" = "${IMAGE}"
+ test "${CANDIDATE_REVISION}" != "${ROLLBACK_REVISION}"
+ candidate_ceiling="$(gcloud run revisions describe "${CANDIDATE_REVISION}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" \
+ --format="value(metadata.annotations['autoscaling.knative.dev/maxScale'])")"
+ test "${candidate_ceiling}" = "${PUSH_MAX_INSTANCES}"
+
+ - name: Probe the candidate readiness endpoint
+ shell: bash
+ run: |
+ set -euo pipefail
+ [[ "${CANDIDATE_URL}" =~ ^https://[^/]+$ ]]
+ for attempt in $(seq 1 30); do
+ code="$(curl -sS -o "${RUNNER_TEMP}/push-ready.json" -w '%{http_code}' \
+ --max-time 10 "${CANDIDATE_URL}/ready" || true)"
+ if test "${code}" = 200; then
+ jq -e . < "${RUNNER_TEMP}/push-ready.json" > /dev/null
+ curl --fail --silent --show-error --max-time 10 "${CANDIDATE_URL}/health" \
+ | jq -e '.ok == true and .deliveryProtocol == 2' > /dev/null
+ echo "candidate ${CANDIDATE_REVISION} is ready after ${attempt} attempt(s)"
+ exit 0
+ fi
+ echo "attempt ${attempt}: /ready returned ${code}"
+ sleep 5
+ done
+ echo "candidate ${CANDIDATE_REVISION} never reported ready" >&2
+ exit 1
+
+ # Why: a gateway that boots and answers /ready can still be unable to send. This proves the
+ # runtime account's FCM grant end to end without delivering anything: validate_only stops
+ # Google before any push, and the deliberately invalid token means a healthy credential
+ # answers INVALID_ARGUMENT. PERMISSION_DENIED is the failure this step exists to catch.
+ #
+ # Only the four verdicts below are conclusive. A 429, a 5xx, or a transport failure says
+ # nothing about the credential, so it is retried rather than treated as either answer; a
+ # denied credential still fails on the first attempt, without burning the retries.
+ - name: Prove the runtime identity can reach FCM
+ shell: bash
+ run: |
+ set -euo pipefail
+ token="$(gcloud auth print-access-token \
+ --impersonate-service-account "${PUSH_RUNTIME_SERVICE_ACCOUNT}")"
+ test -n "${token}"
+ echo "::add-mask::${token}"
+ body='{"validate_only":true,"message":{"token":"orca-push-deploy-probe-invalid-token","notification":{"title":"Orca","body":"deploy probe"}}}'
+ for attempt in $(seq 1 5); do
+ code="$(curl -sS -o "${RUNNER_TEMP}/push-fcm.json" -w '%{http_code}' --max-time 20 \
+ -X POST "https://fcm.googleapis.com/v1/projects/${GCP_PROJECT_ID}/messages:send" \
+ -H "Authorization: Bearer ${token}" \
+ -H 'Content-Type: application/json' \
+ --data "${body}" || true)"
+ status="$(jq -r '.error.status // empty' < "${RUNNER_TEMP}/push-fcm.json" || true)"
+ echo "attempt ${attempt}: FCM validate-only send returned HTTP ${code} status ${status:-OK}"
+ if test "${status}" = PERMISSION_DENIED || test "${status}" = INVALID_ARGUMENT ||
+ test "${code}" = 401 || test "${code}" = 403; then
+ break
+ fi
+ sleep 5
+ done
+ if test "${status}" = PERMISSION_DENIED || test "${code}" = 401 || test "${code}" = 403; then
+ echo "the push runtime identity cannot send through FCM" >&2
+ exit 1
+ fi
+ test "${status}" = INVALID_ARGUMENT
+
+ - name: Shift all traffic to the verified candidate
+ shell: bash
+ run: |
+ set -euo pipefail
+ echo "TRAFFIC_SHIFT_ATTEMPTED=true" >> "${GITHUB_ENV}"
+ gcloud run services update-traffic "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --to-revisions "${CANDIDATE_REVISION}=100" \
+ --quiet
+ serving="$(gcloud run services describe "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" --format=json \
+ | jq -r '[.status.traffic[] | select((.percent // 0) > 0)]
+ | if length == 1 and .[0].percent == 100 then .[0].revisionName else empty end')"
+ test "${serving}" = "${CANDIDATE_REVISION}"
+ echo "TRAFFIC_SHIFTED=true" >> "${GITHUB_ENV}"
+
+ # Why: the summary is written before the origin check, not after it. Once traffic has
+ # moved, the rollback target is the single thing an operator needs, and a summary that only
+ # appeared on success would be missing in exactly the run that needs it.
+ - name: Publish the rollout summary
+ if: ${{ always() && env.CANDIDATE_REVISION != '' && env.ROLLBACK_REVISION != '' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ {
+ echo '### Push gateway rollout'
+ echo
+ echo "Source: ${SOURCE_SHA}"
+ echo
+ echo "Revision: \`${CANDIDATE_REVISION}\`"
+ echo
+ echo "Image: \`${IMAGE_DIGEST}\`"
+ echo
+ echo "Rollback: \`gcloud run services update-traffic ${SERVICE_NAME}" \
+ "--region ${GCP_REGION} --to-revisions ${ROLLBACK_REVISION}=100\`"
+ } >> "${GITHUB_STEP_SUMMARY}"
+
+ - name: Verify the public origin after the shift
+ shell: bash
+ run: |
+ set -euo pipefail
+ for attempt in $(seq 1 30); do
+ code="$(curl -sS -o /dev/null -w '%{http_code}' --max-time 10 \
+ "${PUSH_ORIGIN}/ready" || true)"
+ if test "${code}" = 200; then
+ curl --fail --silent --show-error --max-time 10 "${PUSH_ORIGIN}/health" \
+ | jq -e '.ok == true and .deliveryProtocol == 2' > /dev/null
+ echo "${PUSH_ORIGIN} is ready after ${attempt} attempt(s)"
+ exit 0
+ fi
+ echo "attempt ${attempt}: ${PUSH_ORIGIN}/ready returned ${code}"
+ sleep 5
+ done
+ echo "${PUSH_ORIGIN} never reported ready after the shift" >&2
+ exit 1
+
+ # Why: everything after the shift runs with production on the candidate. A failure there
+ # is not a failure to deploy, it is a live gateway that has to go back, so the traffic move
+ # is undone here rather than left to whoever reads the run.
+ - name: Roll traffic back to the previous revision
+ if: ${{ (failure() || cancelled()) && env.TRAFFIC_SHIFT_ATTEMPTED == 'true' }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ test -n "${ROLLBACK_REVISION:-}"
+ gcloud run services update-traffic "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --to-revisions "${ROLLBACK_REVISION}=100" \
+ --quiet
+ serving="$(gcloud run services describe "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" --region "${GCP_REGION}" --format=json \
+ | jq -r '[.status.traffic[] | select((.percent // 0) > 0)]
+ | if length == 1 and .[0].percent == 100 then .[0].revisionName else empty end')"
+ test "${serving}" = "${ROLLBACK_REVISION}"
+ echo "TRAFFIC_ROLLED_BACK=true" >> "${GITHUB_ENV}"
+ {
+ echo
+ echo '### Push gateway rolled back'
+ echo
+ echo "Traffic returned to \`${ROLLBACK_REVISION}\`; the candidate" \
+ "\`${CANDIDATE_REVISION}\` no longer serves."
+ } >> "${GITHUB_STEP_SUMMARY}"
+
+ # Why: a candidate that never took traffic is a revision holding a warm floor and a Cloud
+ # SQL pool for nothing. Its tag comes off first, because Cloud Run refuses to delete a
+ # revision a traffic target still names, and clearing CANDIDATE_TAG makes the always() tag
+ # step below a no-op rather than a second failure.
+ - name: Delete the rejected candidate revision
+ if: ${{ (failure() || cancelled()) && (env.TRAFFIC_SHIFT_ATTEMPTED != 'true' || env.TRAFFIC_ROLLED_BACK == 'true') }}
+ shell: bash
+ run: |
+ set -euo pipefail
+ test -n "${CANDIDATE_REVISION:-}" || exit 0
+ if test -n "${CANDIDATE_TAG:-}"; then
+ gcloud run services update-traffic "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --remove-tags "${CANDIDATE_TAG}" \
+ --quiet
+ echo "CANDIDATE_TAG=" >> "${GITHUB_ENV}"
+ fi
+ gcloud run revisions delete "${CANDIDATE_REVISION}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --quiet
+ echo "deleted the candidate revision ${CANDIDATE_REVISION}"
+
+ - name: Drop the candidate traffic tag
+ if: always()
+ shell: bash
+ run: |
+ set -euo pipefail
+ test -n "${CANDIDATE_TAG:-}" || exit 0
+ gcloud run services update-traffic "${SERVICE_NAME}" \
+ --project "${GCP_PROJECT_ID}" \
+ --region "${GCP_REGION}" \
+ --remove-tags "${CANDIDATE_TAG}" \
+ --quiet
diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml
index f75d7ba00bb6..942f0f34a56e 100644
--- a/.github/workflows/e2e.yml
+++ b/.github/workflows/e2e.yml
@@ -227,6 +227,7 @@ jobs:
mapfile -t TEST_FILES < <(jq -r '.[] | select(
. != "tests/e2e/ssh-startup-exec-readiness.spec.ts" and
. != "tests/e2e/paired-startup-exec-readiness.spec.ts" and
+ . != "tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts" and
. != "tests/e2e/local-ssh-browser-routing.spec.ts" and
. != "tests/e2e/ssh-browser-network-execution-route.docker.unit.test.ts" and
. != "tests/e2e/ssh-localhost.spec.ts" and
@@ -272,6 +273,7 @@ jobs:
if: >-
inputs.test_files == '' ||
inputs.ssh_source_changed == 'true' ||
+ contains(inputs.test_files, 'tests/e2e/ssh-docker-five-pane-input-under-flood.spec.ts') ||
contains(inputs.test_files, 'tests/e2e/local-ssh-browser-routing.spec.ts') ||
contains(inputs.test_files, 'tests/e2e/ssh-client-hosted-browser-drop-reconnect.spec.ts') ||
contains(inputs.test_files, 'tests/e2e/ssh-startup-exec-readiness.spec.ts') ||
diff --git a/.github/workflows/mobile-android-release.yml b/.github/workflows/mobile-android-release.yml
index 35e900dc31c3..17100c788b8f 100644
--- a/.github/workflows/mobile-android-release.yml
+++ b/.github/workflows/mobile-android-release.yml
@@ -104,11 +104,47 @@ jobs:
--clobber \
android/app/build/outputs/apk/release/*.apk
else
+ # Why: release tags live on side branches, so GitHub's automatic
+ # previous-tag detection reaches back several releases; that body
+ # already exceeds the 125000-character API limit and grows each
+ # release. Pin the comparison base and cap the size.
+ notes_file="$RUNNER_TEMP/android-release-notes.md"
+ previous_tag="$(
+ gh release list --repo "$GITHUB_REPOSITORY" --limit 200 --json tagName --jq '.[].tagName' \
+ | grep '^mobile-android-v' | grep -Fxv "$tag" | sort -V | tail -1 || true
+ )"
+
+ if [ -n "$previous_tag" ]; then
+ # Why: gh writes the JSON error body to stdout on an HTTP error, so a
+ # non-empty file is not proof of success — gate on exit status.
+ if ! gh api "repos/$GITHUB_REPOSITORY/releases/generate-notes" -X POST \
+ -f tag_name="$tag" \
+ -f target_commitish="$GITHUB_SHA" \
+ -f previous_tag_name="$previous_tag" \
+ --jq .body > "$notes_file"; then
+ : > "$notes_file"
+ fi
+ fi
+ if [ ! -s "$notes_file" ]; then
+ printf 'Orca Mobile Android %s\n' "$tag" > "$notes_file"
+ fi
+ # Why: reuse the desktop release path's character-safe truncation so a
+ # multi-byte character cannot be split at the cap.
+ NOTES_FILE="$notes_file" \
+ NOTES_MODULE="$GITHUB_WORKSPACE/config/scripts/create-draft-release.mjs" \
+ node --input-type=module -e '
+ const { readFileSync, writeFileSync } = await import("node:fs")
+ const { pathToFileURL } = await import("node:url")
+ const { truncateReleaseBody } = await import(pathToFileURL(process.env.NOTES_MODULE).href)
+ const file = process.env.NOTES_FILE
+ writeFileSync(file, truncateReleaseBody(readFileSync(file, "utf8")))
+ '
+
gh release create "$tag" \
--repo "$GITHUB_REPOSITORY" \
--title "Orca Mobile Android $tag" \
--prerelease \
--latest=false \
- --generate-notes \
+ --notes-file "$notes_file" \
android/app/build/outputs/apk/release/*.apk
fi
diff --git a/.github/workflows/packaged-browser-e2e.yml b/.github/workflows/packaged-browser-e2e.yml
new file mode 100644
index 000000000000..2a23ac58988c
--- /dev/null
+++ b/.github/workflows/packaged-browser-e2e.yml
@@ -0,0 +1,74 @@
+name: Packaged browser compatibility
+on:
+ workflow_dispatch:
+ inputs:
+ ref:
+ description: Commit SHA or ref to validate (defaults to the selected revision)
+ type: string
+ required: false
+ schedule:
+ - cron: '20 8 * * 1'
+ workflow_call:
+ inputs:
+ ref:
+ type: string
+ required: false
+permissions:
+ contents: read
+jobs:
+ compatibility:
+ runs-on: ubuntu-latest
+ timeout-minutes: 25
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ ref: ${{ inputs.ref || github.sha }}
+ persist-credentials: false
+ - name: Install headless tools
+ run: sudo apt-get update && sudo apt-get install -y build-essential openssh-client python3 ripgrep xvfb zsh openbox x11-utils
+ - uses: ./.github/actions/install-node-dependencies
+ with:
+ native-runtime: electron
+ - name: Download pinned old release
+ env:
+ GH_TOKEN: ${{ github.token }}
+ run: |
+ gh release download v1.4.188 --repo stablyai/orca --pattern orca-ide_1.4.188_amd64.deb --dir "$RUNNER_TEMP/old-orca"
+ python3 - <<'PYVERIFY'
+ import base64,hashlib,os,pathlib,subprocess
+ root=pathlib.Path(os.environ['RUNNER_TEMP'])/'old-orca'
+ package=root/'orca-ide_1.4.188_amd64.deb'
+ expected='uGONFUDfinYggxcT9ac72wnnlofLQaqasDDeP0HWOSqarBwTi1Ax3khmzKUY3vUnvuYOpSCEmsH4InzLZ2vg6g=='
+ assert base64.b64encode(hashlib.sha512(package.read_bytes()).digest()).decode()==expected
+ extracted=root/'extracted'
+ subprocess.run(['dpkg-deb','-x',str(package),str(extracted)],check=True)
+ executable=extracted/'opt'/'Orca'/'orca-ide'
+ assert executable.is_file() and os.access(executable,os.X_OK)
+ with open(os.environ['GITHUB_ENV'],'a') as env: env.write('ORCA_CROSS_VERSION_PACKAGED_EXECUTABLE='+str(executable)+'\n')
+ print('Verified old package:',executable)
+ PYVERIFY
+ - name: Build current Electron app
+ env:
+ VITE_EXPOSE_STORE: 'true'
+ run: |
+ pnpm run build:relay
+ pnpm exec electron-vite build --mode e2e
+ pnpm run build:web-from-renderer
+ - name: Run both mixed-version directions
+ env:
+ PLAYWRIGHT_JSON_OUTPUT_FILE: test-results/packaged-browser-results.json
+ run: >-
+ xvfb-run --auto-servernum bash .github/scripts/e2e-with-window-manager.sh
+ env SKIP_BUILD=1 ORCA_E2E_FORWARD_APP_LOGS=1
+ pnpm exec playwright test --config tests/playwright.config.ts
+ tests/e2e/packaged-mixed-version-browser-placement.spec.ts
+ --project=electron-headless --workers=1 --retries=0 --repeat-each=3 --reporter=list,json
+ - name: Require all six compatibility executions
+ if: always()
+ run: node config/scripts/verify-packaged-browser-participation.mjs test-results/packaged-browser-results.json
+ - uses: actions/upload-artifact@v7
+ if: always()
+ with:
+ name: packaged-mixed-version-audit
+ path: test-results/
+ retention-days: 3
diff --git a/.github/workflows/terminal-ime-e2e.yml b/.github/workflows/terminal-ime-e2e.yml
index 1ab905d87830..bd6be26bd272 100644
--- a/.github/workflows/terminal-ime-e2e.yml
+++ b/.github/workflows/terminal-ime-e2e.yml
@@ -70,3 +70,40 @@ jobs:
path: test-results/
retention-days: 7
if-no-files-found: ignore
+
+ linux-wayland:
+ name: Linux Wayland Hangul terminating digit
+ runs-on: ubuntu-22.04
+ timeout-minutes: 25
+ steps:
+ - uses: actions/checkout@v6
+ with:
+ persist-credentials: false
+ - name: Install native build, nested compositor and IME tools
+ run: >-
+ sudo apt-get update && sudo apt-get install -y
+ build-essential python3 fonts-noto-cjk dbus-x11 dconf-gsettings-backend
+ ibus ibus-hangul gnome-shell gnome-settings-daemon libglib2.0-bin
+ xdotool xvfb x11-utils imagemagick
+ - uses: ./.github/actions/install-node-dependencies
+ with:
+ native-runtime: electron
+ - name: Build Electron app for E2E
+ env:
+ VITE_EXPOSE_STORE: 'true'
+ run: |
+ pnpm run build:relay
+ pnpm exec electron-vite build --mode e2e
+ pnpm run build:web-from-renderer
+ - name: Run native Wayland Hangul terminating digit
+ env:
+ SKIP_BUILD: '1'
+ run: node config/scripts/run-terminal-ibus-hangul-e2e.mjs --nested-wayland
+ - name: Upload Wayland terminal IME evidence
+ if: always()
+ uses: actions/upload-artifact@v7
+ with:
+ name: terminal-wayland-ime-evidence
+ path: test-results/
+ retention-days: 7
+ if-no-files-found: error
diff --git a/README.md b/README.md
index 2ae59035da85..7e3540c80f14 100644
--- a/README.md
+++ b/README.md
@@ -36,7 +36,7 @@
Monitor and steer your agents from your phone — get notified when an agent finishes and send follow-ups from anywhere.
-[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [TestFlight](https://testflight.apple.com/join/YjeGMQBA) · [Android APK 0.0.47](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.47/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
+[iOS App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) · [TestFlight](https://testflight.apple.com/join/YjeGMQBA) · [Android APK 0.0.48](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.48/app-release.apk) · [Docs →](https://www.onorca.dev/docs/mobile)
@@ -230,7 +230,7 @@ yay -S stably-orca-bin
Pair with your desktop app to monitor and steer your agents from your phone.
- **iOS:** [Download on the App Store](https://apps.apple.com/us/app/orca-ide/id6766130217) or [join TestFlight](https://testflight.apple.com/join/YjeGMQBA)
-- **Android:** [Download APK 0.0.47](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.47/app-release.apk) · [Install guide](https://www.onorca.dev/docs/android-apk)
+- **Android:** [Download APK 0.0.48](https://github.com/stablyai/orca/releases/download/mobile-android-v0.0.48/app-release.apk) · [Install guide](https://www.onorca.dev/docs/android-apk)
---
diff --git a/cloud/apps/relay-fence-broker/package.json b/cloud/apps/relay-fence-broker/package.json
index c9bf65c2cf36..06379184f13e 100644
--- a/cloud/apps/relay-fence-broker/package.json
+++ b/cloud/apps/relay-fence-broker/package.json
@@ -14,8 +14,8 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
- "@hono/node-server": "^1.19.14",
- "hono": "^4.12.27",
+ "@hono/node-server": "^1.19.17",
+ "hono": "^4.13.7",
"zod": "^3.25.76"
},
"devDependencies": {
diff --git a/cloud/apps/relay-ops/package.json b/cloud/apps/relay-ops/package.json
index da4f73f86721..2881a5c09f3e 100644
--- a/cloud/apps/relay-ops/package.json
+++ b/cloud/apps/relay-ops/package.json
@@ -16,8 +16,8 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
- "@hono/node-server": "^1.19.14",
- "hono": "^4.12.27",
+ "@hono/node-server": "^1.19.17",
+ "hono": "^4.13.7",
"zod": "^3.25.76"
},
"devDependencies": {
diff --git a/cloud/apps/relay/package.json b/cloud/apps/relay/package.json
index 4c2b2e4269c6..de30d66e4135 100644
--- a/cloud/apps/relay/package.json
+++ b/cloud/apps/relay/package.json
@@ -15,13 +15,13 @@
"typecheck": "tsc -p tsconfig.json --noEmit"
},
"dependencies": {
- "@hono/node-server": "^1.19.14",
+ "@hono/node-server": "^1.19.17",
"@orca-cloud/relay-contract": "workspace:*",
- "hono": "^4.12.27",
+ "hono": "^4.13.7",
"jose": "^6.1.3",
"pg": "^8.22.0",
"tweetnacl": "^1.0.3",
- "ws": "^8.18.3",
+ "ws": "^8.21.3",
"zod": "^3.25.76"
},
"devDependencies": {
diff --git a/cloud/apps/relay/src/app.ts b/cloud/apps/relay/src/app.ts
index 3df01d9e9cef..c45e31c4a014 100644
--- a/cloud/apps/relay/src/app.ts
+++ b/cloud/apps/relay/src/app.ts
@@ -606,8 +606,9 @@ export function createRelayApp(
const source = await operations.assignments.cellDeploymentStatus(
body.data.sourceCellId
)
+ // Any cell that can be drained can be a rehome source, in either
+ // direction, so the probe is gated on the protocol and not on a region.
if (
- source.region !== RELAY_DEFAULT_REGION ||
!source.runtime ||
source.runtime.cellIncarnation !== body.data.sourceCellIncarnation ||
!source.runtime.ready ||
@@ -1412,6 +1413,11 @@ const RegionalRehomeControlSchema = z.discriminatedUnion('action', [
.int()
.min(60_000)
.max(30 * 24 * 60 * 60_000),
+ hostCooldownMs: z
+ .number()
+ .int()
+ .min(60_000)
+ .max(30 * 24 * 60 * 60_000),
drainGraceMs: z.number().int().min(60_000).max(60 * 60_000),
confirmation: z.enum([
'ENABLE_REGIONAL_REHOMING',
diff --git a/cloud/apps/relay/src/assignment-inventory-snapshot.ts b/cloud/apps/relay/src/assignment-inventory-snapshot.ts
index 0675bd49b949..652fbdf21842 100644
--- a/cloud/apps/relay/src/assignment-inventory-snapshot.ts
+++ b/cloud/apps/relay/src/assignment-inventory-snapshot.ts
@@ -1,3 +1,4 @@
+import { RELAY_DEFAULT_REGION } from '@orca-cloud/relay-contract'
import type { RelayDatabase, SqlRow } from './database.js'
export type CellInventorySnapshotRow = {
@@ -92,7 +93,7 @@ export async function readAssignmentInventorySnapshot(
return {
cells: cellRows.map((row) => ({
cellId: asText(row, 'cell_id'),
- region: optionalText(row, 'region') ?? 'us-central1',
+ region: optionalText(row, 'region') ?? RELAY_DEFAULT_REGION,
admissionState: optionalText(row, 'admission_state') ?? 'unset',
enabled: asInteger(row, 'enabled') === 1,
capacityRequests: asInteger(row, 'capacity_requests'),
diff --git a/cloud/apps/relay/src/assignment-store.ts b/cloud/apps/relay/src/assignment-store.ts
index 226df9b3984e..9ead45df22e8 100644
--- a/cloud/apps/relay/src/assignment-store.ts
+++ b/cloud/apps/relay/src/assignment-store.ts
@@ -30,6 +30,9 @@ import {
ASSIGNMENT_CONNECTION_HEADROOM_QUERY
} from './assignment-connection-headroom-query.js'
import { AssignmentIdentityQueue } from './assignment-identity-queue.js'
+import {
+ REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS
+} from './database.js'
import type { RelayCellConfig } from './config.js'
import type {
RelayDatabase,
@@ -121,7 +124,7 @@ export type RelayAssignmentMigration = AssignmentIdentity & {
export type RegionalRehomeAttempt = AssignmentIdentity & {
attemptId: string
- preferredRegion: 'asia-east2'
+ preferredRegion: RelayRegion
sourceCellId: string
sourceCellUrl: string
sourceCellIncarnation: string
@@ -151,6 +154,7 @@ export type RegionalRehomeControl = {
notBefore: number
ratePerMinute: number
preferenceMaxAgeMs: number
+ hostCooldownMs: number
drainGraceMs: number
}
@@ -4921,6 +4925,7 @@ export class RelayAssignmentStore {
notBefore: number
ratePerMinute: number
preferenceMaxAgeMs: number
+ hostCooldownMs: number
drainGraceMs: number
}): Promise {
if (!Number.isSafeInteger(input.expectedGeneration) || input.expectedGeneration < 0) {
@@ -4939,6 +4944,13 @@ export class RelayAssignmentStore {
) {
throw new Error('invalid_regional_rehome_preference_age')
}
+ if (
+ !Number.isSafeInteger(input.hostCooldownMs) ||
+ input.hostCooldownMs < 60_000 ||
+ input.hostCooldownMs > 30 * 24 * 60 * 60_000
+ ) {
+ throw new Error('invalid_regional_rehome_host_cooldown')
+ }
if (
!Number.isSafeInteger(input.drainGraceMs) ||
input.drainGraceMs < 60_000 ||
@@ -4967,14 +4979,15 @@ export class RelayAssignmentStore {
await transaction.query(
`UPDATE relay_region_rehome_control
SET generation = generation + 1, enabled = ?, not_before = ?,
- rate_per_minute = ?, preference_max_age_ms = ?, drain_grace_ms = ?,
- updated_at = ?
+ rate_per_minute = ?, preference_max_age_ms = ?, host_cooldown_ms = ?,
+ drain_grace_ms = ?, updated_at = ?
WHERE control_id = 'global'`,
[
input.enabled ? 1 : 0,
input.notBefore,
input.ratePerMinute,
input.preferenceMaxAgeMs,
+ input.hostCooldownMs,
input.drainGraceMs,
now
]
@@ -5006,10 +5019,17 @@ export class RelayAssignmentStore {
await database.query(
`INSERT INTO relay_region_rehome_control
(control_id, generation, enabled, observation_started_at, not_before,
- rate_per_minute, preference_max_age_ms, drain_grace_ms, updated_at)
- VALUES ('global', 0, 0, ?, 0, 10, ?, ?, ?)
+ rate_per_minute, preference_max_age_ms, host_cooldown_ms, drain_grace_ms,
+ updated_at)
+ VALUES ('global', 0, 0, ?, 0, 10, ?, ?, ?, ?)
ON CONFLICT (control_id) DO NOTHING`,
- [now, 24 * 60 * 60_000, 60 * 60_000, now]
+ [
+ now,
+ 24 * 60 * 60_000,
+ REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS,
+ 60 * 60_000,
+ now
+ ]
)
}
@@ -5017,6 +5037,9 @@ export class RelayAssignmentStore {
return await this.readRegionalRehomeFleetSafety(this.database, this.now())
}
+ // The rehome fleet is every general cell that can be drained: those are the
+ // sources and, because a host must be movable back out again, the only legal
+ // targets. The region join stays so a cell with no region row is excluded.
private async readRegionalRehomeFleetSafety(
database: RelayDatabase,
now: number
@@ -5037,10 +5060,7 @@ export class RelayAssignmentStore {
ON safety.cell_id = runtime.cell_id
AND safety.cell_incarnation = runtime.cell_incarnation
WHERE cell.enabled = 1 AND admission.admission_state = 'general'
- AND (
- region.region = 'asia-east2' OR
- (region.region = 'us-central1' AND capability.regional_rehome_protocol >= 1)
- )`
+ AND capability.regional_rehome_protocol >= 1`
)
const valid = rows.filter(
(row) =>
@@ -5119,6 +5139,10 @@ export class RelayAssignmentStore {
}
const intervalMs = Math.ceil(60_000 / integer(control, 'rate_per_minute'))
const preferenceCutoff = now - integer(control, 'preference_max_age_ms')
+ // A host that was rehomed recently is left alone whichever way its
+ // preference now points: a flapping region probe must not walk one host
+ // back and forth across an ocean.
+ const cooldownCutoff = now - integer(control, 'host_cooldown_ms')
await transaction.query(
`INSERT INTO relay_region_rehome_worker_state
(worker_id, next_dispatch_at, paused_until, consecutive_failures, updated_at)
@@ -5284,9 +5308,8 @@ export class RelayAssignmentStore {
JOIN relay_cell_capabilities capability
ON capability.cell_id = runtime.cell_id
AND capability.cell_incarnation = runtime.cell_incarnation
- WHERE preference.preferred_region = 'asia-east2'
+ WHERE preference.preferred_region <> region.region
AND preference.observed_at >= ?
- AND region.region = 'us-central1'
AND admission.admission_state = 'general'
AND runtime.ready = 1 AND runtime.last_heartbeat_at > ?
AND capability.regional_rehome_protocol >= 1
@@ -5306,9 +5329,38 @@ export class RelayAssignmentStore {
AND migration.relay_host_id = assignment.relay_host_id
AND migration.completed_at IS NULL AND migration.aborted_at IS NULL
)
+ AND NOT EXISTS (
+ SELECT 1 FROM relay_region_rehome_attempts recent
+ WHERE recent.user_id = preference.user_id
+ AND recent.relay_host_id = preference.relay_host_id
+ AND recent.created_at > ?
+ )
+ AND EXISTS (
+ SELECT 1 FROM relay_cell_regions target_region
+ JOIN relay_cells target_cell ON target_cell.cell_id = target_region.cell_id
+ JOIN relay_cell_admission target_admission
+ ON target_admission.cell_id = target_region.cell_id
+ JOIN relay_cell_runtime target_runtime
+ ON target_runtime.cell_id = target_region.cell_id
+ JOIN relay_cell_capabilities target_capability
+ ON target_capability.cell_id = target_runtime.cell_id
+ AND target_capability.cell_incarnation = target_runtime.cell_incarnation
+ WHERE target_region.region = preference.preferred_region
+ AND target_cell.enabled = 1
+ AND target_admission.admission_state = 'general'
+ AND target_runtime.ready = 1
+ AND target_runtime.last_heartbeat_at > ?
+ AND target_capability.regional_rehome_protocol >= 1
+ )
ORDER BY preference.observed_at, preference.user_id, preference.relay_host_id
LIMIT 10`,
- [preferenceCutoff, now - this.heartbeatTtlMs, now]
+ [
+ preferenceCutoff,
+ now - this.heartbeatTtlMs,
+ now,
+ cooldownCutoff,
+ now - this.heartbeatTtlMs
+ ]
)
candidatesTotal = candidates.length
for (const candidate of candidates) {
@@ -5320,6 +5372,7 @@ export class RelayAssignmentStore {
sourceCellId: text(candidate, 'source_cell_id'),
assignmentEpoch: integer(candidate, 'assignment_epoch'),
preferenceCutoff,
+ cooldownCutoff,
drainGraceMs: integer(control, 'drain_grace_ms'),
processSafety: effectiveProcessSafety,
worker,
@@ -5374,6 +5427,7 @@ export class RelayAssignmentStore {
sourceCellId: string
assignmentEpoch: number
preferenceCutoff: number
+ cooldownCutoff: number
drainGraceMs: number
processSafety: RegionalRehomeSafetySnapshot
worker: SqlRow
@@ -5397,14 +5451,11 @@ export class RelayAssignmentStore {
[input.identity.userId, input.identity.relayHostId]
)
)[0]
- if (
- !preference ||
- text(preference, 'preferred_region') !== 'asia-east2' ||
- integer(preference, 'observed_at') < input.preferenceCutoff
- ) {
+ if (!preference || integer(preference, 'observed_at') < input.preferenceCutoff) {
input.skips.push({ reason: 'candidate_stale' })
return null
}
+ const preferredRegion = relayRegion(preference, 'preferred_region')
const activeMigration = await transaction.queryLocked(
`SELECT assignment_epoch FROM relay_assignment_migrations
WHERE user_id = ? AND relay_host_id = ?
@@ -5415,6 +5466,18 @@ export class RelayAssignmentStore {
input.skips.push({ reason: 'candidate_stale' })
return null
}
+ // Re-read under the claim: an attempt committed between the scan and here
+ // would otherwise start a second move for the same host.
+ const recentAttempt = await transaction.query(
+ `SELECT 1 FROM relay_region_rehome_attempts
+ WHERE user_id = ? AND relay_host_id = ? AND created_at > ?
+ LIMIT 1`,
+ [input.identity.userId, input.identity.relayHostId, input.cooldownCutoff]
+ )
+ if (recentAttempt.length > 0) {
+ input.skips.push({ reason: 'host_cooldown' })
+ return null
+ }
const activityLeases = await this.lockAssignmentActivities(transaction, input.identity)
assertAssignmentActivityCounts(assignment, activityLeases, 0)
const cells = await this.lockCellInventory(transaction, 'nowait')
@@ -5469,11 +5532,17 @@ export class RelayAssignmentStore {
)
return null
}
+ // The preference read under lock can now agree with the cell the host is
+ // already on: nothing to move, in either direction.
+ if (regions.get(input.sourceCellId) === preferredRegion) {
+ input.skips.push({ reason: 'candidate_stale' })
+ return null
+ }
if (
!source ||
integer(source, 'enabled') !== 1 ||
admission.get(input.sourceCellId) !== 'general' ||
- regions.get(input.sourceCellId) !== RELAY_DEFAULT_REGION ||
+ regions.get(input.sourceCellId) === undefined ||
!sourceRuntime ||
integer(sourceRuntime, 'ready') !== 1 ||
integer(sourceRuntime, 'last_heartbeat_at') <= input.now - this.heartbeatTtlMs ||
@@ -5502,17 +5571,25 @@ export class RelayAssignmentStore {
return null
}
const connectionHeadroom = await this.connectionHeadroomByCell(transaction)
+ // A target must be drainable too, or the host lands somewhere it can never
+ // be rehomed out of again -- the trap this bidirectional move exists to undo.
const eligibleTargets = cells.filter((row) => {
const cellId = text(row, 'cell_id')
const runtime = runtimes.find((candidate) => text(candidate, 'cell_id') === cellId)
+ const capability = capabilities.find(
+ (candidate) => text(candidate, 'cell_id') === cellId
+ )
return (
cellId !== input.sourceCellId &&
integer(row, 'enabled') === 1 &&
admission.get(cellId) === 'general' &&
- regions.get(cellId) === 'asia-east2' &&
+ regions.get(cellId) === preferredRegion &&
runtime !== undefined &&
integer(runtime, 'ready') === 1 &&
- integer(runtime, 'last_heartbeat_at') > input.now - this.heartbeatTtlMs
+ integer(runtime, 'last_heartbeat_at') > input.now - this.heartbeatTtlMs &&
+ capability !== undefined &&
+ text(capability, 'cell_incarnation') === text(runtime, 'cell_incarnation') &&
+ integer(capability, 'regional_rehome_protocol') >= 1
)
})
const targetIsClean = (row: SqlRow): boolean => {
@@ -5668,12 +5745,13 @@ export class RelayAssignmentStore {
drain_grace_ms, send_attempts, last_send_attempt_at,
drain_receipt_at, drain_outcome, completed_at, aborted_at,
created_at, updated_at)
- VALUES (?, ?, ?, 'asia-east2', ?, ?, ?, ?, ?, ?, ?, 0, NULL,
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 0, NULL,
NULL, NULL, NULL, NULL, ?, ?)`,
[
attemptId,
input.identity.userId,
input.identity.relayHostId,
+ preferredRegion,
input.sourceCellId,
text(sourceRuntime, 'cell_incarnation'),
targetCellId,
@@ -5688,7 +5766,7 @@ export class RelayAssignmentStore {
return {
...input.identity,
attemptId,
- preferredRegion: 'asia-east2',
+ preferredRegion,
sourceCellId: input.sourceCellId,
sourceCellUrl: text(source, 'cell_url'),
sourceCellIncarnation: text(sourceRuntime, 'cell_incarnation'),
@@ -8101,7 +8179,7 @@ function regionalRehomeAttempt(row: SqlRow): RegionalRehomeAttempt {
attemptId: text(row, 'attempt_id'),
userId: text(row, 'user_id'),
relayHostId: text(row, 'relay_host_id'),
- preferredRegion: 'asia-east2',
+ preferredRegion: relayRegion(row, 'preferred_region'),
sourceCellId: text(row, 'source_cell_id'),
sourceCellUrl: text(row, 'source_cell_url'),
sourceCellIncarnation: text(row, 'source_cell_incarnation'),
@@ -8122,6 +8200,7 @@ function regionalRehomeControl(row: SqlRow): RegionalRehomeControl {
notBefore: integer(row, 'not_before'),
ratePerMinute: integer(row, 'rate_per_minute'),
preferenceMaxAgeMs: integer(row, 'preference_max_age_ms'),
+ hostCooldownMs: integer(row, 'host_cooldown_ms'),
drainGraceMs: integer(row, 'drain_grace_ms')
}
}
@@ -8161,10 +8240,9 @@ function regionalRehomeFleetSafetyFromInventory(input: {
return (
integer(row, 'enabled') === 1 &&
input.admission.get(cellId) === 'general' &&
- (input.regions.get(cellId) === 'asia-east2' ||
- (input.regions.get(cellId) === RELAY_DEFAULT_REGION &&
- capability !== undefined &&
- integer(capability, 'regional_rehome_protocol') >= 1))
+ input.regions.get(cellId) !== undefined &&
+ capability !== undefined &&
+ integer(capability, 'regional_rehome_protocol') >= 1
)
})
const valid = required.flatMap((row) => {
@@ -8231,6 +8309,7 @@ function regionalRehomeFleetSafetyFailure(
type RegionalRehomeCandidateSkip = {
reason:
| 'candidate_stale'
+ | 'host_cooldown'
| 'source_ineligible'
| 'source_unclean'
| 'source_control_inactive'
diff --git a/cloud/apps/relay/src/cell-heartbeat-client.ts b/cloud/apps/relay/src/cell-heartbeat-client.ts
index 3bbcd08ecd61..5c990310413e 100644
--- a/cloud/apps/relay/src/cell-heartbeat-client.ts
+++ b/cloud/apps/relay/src/cell-heartbeat-client.ts
@@ -1,4 +1,5 @@
import { randomUUID } from 'node:crypto'
+import { RELAY_DEFAULT_REGION } from '@orca-cloud/relay-contract'
import type { RelayConfig } from './config.js'
import { googleMetadataIdentityToken } from './google-metadata-identity-token.js'
import type { RegionalRehomeSafetySnapshot } from './relay-observability.js'
@@ -57,7 +58,7 @@ export function startCellHeartbeat(
v: 1,
cellId: config.cellId,
cellUrl: config.cellUrl,
- region: config.region ?? 'us-central1',
+ region: config.region ?? RELAY_DEFAULT_REGION,
cellIncarnation,
startedAt,
ready,
diff --git a/cloud/apps/relay/src/database-postgres-timeout.test.ts b/cloud/apps/relay/src/database-postgres-timeout.test.ts
index c9021a9ef184..f678fecc4bb4 100644
--- a/cloud/apps/relay/src/database-postgres-timeout.test.ts
+++ b/cloud/apps/relay/src/database-postgres-timeout.test.ts
@@ -34,7 +34,11 @@ vi.mock('pg', () => ({
}
}))
-import { openRelayDatabase, relayPostgresStatementTimeoutMs } from './database.js'
+import {
+ openRelayDatabase,
+ POSTGRES_SCHEMA_MIGRATIONS,
+ relayPostgresStatementTimeoutMs
+} from './database.js'
import { applyPostgresSchema } from './postgres-schema-startup.js'
const SCHEMA_POOL = {
@@ -118,7 +122,9 @@ describe('PostgreSQL relay deadlines', () => {
// Statements can open with a leading `--` rationale comment.
const body = (statement: string): string =>
statement.replace(/^(?:\s*--[^\n]*\n)*\s*/, '')
- expect(ddl.every((statement) => /^CREATE\b/i.test(body(statement)))).toBe(true)
+ expect(
+ ddl.every((statement) => /^(?:CREATE|ALTER TABLE)\b/i.test(body(statement)))
+ ).toBe(true)
// The backfill is DML, so it stays on the deadline-bearing serving pool.
expect(ddl.some((statement) => statement.includes('INSERT INTO'))).toBe(false)
await database.close()
@@ -263,6 +269,54 @@ describe('PostgreSQL schema startup', () => {
expect(query).toHaveBeenCalledTimes(2)
})
+ it('treats an existing constraint as an applied ADD CONSTRAINT', async () => {
+ // Postgres has no `ADD CONSTRAINT IF NOT EXISTS`, and a retry would only
+ // repeat 42710, so a re-run and a concurrent startup both move on.
+ const error = Object.assign(new Error('already exists'), { code: '42710' })
+ const query = vi
+ .fn<(statement: string) => Promise>()
+ .mockRejectedValueOnce(error)
+ .mockResolvedValue(undefined)
+ const pause = vi.fn(async () => undefined)
+
+ await applyPostgresSchema(
+ ['ALTER TABLE test ADD CONSTRAINT test_check CHECK (id > 0)', 'CREATE TABLE test2'],
+ query,
+ { wait: pause }
+ )
+
+ expect(pause).not.toHaveBeenCalled()
+ expect(query).toHaveBeenCalledTimes(2)
+ expect(query).toHaveBeenLastCalledWith('CREATE TABLE test2')
+ })
+
+ it('recognises every shipped ADD CONSTRAINT migration as re-runnable', async () => {
+ // Guards the statement text against the pattern that classifies it.
+ const shipped = POSTGRES_SCHEMA_MIGRATIONS.filter((statement) =>
+ statement.includes('ADD CONSTRAINT')
+ )
+ expect(shipped.length).toBeGreaterThan(0)
+ const error = Object.assign(new Error('already exists'), { code: '42710' })
+ const query = vi.fn<(statement: string) => Promise>().mockRejectedValue(error)
+
+ await applyPostgresSchema(shipped, query, { wait: async () => undefined })
+
+ expect(query).toHaveBeenCalledTimes(shipped.length)
+ })
+
+ it('still fails an ADD CONSTRAINT that violates existing rows', async () => {
+ const error = Object.assign(new Error('check violation'), { code: '23514' })
+ const query = vi.fn<(statement: string) => Promise>().mockRejectedValue(error)
+
+ await expect(
+ applyPostgresSchema(
+ ['ALTER TABLE test ADD CONSTRAINT test_check CHECK (id > 0)'],
+ query,
+ { wait: async () => undefined }
+ )
+ ).rejects.toBe(error)
+ })
+
it.each([
['42710', 'CREATE INDEX IF NOT EXISTS test_index ON test(id)'],
['42710', 'CREATE TABLE test'],
diff --git a/cloud/apps/relay/src/database.test.ts b/cloud/apps/relay/src/database.test.ts
index 32e50a7bc6a0..56122def4bee 100644
--- a/cloud/apps/relay/src/database.test.ts
+++ b/cloud/apps/relay/src/database.test.ts
@@ -2,7 +2,12 @@ import { mkdtempSync, rmSync } from 'node:fs'
import { tmpdir } from 'node:os'
import { join } from 'node:path'
import { afterEach, describe, expect, it } from 'vitest'
-import { openInMemoryRelayDatabase, openRelayDatabase } from './database.js'
+import {
+ openInMemoryRelayDatabase,
+ openRelayDatabase,
+ POSTGRES_SCHEMA_MIGRATIONS,
+ REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS
+} from './database.js'
const temporaryDirectories: string[] = []
@@ -142,6 +147,41 @@ describe('relay database', () => {
await second.close()
})
+ it('renders every region check from the shared region list', async () => {
+ // Derived, not hand-written: a third region must not leave one column
+ // rejecting a value the rest of the relay already accepts.
+ const database = await openInMemoryRelayDatabase()
+ const checked = await database.query(
+ `SELECT name, sql FROM sqlite_master
+ WHERE type = 'table'
+ AND name IN ('relay_assignment_region_preferences', 'relay_cell_regions',
+ 'relay_region_rehome_attempts')
+ ORDER BY name`
+ )
+ const list = `IN ('us-central1', 'asia-east2')`
+ expect(checked.map((row) => row.name)).toEqual([
+ 'relay_assignment_region_preferences',
+ 'relay_cell_regions',
+ 'relay_region_rehome_attempts'
+ ])
+ expect(checked.every((row) => String(row.sql).includes(list))).toBe(true)
+ expect(
+ POSTGRES_SCHEMA_MIGRATIONS.some((statement) => statement.includes(list))
+ ).toBe(true)
+ await database.close()
+ })
+
+ it('indexes rehome attempts by host recency for the per-host cooldown', async () => {
+ const database = await openInMemoryRelayDatabase()
+ const rows = await database.query(
+ `SELECT sql FROM sqlite_master
+ WHERE type = 'index' AND name = 'relay_region_rehome_attempts_host_recency'`
+ )
+ expect(rows[0]?.sql).toContain('(user_id, relay_host_id, created_at)')
+ expect(REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS).toBe(7 * 24 * 60 * 60_000)
+ await database.close()
+ })
+
it('indexes region preference expiry by observation time', async () => {
const database = await openInMemoryRelayDatabase()
const rows = await database.query(
diff --git a/cloud/apps/relay/src/database.ts b/cloud/apps/relay/src/database.ts
index 2558831ca643..d51f4e7a4236 100644
--- a/cloud/apps/relay/src/database.ts
+++ b/cloud/apps/relay/src/database.ts
@@ -3,6 +3,7 @@ import { performance } from 'node:perf_hooks'
import { join } from 'node:path'
import { DatabaseSync } from 'node:sqlite'
import pg from 'pg'
+import { RELAY_REGIONS } from '@orca-cloud/relay-contract'
import {
emptyPostgresPoolPressureCounts,
PostgresPoolPressure,
@@ -24,6 +25,14 @@ function setLocalLockTimeout(milliseconds: number): string {
return `SET LOCAL lock_timeout = '${milliseconds}ms'`
}
+// Region CHECK lists come from the contract so a new region cannot leave a
+// column rejecting values the rest of the relay already accepts.
+const REGION_LIST = RELAY_REGIONS.map((region) => `'${region}'`).join(', ')
+
+// A host that was just moved is not a candidate again for this long, so a
+// desktop whose region probe flips cannot walk itself back and forth.
+export const REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS = 7 * 24 * 60 * 60_000
+
export type SqlRow = Record
export type RelayLockOptions = {
failIfUnavailable?: boolean
@@ -181,7 +190,7 @@ CREATE TABLE IF NOT EXISTS relay_assignment_region_preferences (
user_id TEXT NOT NULL,
relay_host_id TEXT NOT NULL,
preferred_region TEXT NOT NULL
- CHECK (preferred_region IN ('us-central1', 'asia-east2')),
+ CHECK (preferred_region IN (${REGION_LIST})),
observed_at BIGINT NOT NULL,
PRIMARY KEY (user_id, relay_host_id)
);
@@ -204,6 +213,8 @@ CREATE TABLE IF NOT EXISTS relay_region_rehome_control (
not_before BIGINT NOT NULL,
rate_per_minute BIGINT NOT NULL,
preference_max_age_ms BIGINT NOT NULL,
+ host_cooldown_ms BIGINT NOT NULL
+ DEFAULT ${REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS},
drain_grace_ms BIGINT NOT NULL,
updated_at BIGINT NOT NULL
);
@@ -212,7 +223,9 @@ CREATE TABLE IF NOT EXISTS relay_region_rehome_attempts (
attempt_id TEXT PRIMARY KEY,
user_id TEXT NOT NULL,
relay_host_id TEXT NOT NULL,
- preferred_region TEXT NOT NULL CHECK (preferred_region = 'asia-east2'),
+ preferred_region TEXT NOT NULL
+ CONSTRAINT relay_region_rehome_attempts_preferred_region_valid
+ CHECK (preferred_region IN (${REGION_LIST})),
source_cell_id TEXT NOT NULL,
source_cell_incarnation TEXT NOT NULL,
target_cell_id TEXT NOT NULL,
@@ -234,6 +247,8 @@ CREATE TABLE IF NOT EXISTS relay_region_rehome_attempts (
);
CREATE INDEX IF NOT EXISTS relay_region_rehome_attempts_pending
ON relay_region_rehome_attempts(drain_receipt_at, last_send_attempt_at, completed_at, aborted_at);
+CREATE INDEX IF NOT EXISTS relay_region_rehome_attempts_host_recency
+ ON relay_region_rehome_attempts(user_id, relay_host_id, created_at);
CREATE TABLE IF NOT EXISTS relay_cells (
cell_id TEXT PRIMARY KEY,
@@ -248,7 +263,7 @@ CREATE TABLE IF NOT EXISTS relay_cells (
CREATE TABLE IF NOT EXISTS relay_cell_regions (
cell_id TEXT PRIMARY KEY,
- region TEXT NOT NULL CHECK (region IN ('us-central1', 'asia-east2'))
+ region TEXT NOT NULL CHECK (region IN (${REGION_LIST}))
);
CREATE TABLE IF NOT EXISTS relay_cell_admission (
@@ -580,6 +595,21 @@ CREATE TABLE IF NOT EXISTS relay_audit_events (
CREATE INDEX IF NOT EXISTS relay_audit_events_at ON relay_audit_events(at);
`
+// Rehoming is bidirectional, but tables created before that carry the
+// original single-region column check. The old constraint is the one Postgres
+// auto-named; the replacement is named, so both statements are no-ops on a
+// database the current schema created and neither can drop the other.
+export const POSTGRES_SCHEMA_MIGRATIONS = [
+ `ALTER TABLE relay_region_rehome_attempts
+ DROP CONSTRAINT IF EXISTS relay_region_rehome_attempts_preferred_region_check`,
+ `ALTER TABLE relay_region_rehome_attempts
+ ADD CONSTRAINT relay_region_rehome_attempts_preferred_region_valid
+ CHECK (preferred_region IN (${REGION_LIST}))`,
+ `ALTER TABLE relay_region_rehome_control
+ ADD COLUMN IF NOT EXISTS host_cooldown_ms BIGINT NOT NULL
+ DEFAULT ${REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS}`
+]
+
function postgresSql(sql: string): string {
let index = 0
return sql.replace(/\?/g, () => `$${++index}`)
@@ -1009,7 +1039,10 @@ async function applySchemaOnUntimedPool(
const database = new PostgresDatabase(pool)
try {
await applyPostgresSchema(
- SCHEMA.split(';').filter((statement) => statement.trim()),
+ [
+ ...SCHEMA.split(';').filter((statement) => statement.trim()),
+ ...POSTGRES_SCHEMA_MIGRATIONS
+ ],
async (statement) => await database.query(statement)
)
} finally {
diff --git a/cloud/apps/relay/src/host-session-client-accept.test.ts b/cloud/apps/relay/src/host-session-client-accept.test.ts
index 83b6c21f997e..0cec6531e3fe 100644
--- a/cloud/apps/relay/src/host-session-client-accept.test.ts
+++ b/cloud/apps/relay/src/host-session-client-accept.test.ts
@@ -1,5 +1,5 @@
import { EventEmitter } from 'node:events'
-import { RELAY_CLOSE_CODE } from '@orca-cloud/relay-contract'
+import { RELAY_CLOSE_CODE, RELAY_PROTOCOL_LIMITS } from '@orca-cloud/relay-contract'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
import type WebSocket from 'ws'
import type { RelayAssignmentStore } from './assignment-store.js'
@@ -102,7 +102,9 @@ function harness(options: { random?: () => number; now?: () => number } = {}) {
const store = {
resolveResume: vi.fn().mockResolvedValue({ userId: identity.sub }),
reserveCredential: vi.fn().mockResolvedValue(reservation),
- failReservation: vi.fn().mockResolvedValue(undefined)
+ failReservation: vi.fn().mockResolvedValue(undefined),
+ recordConnectionBasis: vi.fn().mockResolvedValue(undefined),
+ deactivateBasis: vi.fn().mockResolvedValue(undefined)
}
const observer = {
recordAuth: vi.fn(),
@@ -110,7 +112,9 @@ function harness(options: { random?: () => number; now?: () => number } = {}) {
recordHttp: vi.fn(),
recordReconnect: vi.fn(),
recordSql: vi.fn(),
- recordClientAcceptAbandoned: vi.fn()
+ recordClientAcceptAbandoned: vi.fn(),
+ recordClientAcceptCompleted: vi.fn(),
+ recordControlRtt: vi.fn()
} satisfies RelayRuntimeObserver
const registry = new HostSessionRegistry(
config,
@@ -325,6 +329,210 @@ describe('client accept abandoned mid-DB-phase', () => {
})
})
+describe('successful client accept timing', () => {
+ beforeEach(() => vi.useFakeTimers())
+ afterEach(() => {
+ vi.clearAllTimers()
+ vi.useRealTimers()
+ })
+
+ it('times every serialized stage plus the attach window once relay-hello lands', async () => {
+ let now = 1_700_000_000_000
+ const h = harness({ now: () => now })
+ const control = await activeHost(h)
+ h.store.resolveResume.mockImplementationOnce(async () => {
+ now += 5
+ return { userId: identity.sub }
+ })
+ h.store.reserveCredential.mockImplementationOnce(async () => {
+ now += 7
+ return reservation
+ })
+ h.acquireActivity.mockImplementationOnce(async () => {
+ now += 11
+ })
+ h.store.recordConnectionBasis.mockImplementationOnce(async () => {
+ now += 3
+ })
+ const client = new FakeSocket()
+ const hostData = new FakeSocket()
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
+ try {
+ await h.registry.acceptClient(client as unknown as WebSocket, identity.relayHostId, 'cred')
+ const connOpen = JSON.parse(
+ String(control.send.mock.calls.find((call) => String(call[0]).includes('conn-open'))![0])
+ ) as { connId: string; connTicket: string }
+ // The desktop's data leg is the attach window this is meant to expose.
+ now += 23
+ const accepted = await h.registry.acceptHostData(
+ hostData as unknown as WebSocket,
+ connOpen.connId,
+ connOpen.connTicket,
+ 1
+ )
+
+ expect(accepted).toBe(true)
+ expect(h.observer.recordClientAcceptCompleted).toHaveBeenCalledWith({
+ totalMs: 49,
+ stageMs: { assignment: 5, credential: 7, activity: 11, attach: 23, basis: 3 }
+ })
+ const line = log.mock.calls
+ .map((call) => String(call[0]))
+ .find((entry) => entry.includes('orca_relay_client_accept_completed'))
+ expect(line).toBeDefined()
+ const event = JSON.parse(line!) as {
+ role: string
+ cellId: string
+ region: string
+ credentialKind: string
+ stageMs: Record
+ totalMs: number
+ relayHostIdDigest: string
+ }
+ expect(event.credentialKind).toBe('resume')
+ // Joins the line back to the emitting process, like the runtime metrics event.
+ expect(event).toMatchObject({ role: 'cell', cellId: config.cellId, region: 'us-central1' })
+ expect(Object.keys(event.stageMs).sort()).toEqual([
+ 'activity',
+ 'assignment',
+ 'attach',
+ 'basis',
+ 'credential'
+ ])
+ for (const stage of Object.values(event.stageMs)) expect(stage).toBeGreaterThanOrEqual(0)
+ // The stages tile the accept end to end: every millisecond is attributed.
+ const summed = Object.values(event.stageMs).reduce((total, stage) => total + stage, 0)
+ expect(summed).toBe(event.totalMs)
+ expect(event.relayHostIdDigest).toMatch(/^[0-9a-f]{12}$/)
+ expect(line).not.toContain(identity.relayHostId)
+ } finally {
+ log.mockRestore()
+ h.registry.drain(0)
+ vi.advanceTimersByTime(0)
+ }
+ })
+})
+
+// Fires one heartbeat and returns the `t` of the ping it sent, which is the only
+// echo the registry will time.
+async function advanceToPing(control: FakeSocket, clock: { now: number }): Promise {
+ clock.now += RELAY_PROTOCOL_LIMITS.controlPingIntervalMs
+ await vi.advanceTimersByTimeAsync(RELAY_PROTOCOL_LIMITS.controlPingIntervalMs)
+ const ping = control.send.mock.calls
+ .filter((call) => String(call[0]).includes('"type":"ping"'))
+ .at(-1)!
+ return (JSON.parse(String(ping[0])) as { t: number }).t
+}
+
+describe('control round-trip sampling', () => {
+ beforeEach(() => vi.useFakeTimers())
+ afterEach(() => {
+ vi.clearAllTimers()
+ vi.useRealTimers()
+ })
+
+ it('logs a host once at the fourth sample and not again within the hour', async () => {
+ const clock = { now: 1_700_000_000_000 }
+ const h = harness({ now: () => clock.now })
+ const control = await activeHost(h)
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
+ const rttLines = (): string[] =>
+ log.mock.calls
+ .map((call) => String(call[0]))
+ .filter((entry) => entry.includes('orca_relay_host_control_rtt'))
+ // One heartbeat, then the desktop's echo of that ping's own `t` 40 ms later.
+ const roundTrip = async (): Promise => {
+ const pingAt = await advanceToPing(control, clock)
+ clock.now += 40
+ control.emit('message', JSON.stringify({ type: 'pong', t: pingAt }), false)
+ }
+ try {
+ for (let round = 0; round < 3; round++) await roundTrip()
+ expect(h.observer.recordControlRtt).toHaveBeenCalledTimes(3)
+ expect(rttLines()).toHaveLength(0)
+
+ await roundTrip()
+ expect(h.observer.recordControlRtt).toHaveBeenLastCalledWith(40)
+ expect(rttLines()).toHaveLength(1)
+ expect(JSON.parse(rttLines()[0]!)).toMatchObject({
+ event: 'orca_relay_host_control_rtt',
+ role: 'cell',
+ cellId: config.cellId,
+ region: 'us-central1',
+ rttMsMedian: 40,
+ sampleCount: 4
+ })
+ expect(rttLines()[0]).not.toContain(identity.relayHostId)
+
+ // Later samples keep feeding the fleet metric, but stay silent for an hour.
+ for (let round = 0; round < 8; round++) await roundTrip()
+ expect(h.observer.recordControlRtt).toHaveBeenCalledTimes(12)
+ expect(rttLines()).toHaveLength(1)
+
+ const elapsedStart = clock.now
+ while (clock.now - elapsedStart < 60 * 60 * 1000) await roundTrip()
+ expect(rttLines()).toHaveLength(2)
+ } finally {
+ log.mockRestore()
+ h.registry.drain(0)
+ vi.advanceTimersByTime(0)
+ }
+ })
+
+ it('ignores a pong that answers no outstanding ping', async () => {
+ const clock = { now: 1_700_000_000_000 }
+ const h = harness({ now: () => clock.now })
+ const control = await activeHost(h)
+ try {
+ // Nothing has been pinged yet, so even a plausible echo is not a round trip.
+ control.emit('message', JSON.stringify({ type: 'pong' }), false)
+ control.emit('message', JSON.stringify({ type: 'pong', t: 'later' }), false)
+ control.emit('message', JSON.stringify({ type: 'pong', t: clock.now }), false)
+ control.emit('message', JSON.stringify({ type: 'pong', t: clock.now - 10 }), false)
+ expect(h.observer.recordControlRtt).not.toHaveBeenCalled()
+
+ const pingAt = await advanceToPing(control, clock)
+ // A guessed timestamp is not the outstanding ping's `t`, so it is dropped.
+ control.emit('message', JSON.stringify({ type: 'pong', t: pingAt - 1 }), false)
+ control.emit('message', JSON.stringify({ type: 'pong', t: pingAt + 1 }), false)
+ expect(h.observer.recordControlRtt).not.toHaveBeenCalled()
+
+ clock.now += 10
+ control.emit('message', JSON.stringify({ type: 'pong', t: pingAt }), false)
+ expect(h.observer.recordControlRtt).toHaveBeenCalledWith(10)
+ } finally {
+ h.registry.drain(0)
+ vi.advanceTimersByTime(0)
+ }
+ })
+
+ it('records one sample per ping however many pongs a host floods', async () => {
+ const clock = { now: 1_700_000_000_000 }
+ const h = harness({ now: () => clock.now })
+ const control = await activeHost(h)
+ const log = vi.spyOn(console, 'log').mockImplementation(() => undefined)
+ try {
+ const pingAt = await advanceToPing(control, clock)
+ clock.now += 12
+ for (let flood = 0; flood < 5_000; flood++) {
+ control.emit('message', JSON.stringify({ type: 'pong', t: pingAt }), false)
+ control.emit('message', JSON.stringify({ type: 'pong', t: clock.now }), false)
+ }
+ // One answered ping is one process-wide sample and one per-session sample, so
+ // neither the metric window nor the hourly log line can be flooded.
+ expect(h.observer.recordControlRtt).toHaveBeenCalledTimes(1)
+ expect(h.observer.recordControlRtt).toHaveBeenCalledWith(12)
+ expect(
+ log.mock.calls.filter((call) => String(call[0]).includes('orca_relay_host_control_rtt'))
+ ).toHaveLength(0)
+ } finally {
+ log.mockRestore()
+ h.registry.drain(0)
+ vi.advanceTimersByTime(0)
+ }
+ })
+})
+
describe('control lease jitter', () => {
beforeEach(() => vi.useFakeTimers())
afterEach(() => {
diff --git a/cloud/apps/relay/src/host-session-registry.test.ts b/cloud/apps/relay/src/host-session-registry.test.ts
index f632d5d43581..920faa6f4b84 100644
--- a/cloud/apps/relay/src/host-session-registry.test.ts
+++ b/cloud/apps/relay/src/host-session-registry.test.ts
@@ -3,6 +3,7 @@ import {
ASSIGNMENT_LIMITS,
CONTROL_CONTINUITY_LIMITS,
RELAY_CLOSE_CODE,
+ RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS,
RELAY_PROTOCOL_LIMITS
} from '@orca-cloud/relay-contract'
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'
@@ -1005,3 +1006,115 @@ describe('control lease recovery after the session is gone', () => {
}
})
})
+
+describe('host hello ack pending connections', () => {
+ const DETAILS = new Set([RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS])
+ const LEGACY_ENTRY = { connId: 'conn-1', connTicket: 'T'.repeat(43) }
+ const DETAILED_ENTRY = { ...LEGACY_ENTRY, kind: 'invite', relayDeviceId: 'device-1' }
+
+ beforeEach(() => vi.useFakeTimers())
+ afterEach(() => {
+ vi.clearAllTimers()
+ vi.useRealTimers()
+ })
+
+ function newRegistry(): ReturnType {
+ return createRegistry(
+ vi
+ .fn()
+ .mockResolvedValue('control:production-gce-c3:1')
+ )
+ }
+
+ function addPendingConnection(session: HostSession): void {
+ session.pendingConns.set('conn-1', {
+ ...LEGACY_ENTRY,
+ reservation: {
+ userId: identity.sub,
+ relayHostId: identity.relayHostId,
+ credentialKind: 'invite',
+ relayDeviceId: 'device-1'
+ },
+ client: new FakeSocket() as unknown as WebSocket,
+ attachTimer: setTimeout(() => {}, 60_000),
+ credentialActivityId: null
+ } as unknown as Parameters[1])
+ }
+
+ function sentAck(socket: FakeSocket): Record {
+ const acks = socket.send.mock.calls
+ .map((call) => JSON.parse(String(call[0])) as Record)
+ .filter((message) => message.type === 'host-hello-ack')
+ return acks.at(-1)!
+ }
+
+ function sessionOf(registry: HostSessionRegistry): HostSession {
+ return registry.get({ userId: identity.sub, relayHostId: identity.relayHostId })!
+ }
+
+ async function ackFor(capabilities?: ReadonlySet): Promise> {
+ const { registry, activate } = newRegistry()
+ const socket = new FakeSocket()
+ registry.acceptControl(
+ socket as unknown as WebSocket,
+ identity,
+ undefined,
+ capabilities ?? new Set()
+ )
+ await activate(socket as unknown as WebSocket, identity, null, 1, false, 1)
+ const session = sessionOf(registry)
+ addPendingConnection(session)
+ socket.send.mockClear()
+ ;(registry as unknown as { sendHelloAck(session: HostSession): void }).sendHelloAck(session)
+ return sentAck(socket)
+ }
+
+ async function ackAfterRebind(
+ first: ReadonlySet,
+ successor: ReadonlySet
+ ): Promise<{ opening: Record; rebound: Record }> {
+ const { registry, activate } = newRegistry()
+ const opening = new FakeSocket()
+ registry.acceptControl(opening as unknown as WebSocket, identity, undefined, first)
+ await activate(opening as unknown as WebSocket, identity, null, 1, false, 1)
+ const session = sessionOf(registry)
+ addPendingConnection(session)
+ opening.send.mockClear()
+ ;(registry as unknown as { sendHelloAck(session: HostSession): void }).sendHelloAck(session)
+
+ const rebound = new FakeSocket()
+ registry.acceptControl(rebound as unknown as WebSocket, identity, undefined, successor)
+ await activate(rebound as unknown as WebSocket, identity, session, 1, true, 1)
+ return { opening: sentAck(opening), rebound: sentAck(rebound) }
+ }
+
+ it('states the pending kind and device to a host that advertised it can read them', async () => {
+ const ack = await ackFor(DETAILS)
+
+ expect(ack.pendingConns).toEqual([DETAILED_ENTRY])
+ })
+
+ it('restates only the identifiers to a host that never advertised the capability', async () => {
+ // A shipped host parses these entries strictly, so an unannounced key fails
+ // the whole ack parse and kills a control that was working.
+ const ack = await ackFor()
+
+ expect(ack.pendingConns).toEqual([LEGACY_ENTRY])
+ })
+
+ it('downgrades the restated entry when the successor control drops the capability', async () => {
+ // The capability belongs to the socket, not the session: a rebind can land a
+ // control whose decoder is older than the one that opened the session.
+ const { opening, rebound } = await ackAfterRebind(DETAILS, new Set())
+
+ expect(opening.pendingConns).toEqual([DETAILED_ENTRY])
+ expect(rebound.pendingConns).toEqual([LEGACY_ENTRY])
+ })
+
+ it('upgrades the restated entry when the successor control adds the capability', async () => {
+ const { opening, rebound } = await ackAfterRebind(new Set(), DETAILS)
+
+ expect(opening.pendingConns).toEqual([LEGACY_ENTRY])
+ expect(rebound.pendingConns).toEqual([DETAILED_ENTRY])
+ })
+})
diff --git a/cloud/apps/relay/src/host-session-registry.ts b/cloud/apps/relay/src/host-session-registry.ts
index 1b7ed3df4afe..3b4e616a692c 100644
--- a/cloud/apps/relay/src/host-session-registry.ts
+++ b/cloud/apps/relay/src/host-session-registry.ts
@@ -1,6 +1,7 @@
import { createHash, createHmac, randomBytes, randomUUID, timingSafeEqual } from 'node:crypto'
import {
ASSIGNMENT_LIMITS,
+ RELAY_DEFAULT_REGION,
AuthRefreshSchema,
buildHostChallengePlaintext,
buildHostProofMacInput,
@@ -13,9 +14,11 @@ import {
HostChallengeAckSchema,
HostHelloSchema,
InviteCreateSchema,
+ RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS,
RELAY_PROTOCOL_LIMITS,
RELAY_CLOSE_CODE,
- type RelayHostCloseReason
+ type RelayHostCloseReason,
+ type RelayRegion
} from '@orca-cloud/relay-contract'
import nacl from 'tweetnacl'
import type WebSocket from 'ws'
@@ -29,7 +32,12 @@ import {
import { HostCloseReasonMemory } from './host-close-reason-memory.js'
import { relayHostLogDigest } from './relay-host-log-digest.js'
import type { RelayTokenClaims } from './relay-token-verifier.js'
-import type { RelayClientAcceptStage, RelayRuntimeObserver } from './relay-observability.js'
+import {
+ percentile,
+ type RelayClientAcceptStage,
+ type RelayClientAcceptTimedStage,
+ type RelayRuntimeObserver
+} from './relay-observability.js'
import type { PendingHostDataReservation } from './relay-connection-ledger.js'
import { closeRelayWebSocket } from './relay-websocket-close.js'
import { ProcessQueuedByteBudget, wireSplice } from './splice-forwarder.js'
@@ -45,6 +53,20 @@ function printableCloseReason(reason: Buffer | string): string {
type VerifyRelayToken = (token: string) => Promise
type HostState = 'proving' | 'active' | 'orphaned' | 'drain-only' | 'closed'
+// A host's distance to its cell moves on the scale of a rehome, not a heartbeat,
+// so a short window is enough to ride out one stalled ping.
+const CONTROL_RTT_WINDOW = 8
+const CONTROL_RTT_LOG_SAMPLE_THRESHOLD = 4
+const CONTROL_RTT_LOG_INTERVAL_MS = 60 * 60 * 1000
+// A pong claiming a multi-minute round trip is clock skew, not distance.
+const CONTROL_RTT_MAX_PLAUSIBLE_MS = 120_000
+
+// Wall clock can step backwards mid-accept; a negative latency would poison the
+// percentiles it feeds.
+function nonNegativeMs(elapsedMs: number): number {
+ return Math.max(0, elapsedMs)
+}
+
const CONTROL_ACTIVITY_RENEWAL_INTERVAL_MS = RELAY_PROTOCOL_LIMITS.controlPingIntervalMs * 2
// Preserve the existing 75s renewal runway after doubling the successful-call interval.
const CONTROL_ACTIVITY_LEASE_MS =
@@ -68,6 +90,10 @@ export type HostSession = {
orphanTimer: ReturnType | null
heartbeatTimer: ReturnType | null
lastPongAt: number
+ // The `t` of the ping still waiting for its echo; null once one has answered it.
+ pendingPingAt: number | null
+ controlRttSamplesMs: number[]
+ controlRttLoggedAt: number | null
activityRenewalDueAt: number
activityRenewalAttempt: number
activityRenewalCompletedAttempt: number
@@ -95,6 +121,15 @@ type PendingConnection = {
attachTimer: ReturnType
credentialActivityId: string | null
capacityReservation?: PendingHostDataReservation
+ timing: ClientAcceptTiming
+}
+
+// Carries the phone-side accept clock across to the desktop's data leg, which
+// lands in a separate call and is the only place the accept is known to succeed.
+type ClientAcceptTiming = {
+ startedAt: number
+ connOpenAt: number
+ stageMs: Record
}
function decodeCanonicalBase64(value: string, bytes: number): Uint8Array | null {
@@ -146,6 +181,7 @@ export class HostSessionRegistry {
// but a signed-out desktop never comes back, so the phone that asks minutes
// later would otherwise find nothing to explain its rejection with.
private readonly hostCloseReasons = new HostCloseReasonMemory(() => this.now())
+ private readonly hostCapabilities = new WeakMap>()
private draining = false
constructor(
@@ -192,6 +228,17 @@ export class HostSessionRegistry {
)
return true
}
+ const stageMs: Record = {
+ assignment: 0,
+ credential: 0,
+ activity: 0
+ }
+ let stageCursor = acceptStartedAt
+ const markStage = (stage: RelayClientAcceptStage): void => {
+ const at = this.now()
+ stageMs[stage] = at - stageCursor
+ stageCursor = at
+ }
if (this.config.role === 'cell') {
// Each lookup is its own pooled round trip; stop between them once the phone
// has left instead of running the rest of the chain for nobody.
@@ -212,6 +259,7 @@ export class HostSessionRegistry {
}
if (abandonedByClient('assignment')) return
}
+ markStage('assignment')
const reservation = await this.store.reserveCredential(hostId, credential)
if (!reservation) {
capacityReservation?.release()
@@ -221,6 +269,7 @@ export class HostSessionRegistry {
}
this.observer.recordAuth(true)
if (abandonedByClient('credential', () => this.failReservationBestEffort(reservation))) return
+ markStage('credential')
const sessionKey = this.key(reservation.userId, hostId)
const session = this.sessions.get(sessionKey)
if (
@@ -275,6 +324,7 @@ export class HostSessionRegistry {
) {
return
}
+ markStage('activity')
const attachTimer = setTimeout(() => {
session.pendingConns.delete(connId)
capacityReservation?.release()
@@ -289,7 +339,10 @@ export class HostSessionRegistry {
client: socket,
attachTimer,
credentialActivityId,
- capacityReservation
+ capacityReservation,
+ // Attach starts where the activity stage ended, so the conn-open send is
+ // charged to it and no wall-clock gap goes unattributed.
+ timing: { startedAt: acceptStartedAt, connOpenAt: stageCursor, stageMs }
}
capacityReservation?.bind(connId)
session.pendingConns.set(connId, pending)
@@ -336,6 +389,7 @@ export class HostSessionRegistry {
return false
}
this.observer.recordAuth(true)
+ const attachedAt = this.now()
clearTimeout(pending.attachTimer)
session.pendingConns.delete(connId)
session.activeConnIds.add(connId)
@@ -409,6 +463,7 @@ export class HostSessionRegistry {
close()
return false
}
+ const helloAt = this.now()
send(pending.client, 'relay-hello', {
ok: true,
credentialKind: pending.reservation.credentialKind,
@@ -424,14 +479,92 @@ export class HostSessionRegistry {
}
: {})
})
+ this.recordClientAcceptCompleted(session, pending, attachedAt, helloAt)
return true
}
+ // The stages tile the whole accept, so their sum is the total minus only the
+ // clamping above: `basis` is the splice lease and connection-basis writes that
+ // land between the host data leg and relay-hello.
+ private recordClientAcceptCompleted(
+ session: HostSession,
+ pending: PendingConnection,
+ attachedAt: number,
+ helloAt: number
+ ): void {
+ const stageMs: Record = {
+ assignment: nonNegativeMs(pending.timing.stageMs.assignment),
+ credential: nonNegativeMs(pending.timing.stageMs.credential),
+ activity: nonNegativeMs(pending.timing.stageMs.activity),
+ attach: nonNegativeMs(attachedAt - pending.timing.connOpenAt),
+ basis: nonNegativeMs(helloAt - attachedAt)
+ }
+ const totalMs = nonNegativeMs(helloAt - pending.timing.startedAt)
+ this.observer.recordClientAcceptCompleted?.({ totalMs, stageMs })
+ console.log(
+ JSON.stringify({
+ event: 'orca_relay_client_accept_completed',
+ ...this.logIdentity(),
+ credentialKind: pending.reservation.credentialKind,
+ stageMs,
+ totalMs,
+ relayHostIdDigest: relayHostLogDigest(session.relayHostId)
+ })
+ )
+ }
+
+ // Matches the runtime metrics event so a log line and a metric point can be
+ // joined back to the process that emitted them.
+ private logIdentity(): { role: string; cellId: string; region: RelayRegion } {
+ return {
+ role: this.config.role,
+ cellId: this.config.cellId,
+ region: this.config.region ?? RELAY_DEFAULT_REGION
+ }
+ }
+
+ // Every desktop build already echoes the ping's `t`, so a pong is only timed when
+ // it answers the outstanding ping: at most one sample per ping this cell sent,
+ // however many a host floods. A pong that lost the race to the next ping is
+ // dropped here but still counts as proof of life for the silence watchdog.
+ private recordControlRtt(session: HostSession, echoedPingAt: unknown): void {
+ if (typeof echoedPingAt !== 'number' || echoedPingAt !== session.pendingPingAt) return
+ session.pendingPingAt = null
+ const now = this.now()
+ const rttMs = now - echoedPingAt
+ if (rttMs < 0 || rttMs > CONTROL_RTT_MAX_PLAUSIBLE_MS) return
+ this.observer.recordControlRtt?.(rttMs)
+ const samples = session.controlRttSamplesMs
+ samples.push(rttMs)
+ if (samples.length > CONTROL_RTT_WINDOW) samples.shift()
+ if (samples.length < CONTROL_RTT_LOG_SAMPLE_THRESHOLD) return
+ if (
+ session.controlRttLoggedAt !== null &&
+ now - session.controlRttLoggedAt < CONTROL_RTT_LOG_INTERVAL_MS
+ ) {
+ return
+ }
+ session.controlRttLoggedAt = now
+ console.log(
+ JSON.stringify({
+ event: 'orca_relay_host_control_rtt',
+ ...this.logIdentity(),
+ relayHostIdDigest: relayHostLogDigest(session.relayHostId),
+ rttMsMedian: percentile(samples, 0.5),
+ sampleCount: samples.length
+ })
+ )
+ }
+
acceptControl(
socket: WebSocket,
identity: RelayTokenClaims,
- connectionInclusionWatermark?: number
+ connectionInclusionWatermark?: number,
+ hostCapabilities?: ReadonlySet
): void {
+ // Keyed by socket, not session: a rebind swaps the session's socket, and the
+ // successor's own advertisement is the only one that describes its decoder.
+ if (hostCapabilities?.size) this.hostCapabilities.set(socket, hostCapabilities)
if (this.draining) {
socket.close(RELAY_CLOSE_CODE.DRAINING, 'relay draining')
return
@@ -790,6 +923,7 @@ export class HostSessionRegistry {
existing.appVersion = appVersion
existing.leaseExpiresAt = this.controlLeaseExpiresAt()
existing.lastPongAt = this.now()
+ existing.pendingPingAt = null
existing.activityRenewalDueAt =
this.now() + RELAY_PROTOCOL_LIMITS.controlPingIntervalMs
this.wireActiveControl(existing)
@@ -843,6 +977,9 @@ export class HostSessionRegistry {
orphanTimer: null,
heartbeatTimer: null,
lastPongAt: this.now(),
+ pendingPingAt: null,
+ controlRttSamplesMs: [],
+ controlRttLoggedAt: null,
activityRenewalDueAt: this.now() + RELAY_PROTOCOL_LIMITS.controlPingIntervalMs,
activityRenewalAttempt: 0,
activityRenewalCompletedAttempt: 0,
@@ -900,6 +1037,7 @@ export class HostSessionRegistry {
const parsed = JSON.parse(raw.toString()) as Record
if (parsed.type === 'pong') {
session.lastPongAt = this.now()
+ this.recordControlRtt(session, parsed.t)
return
}
if (parsed.type === 'auth-refresh') {
@@ -1047,11 +1185,18 @@ export class HostSessionRegistry {
session.socket.close(RELAY_CLOSE_CODE.DRAINING, 'control lease expired')
return
}
+ session.pendingPingAt = now
send(session.socket, 'ping', { t: now })
}
private sendHelloAck(session: HostSession): void {
if (!session.socket) return
+ // Without these a host that missed the conn-open cannot dial the pending
+ // connection: it would have to guess the pairing kind and the device the
+ // relay authorized. Only sent to a host that said it can read them.
+ const details = this.hostCapabilities
+ .get(session.socket)
+ ?.has(RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS)
send(session.socket, 'host-hello-ack', {
v: 1,
generation: session.generation,
@@ -1060,7 +1205,13 @@ export class HostSessionRegistry {
activeConnIds: [...session.activeConnIds],
pendingConns: [...session.pendingConns.values()].map((pending) => ({
connId: pending.connId,
- connTicket: pending.connTicket
+ connTicket: pending.connTicket,
+ ...(details
+ ? {
+ kind: pending.reservation.credentialKind,
+ relayDeviceId: pending.reservation.relayDeviceId
+ }
+ : {})
}))
})
}
diff --git a/cloud/apps/relay/src/postgres-schema-startup.ts b/cloud/apps/relay/src/postgres-schema-startup.ts
index ba9efc6a7923..22a75cd94657 100644
--- a/cloud/apps/relay/src/postgres-schema-startup.ts
+++ b/cloud/apps/relay/src/postgres-schema-startup.ts
@@ -49,6 +49,20 @@ function concurrentCreateCollision(
return false
}
+const ALTER_TABLE_ADD_CONSTRAINT =
+ /^\s*ALTER\s+TABLE\s+\S+\s+ADD\s+CONSTRAINT\b/i
+
+// Postgres has no `ADD CONSTRAINT IF NOT EXISTS`, so a re-run and a concurrent
+// startup both land on 42710 once the constraint exists. Unlike a CREATE race
+// this is terminal, not transient: retrying only repeats it, so the statement
+// counts as applied.
+function constraintAlreadyApplied(error: unknown, statement: string): boolean {
+ return (
+ ALTER_TABLE_ADD_CONSTRAINT.test(statement) &&
+ (error as { code?: unknown }).code === '42710'
+ )
+}
+
function retryableSchemaError(error: unknown, statement: string): boolean {
const value = error as { code?: unknown; constraint?: unknown }
return (
@@ -73,6 +87,7 @@ export async function applyPostgresSchema(
await query(statement)
break
} catch (error) {
+ if (constraintAlreadyApplied(error, statement)) break
const code = String((error as { code?: unknown }).code)
const remainingMs = deadlineAt - now()
const retryable = retryableSchemaError(error, statement)
diff --git a/cloud/apps/relay/src/regional-host-drain-app.test.ts b/cloud/apps/relay/src/regional-host-drain-app.test.ts
index 1cd349025207..e2a33a07bb08 100644
--- a/cloud/apps/relay/src/regional-host-drain-app.test.ts
+++ b/cloud/apps/relay/src/regional-host-drain-app.test.ts
@@ -315,6 +315,7 @@ describe('regional rehome director controls', () => {
notBefore: 100,
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60_000,
confirmation: 'ENABLE_REGIONAL_REHOMING'
}
@@ -343,6 +344,14 @@ describe('regional rehome director controls', () => {
'deploy-token',
{ ...apply, confirmation: 'DISABLE_REGIONAL_REHOMING' }
)).status).toBe(400)
+ // The per-host cooldown is part of the durable shape an operator must state.
+ const { hostCooldownMs: _omitted, ...withoutCooldown } = apply
+ expect((await postPath(
+ app,
+ '/v1/admin/regional-rehome-control',
+ 'deploy-token',
+ withoutCooldown
+ )).status).toBe(400)
})
it('probes dedicated trust twice and returns only aggregate proof', async () => {
@@ -411,6 +420,78 @@ describe('regional rehome director controls', () => {
expect(JSON.stringify(responseBody)).not.toContain('rehome-token')
})
+ it('probes a source cell in any region, not only the default one', async () => {
+ // Rehoming moves hosts in both directions, so an asia-east2 cell is a
+ // source too and its trust has to be provable the same way.
+ const cellDeploymentStatus = vi.fn().mockResolvedValue({
+ cellId: 'production-gce-c27',
+ cellUrl: 'https://c27.relay.example.test',
+ region: 'asia-east2',
+ runtime: {
+ cellIncarnation,
+ ready: true,
+ heartbeatFresh: true,
+ regionalRehomeProtocol: 1
+ }
+ })
+ const app = createRelayApp(config({ role: 'director', cellId: 'director' }), {
+ store: {} as never,
+ assignments: { cellDeploymentStatus } as never,
+ drain: vi.fn(),
+ regionalRehomeIdentityToken: vi.fn(async () => 'rehome-token'),
+ regionalRehomeFetch: (async () =>
+ Response.json({
+ v: 1,
+ outcome: 'host-not-connected',
+ sharedRuntimeIdentityRejected: true
+ })) as typeof fetch,
+ ready: vi.fn(async () => true)
+ })
+
+ const response = await postPath(
+ app,
+ '/v1/admin/regional-rehome-trust-probe',
+ 'deploy-token',
+ { v: 1, sourceCellId: 'production-gce-c27', sourceCellIncarnation: cellIncarnation }
+ )
+
+ expect(response.status).toBe(200)
+ expect(await response.json()).toMatchObject({ proven: true })
+ })
+
+ it('still refuses a trust probe against a cell without the drain protocol', async () => {
+ const cellDeploymentStatus = vi.fn().mockResolvedValue({
+ cellId: 'production-gce-c27',
+ cellUrl: 'https://c27.relay.example.test',
+ region: 'asia-east2',
+ runtime: {
+ cellIncarnation,
+ ready: true,
+ heartbeatFresh: true,
+ regionalRehomeProtocol: 0
+ }
+ })
+ const sourceFetch = vi.fn()
+ const app = createRelayApp(config({ role: 'director', cellId: 'director' }), {
+ store: {} as never,
+ assignments: { cellDeploymentStatus } as never,
+ drain: vi.fn(),
+ regionalRehomeIdentityToken: vi.fn(async () => 'rehome-token'),
+ regionalRehomeFetch: sourceFetch,
+ ready: vi.fn(async () => true)
+ })
+
+ const response = await postPath(
+ app,
+ '/v1/admin/regional-rehome-trust-probe',
+ 'deploy-token',
+ { v: 1, sourceCellId: 'production-gce-c27', sourceCellIncarnation: cellIncarnation }
+ )
+
+ expect(response.status).toBe(409)
+ expect(sourceFetch).not.toHaveBeenCalled()
+ })
+
it('restricts trust probes to deploy authorization and strict input', async () => {
const app = createRelayApp(config({ role: 'director', cellId: 'director' }), {
store: {} as never,
diff --git a/cloud/apps/relay/src/regional-rehome-constraint-migration-postgres.test.ts b/cloud/apps/relay/src/regional-rehome-constraint-migration-postgres.test.ts
new file mode 100644
index 000000000000..4e9ccda5e13f
--- /dev/null
+++ b/cloud/apps/relay/src/regional-rehome-constraint-migration-postgres.test.ts
@@ -0,0 +1,195 @@
+import pg from 'pg'
+import { afterAll, beforeEach, describe, expect, it } from 'vitest'
+import {
+ openRelayDatabase,
+ REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS,
+ type RelayDatabase
+} from './database.js'
+
+const databaseUrl = process.env.ORCA_RELAY_TEST_POSTGRES_URL
+const describePostgres = databaseUrl ? describe : describe.skip
+const schema = 'relay_rehome_constraint_migration_test'
+
+// The shape shipped before rehoming became bidirectional: a single-region
+// column check that Postgres auto-names.
+const LEGACY_ATTEMPTS_TABLE = `
+CREATE TABLE relay_region_rehome_attempts (
+ attempt_id TEXT PRIMARY KEY,
+ user_id TEXT NOT NULL,
+ relay_host_id TEXT NOT NULL,
+ preferred_region TEXT NOT NULL CHECK (preferred_region = 'asia-east2'),
+ source_cell_id TEXT NOT NULL,
+ source_cell_incarnation TEXT NOT NULL,
+ target_cell_id TEXT NOT NULL,
+ target_cell_incarnation TEXT NOT NULL,
+ previous_epoch BIGINT NOT NULL,
+ assignment_epoch BIGINT NOT NULL,
+ drain_grace_ms BIGINT NOT NULL,
+ send_attempts BIGINT NOT NULL,
+ last_send_attempt_at BIGINT,
+ drain_receipt_at BIGINT,
+ drain_outcome TEXT CHECK (
+ drain_outcome IN ('accepted', 'already-accepted', 'host-not-connected')
+ ),
+ completed_at BIGINT,
+ aborted_at BIGINT,
+ created_at BIGINT NOT NULL,
+ updated_at BIGINT NOT NULL,
+ UNIQUE (user_id, relay_host_id, assignment_epoch)
+)`
+
+// The control row as it shipped before the per-host cooldown existed.
+const LEGACY_CONTROL_TABLE = `
+CREATE TABLE relay_region_rehome_control (
+ control_id TEXT PRIMARY KEY,
+ generation BIGINT NOT NULL,
+ enabled BIGINT NOT NULL,
+ observation_started_at BIGINT NOT NULL,
+ not_before BIGINT NOT NULL,
+ rate_per_minute BIGINT NOT NULL,
+ preference_max_age_ms BIGINT NOT NULL,
+ drain_grace_ms BIGINT NOT NULL,
+ updated_at BIGINT NOT NULL
+)`
+
+const attemptValues = (attemptId: string, preferredRegion: string): unknown[] => [
+ attemptId,
+ 'user-1',
+ 'abcdefghijklmnop',
+ preferredRegion,
+ 'cell-source',
+ '11111111-1111-4111-8111-111111111111',
+ 'cell-target',
+ '22222222-2222-4222-8222-222222222222',
+ 1,
+ Number(attemptId.at(-1)),
+ 0,
+ 0,
+ 1_000_000,
+ 1_000_000
+]
+
+const INSERT_ATTEMPT = `INSERT INTO relay_region_rehome_attempts
+ (attempt_id, user_id, relay_host_id, preferred_region, source_cell_id,
+ source_cell_incarnation, target_cell_id, target_cell_incarnation,
+ previous_epoch, assignment_epoch, drain_grace_ms, send_attempts,
+ created_at, updated_at)
+ VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13, $14)`
+
+describePostgres('PostgreSQL regional rehome constraint migration', () => {
+ let scopedUrl = ''
+
+ async function withClient(
+ operation: (client: pg.Client) => Promise
+ ): Promise {
+ const client = new pg.Client({ connectionString: databaseUrl })
+ await client.connect()
+ try {
+ await operation(client)
+ } finally {
+ await client.end()
+ }
+ }
+
+ beforeEach(async () => {
+ await withClient(async (client) => {
+ await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
+ await client.query(`CREATE SCHEMA ${schema}`)
+ await client.query(`SET search_path = ${schema}`)
+ await client.query(LEGACY_ATTEMPTS_TABLE)
+ await client.query(LEGACY_CONTROL_TABLE)
+ await client.query(
+ `INSERT INTO relay_region_rehome_control
+ (control_id, generation, enabled, observation_started_at, not_before,
+ rate_per_minute, preference_max_age_ms, drain_grace_ms, updated_at)
+ VALUES ('global', 3, 0, 1, 0, 10, 86400000, 60000, 1)`
+ )
+ // Production data the replacement constraint has to validate.
+ await client.query(INSERT_ATTEMPT, attemptValues('attempt-1', 'asia-east2'))
+ })
+ const url = new URL(databaseUrl!)
+ url.searchParams.set('options', `-c search_path=${schema}`)
+ scopedUrl = url.toString()
+ })
+
+ afterAll(async () => {
+ await withClient(async (client) => {
+ await client.query(`DROP SCHEMA IF EXISTS ${schema} CASCADE`)
+ })
+ })
+
+ it('upgrades a legacy single-region constraint in place', async () => {
+ const database = await openRelayDatabase({ databaseUrl: scopedUrl, dataDir: '' })
+ try {
+ await withClient(async (client) => {
+ await client.query(`SET search_path = ${schema}`)
+ await client.query(INSERT_ATTEMPT, attemptValues('attempt-2', 'us-central1'))
+ await expect(
+ client.query(INSERT_ATTEMPT, attemptValues('attempt-3', 'europe-west1'))
+ ).rejects.toMatchObject({ code: '23514' })
+ const constraints = await client.query(
+ `SELECT conname FROM pg_constraint
+ WHERE conrelid = 'relay_region_rehome_attempts'::regclass
+ AND conname LIKE '%preferred_region%'
+ ORDER BY conname`
+ )
+ expect(constraints.rows).toEqual([
+ { conname: 'relay_region_rehome_attempts_preferred_region_valid' }
+ ])
+ // The existing control row keeps its tuning and gains the cooldown.
+ const control = await client.query(
+ `SELECT generation, preference_max_age_ms, host_cooldown_ms
+ FROM relay_region_rehome_control WHERE control_id = 'global'`
+ )
+ expect(control.rows).toEqual([
+ {
+ generation: '3',
+ preference_max_age_ms: '86400000',
+ host_cooldown_ms: String(REGIONAL_REHOME_DEFAULT_HOST_COOLDOWN_MS)
+ }
+ ])
+ })
+ } finally {
+ await database.close()
+ }
+ })
+
+ it('upgrades once across concurrent startups', async () => {
+ const results = await Promise.allSettled(
+ Array.from(
+ { length: 5 },
+ async (): Promise =>
+ await openRelayDatabase({ databaseUrl: scopedUrl, dataDir: '' })
+ )
+ )
+ const databases = results.flatMap((result) =>
+ result.status === 'fulfilled' ? [result.value] : []
+ )
+ await Promise.all(databases.map(async (database) => await database.close()))
+
+ expect(
+ results.flatMap((result) =>
+ result.status === 'rejected'
+ ? [
+ {
+ code: (result.reason as { code?: unknown }).code,
+ message: String(result.reason)
+ }
+ ]
+ : []
+ )
+ ).toEqual([])
+ await withClient(async (client) => {
+ await client.query(`SET search_path = ${schema}`)
+ await client.query(INSERT_ATTEMPT, attemptValues('attempt-4', 'us-central1'))
+ const constraints = await client.query(
+ `SELECT conname FROM pg_constraint
+ WHERE conrelid = 'relay_region_rehome_attempts'::regclass
+ AND conname LIKE '%preferred_region%'`
+ )
+ expect(constraints.rows).toEqual([
+ { conname: 'relay_region_rehome_attempts_preferred_region_valid' }
+ ])
+ })
+ }, 60_000)
+})
diff --git a/cloud/apps/relay/src/regional-rehome-postgres.test.ts b/cloud/apps/relay/src/regional-rehome-postgres.test.ts
index d36e26ecd68b..44f3b3434af7 100644
--- a/cloud/apps/relay/src/regional-rehome-postgres.test.ts
+++ b/cloud/apps/relay/src/regional-rehome-postgres.test.ts
@@ -81,6 +81,153 @@ describePostgres('PostgreSQL regional rehoming', () => {
expect(await context.store.claimRegionalRehome()).not.toBeNull()
})
+ it('moves a us-central1 host onto a cell in its preferred asia-east2 region', async () => {
+ const context = await fixture()
+
+ const attempt = await context.store.claimRegionalRehome()
+ expect(attempt).toMatchObject({
+ preferredRegion: 'asia-east2',
+ sourceCellId: context.source.id,
+ targetCellId: context.target.id
+ })
+ expect(await primary.query(
+ `SELECT preferred_region, source_cell_id, target_cell_id
+ FROM relay_region_rehome_attempts WHERE user_id = ?`,
+ [context.identity.userId]
+ )).toEqual([{
+ preferred_region: 'asia-east2',
+ source_cell_id: context.source.id,
+ target_cell_id: context.target.id
+ }])
+ })
+
+ it('moves an asia-east2 host back onto a cell in its preferred us-central1 region', async () => {
+ const context = await fixture({
+ sourceRegion: 'asia-east2',
+ targetRegion: 'us-central1'
+ })
+
+ const attempt = await context.store.claimRegionalRehome()
+ expect(attempt).toMatchObject({
+ preferredRegion: 'us-central1',
+ sourceCellId: context.source.id,
+ targetCellId: context.target.id
+ })
+ // The durable attempt row must accept the reverse direction too.
+ expect(await primary.query(
+ `SELECT preferred_region, source_cell_id, target_cell_id
+ FROM relay_region_rehome_attempts WHERE user_id = ?`,
+ [context.identity.userId]
+ )).toEqual([{
+ preferred_region: 'us-central1',
+ source_cell_id: context.source.id,
+ target_cell_id: context.target.id
+ }])
+ expect(await primary.query(
+ `SELECT cell_id FROM relay_assignments WHERE user_id = ?`,
+ [context.identity.userId]
+ )).toEqual([{ cell_id: context.target.id }])
+ })
+
+ it('leaves a host whose preference already matches its own region', async () => {
+ const context = await fixture({ preferredRegion: 'us-central1' })
+
+ await expect(context.store.claimRegionalRehome()).resolves.toBeNull()
+ await expect(context.store.inspectRegionalRehomeControl()).resolves.toMatchObject({
+ generation: 1,
+ enabled: true
+ })
+ expect(await attemptAndMigrationCounts(context.identity)).toEqual({
+ attempts: 0,
+ migrations: 0
+ })
+ })
+
+ it('leaves a host whose preference is older than the configured max age', async () => {
+ const context = await fixture()
+ await primary.query(
+ `UPDATE relay_assignment_region_preferences SET observed_at = ?
+ WHERE user_id = ? AND relay_host_id = ?`,
+ [
+ context.now() - 24 * 60 * 60_000 - 1,
+ context.identity.userId,
+ context.identity.relayHostId
+ ]
+ )
+
+ await expect(context.store.claimRegionalRehome()).resolves.toBeNull()
+ await expect(context.store.inspectRegionalRehomeControl()).resolves.toMatchObject({
+ generation: 1,
+ enabled: true
+ })
+ expect(await attemptAndMigrationCounts(context.identity)).toEqual({
+ attempts: 0,
+ migrations: 0
+ })
+ })
+
+ it('leaves a host inside its per-host rehome cooldown, in either direction', async () => {
+ const context = await fixture({ hostCooldownMs: 3 * 24 * 60 * 60_000 })
+ // A move this host already made, whichever way it went.
+ await primary.query(
+ `INSERT INTO relay_region_rehome_attempts
+ (attempt_id, user_id, relay_host_id, preferred_region, source_cell_id,
+ source_cell_incarnation, target_cell_id, target_cell_incarnation,
+ previous_epoch, assignment_epoch, drain_grace_ms, send_attempts,
+ completed_at, created_at, updated_at)
+ VALUES (?, ?, ?, 'us-central1', ?, ?, ?, ?, 0, 1, 0, 0, ?, ?, ?)`,
+ [
+ `pg-rehome-cooldown-${context.identity.relayHostId}`,
+ context.identity.userId,
+ context.identity.relayHostId,
+ context.target.id,
+ '22222222-2222-4222-8222-222222222222',
+ context.source.id,
+ '11111111-1111-4111-8111-111111111111',
+ context.now(),
+ context.now() - 3 * 24 * 60 * 60_000 + 1,
+ context.now()
+ ]
+ )
+
+ await expect(context.store.claimRegionalRehome()).resolves.toBeNull()
+ await expect(context.store.inspectRegionalRehomeControl()).resolves.toMatchObject({
+ generation: 1,
+ enabled: true,
+ hostCooldownMs: 3 * 24 * 60 * 60_000
+ })
+ expect(await attemptAndMigrationCounts(context.identity)).toEqual({
+ attempts: 1,
+ migrations: 0
+ })
+
+ // One millisecond past the window the same host is a candidate again.
+ await primary.query(
+ `UPDATE relay_region_rehome_attempts SET created_at = ? WHERE user_id = ?`,
+ [context.now() - 3 * 24 * 60 * 60_000, context.identity.userId]
+ )
+ await expect(context.store.claimRegionalRehome()).resolves.toMatchObject({
+ sourceCellId: context.source.id,
+ targetCellId: context.target.id
+ })
+ })
+
+ it('leaves a host whose preferred region holds no drainable cell', async () => {
+ // A cell that cannot be drained cannot be a target: the host would land
+ // where no later rehome could move it out again.
+ const context = await fixture({ targetProtocol: 0 })
+
+ await expect(context.store.claimRegionalRehome()).resolves.toBeNull()
+ await expect(context.store.inspectRegionalRehomeControl()).resolves.toMatchObject({
+ generation: 1,
+ enabled: true
+ })
+ expect(await attemptAndMigrationCounts(context.identity)).toEqual({
+ attempts: 0,
+ migrations: 0
+ })
+ })
+
it('skips an unclean cell without latching the control off', async () => {
const context = await fixture()
await primary.query(
@@ -281,7 +428,7 @@ describePostgres('PostgreSQL regional rehoming', () => {
context.store,
context.target,
'22222222-2222-4222-8222-222222222222',
- 0,
+ 1,
900_000,
2
)
@@ -322,7 +469,7 @@ describePostgres('PostgreSQL regional rehoming', () => {
context.store,
context.target,
'44444444-4444-4444-8444-444444444444',
- 0,
+ 1,
context.now()
)
@@ -341,7 +488,7 @@ describePostgres('PostgreSQL regional rehoming', () => {
context.store,
context.target,
'22222222-2222-4222-8222-222222222222',
- 0,
+ 1,
900_000,
2
)
@@ -414,6 +561,26 @@ describePostgres('PostgreSQL regional rehoming', () => {
})
})
+ async function attemptAndMigrationCounts(identity: {
+ userId: string
+ relayHostId: string
+ }): Promise<{ attempts: number; migrations: number }> {
+ const attempts = await primary.query(
+ `SELECT COUNT(*) AS count FROM relay_region_rehome_attempts
+ WHERE user_id = ? AND relay_host_id = ?`,
+ [identity.userId, identity.relayHostId]
+ )
+ const migrations = await primary.query(
+ `SELECT COUNT(*) AS count FROM relay_assignment_migrations
+ WHERE user_id = ? AND relay_host_id = ?`,
+ [identity.userId, identity.relayHostId]
+ )
+ return {
+ attempts: Number(attempts[0]!.count),
+ migrations: Number(migrations[0]!.count)
+ }
+ }
+
async function controlAccounting(identity: {
userId: string
relayHostId: string
@@ -436,12 +603,15 @@ describePostgres('PostgreSQL regional rehoming', () => {
}
}
- async function fixture() {
+ async function fixture(options: FixtureOptions = {}) {
sequence++
let now = 1_000_000
const suffix = String(sequence)
- const source = cell(suffix, 'source', 'us-central1')
- const target = cell(suffix, 'target', 'asia-east2')
+ const sourceRegion = options.sourceRegion ?? 'us-central1'
+ const targetRegion = options.targetRegion ?? 'asia-east2'
+ const preferredRegion = options.preferredRegion ?? targetRegion
+ const source = cell(suffix, 'source', sourceRegion)
+ const target = cell(suffix, 'target', targetRegion)
const store = new RelayAssignmentStore(primary, () => now, storeOptions)
const competingStore = new RelayAssignmentStore(secondary, () => now, storeOptions)
await store.inspectRegionalRehomeControl()
@@ -452,6 +622,7 @@ describePostgres('PostgreSQL regional rehoming', () => {
notBefore: now,
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: options.hostCooldownMs ?? 7 * 24 * 60 * 60_000,
drainGraceMs: 60_000
})
await store.reconcileCells([source, target])
@@ -466,21 +637,22 @@ describePostgres('PostgreSQL regional rehoming', () => {
store,
target,
'22222222-2222-4222-8222-222222222222',
- 0,
+ options.targetProtocol ?? 1,
900_000
)
const identity = {
userId: `pg-rehome-user-${suffix}`,
relayHostId: `rehomehost${suffix.padStart(6, '0')}`
}
- const assignment = await store.assign(identity, undefined, 'us-central1')
+ const assignment = await store.assign(identity, undefined, sourceRegion)
const sourceControl = await store.activateControl(identity, {
cellId: source.id,
assignmentEpoch: assignment.assignmentEpoch,
generation: 1
})
- await store.assign(identity, 'asia-east2')
+ await store.assign(identity, preferredRegion)
return {
+ preferredRegion,
store,
competingStore,
identity,
@@ -500,7 +672,16 @@ const storeOptions = {
heartbeatTtlMs: 45_000
}
-function cell(suffix: string, role: string, region: 'us-central1' | 'asia-east2') {
+type Region = 'us-central1' | 'asia-east2'
+type FixtureOptions = {
+ sourceRegion?: Region
+ targetRegion?: Region
+ preferredRegion?: Region
+ targetProtocol?: number
+ hostCooldownMs?: number
+}
+
+function cell(suffix: string, role: string, region: Region) {
return {
id: `pg-rehome-cell-${suffix}-${role}`,
url: `https://pg-rehome-${suffix}-${role}.example.test`,
diff --git a/cloud/apps/relay/src/regional-rehome-store.test.ts b/cloud/apps/relay/src/regional-rehome-store.test.ts
index 26f711189ee8..2c1c81322665 100644
--- a/cloud/apps/relay/src/regional-rehome-store.test.ts
+++ b/cloud/apps/relay/src/regional-rehome-store.test.ts
@@ -81,6 +81,7 @@ describe('regional rehome assignment state', () => {
notBefore: context.now(),
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60_000
})).rejects.toThrow('regional_rehome_generation_mismatch')
await expect(context.store.applyRegionalRehomeControl({
@@ -89,6 +90,7 @@ describe('regional rehome assignment state', () => {
notBefore: context.now(),
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60_000
})).resolves.toMatchObject({ generation: 3, enabled: true })
await context.database.close()
@@ -207,7 +209,7 @@ describe('regional rehome assignment state', () => {
databasePoolWaitersMax: 0,
databasePoolWaitMsMax: 0
})
- await heartbeat(context.store, target, targetIncarnation, 0, 2, {
+ await heartbeat(context.store, target, targetIncarnation, 1, 2, {
observedAt: context.now(),
sqlFailures: 1,
reconnects: 3,
@@ -226,6 +228,23 @@ describe('regional rehome assignment state', () => {
await context.database.close()
})
+ it('counts only drainable cells as the rehome fleet, in every region', async () => {
+ // The fleet whose health gates a rehome is exactly the cells that can be a
+ // source or a target, and both roles require the drain protocol.
+ const context = await setup({ targetProtocol: 0 })
+
+ expect(await context.store.regionalRehomeFleetSafety()).toMatchObject({
+ requiredCells: 1,
+ missingCells: 0
+ })
+ await heartbeat(context.store, target, targetIncarnation, 1, 2)
+ expect(await context.store.regionalRehomeFleetSafety()).toMatchObject({
+ requiredCells: 2,
+ missingCells: 0
+ })
+ await context.database.close()
+ })
+
it('claims through the measured healthy baseline of pool micro-waits and churn', async () => {
const context = await setup()
const baseline = {
@@ -238,7 +257,7 @@ describe('regional rehome assignment state', () => {
databasePoolWaitMsMax: 1
}
await heartbeat(context.store, source, sourceIncarnation, 1, 2, baseline)
- await heartbeat(context.store, target, targetIncarnation, 0, 2, baseline)
+ await heartbeat(context.store, target, targetIncarnation, 1, 2, baseline)
await activatePreferredSource(context, {
userId: 'user-1',
relayHostId: 'abcdefghijklmnop'
@@ -324,6 +343,204 @@ describe('regional rehome assignment state', () => {
await context.database.close()
})
+ it('moves a live host on an asia-east2 cell back to its preferred us-central1 cell', async () => {
+ const context = await setup()
+ const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
+ await activateReversePreferredSource(context, identity)
+
+ const attempt = await context.store.claimRegionalRehome()
+ expect(attempt).toMatchObject({
+ userId: identity.userId,
+ relayHostId: identity.relayHostId,
+ preferredRegion: 'us-central1',
+ sourceCellId: target.id,
+ sourceCellIncarnation: targetIncarnation,
+ targetCellId: source.id,
+ targetCellIncarnation: sourceIncarnation,
+ previousEpoch: 1,
+ assignmentEpoch: 2,
+ sendAttempts: 1
+ })
+ expect(
+ await context.database.query(
+ `SELECT preferred_region, source_cell_id, target_cell_id
+ FROM relay_region_rehome_attempts`
+ )
+ ).toEqual([{
+ preferred_region: 'us-central1',
+ source_cell_id: target.id,
+ target_cell_id: source.id
+ }])
+ expect(await context.store.resolve(identity)).toMatchObject({ cellId: source.id })
+ await context.database.close()
+ })
+
+ it('drops a candidate at scan time when no cell in the preferred region is usable', async () => {
+ const context = await setup()
+ await activatePreferredSource(context, {
+ userId: 'user-1',
+ relayHostId: 'abcdefghijklmnop'
+ })
+ // A disabled cell is not a target, and the scan must say so: leaving it to
+ // the claim would burn a slot of the candidate batch on a certain skip.
+ await context.database.query(`UPDATE relay_cells SET enabled = 0 WHERE cell_id = ?`, [
+ target.id
+ ])
+
+ const warnings = collectEventWarnings('orca_relay_regional_rehome_candidates_skipped')
+ try {
+ expect(await context.store.claimRegionalRehome()).toBeNull()
+ } finally {
+ warnings.restore()
+ }
+ expect(warnings.entries).toEqual([])
+ expect(
+ await context.database.query(
+ `SELECT next_dispatch_at FROM relay_region_rehome_worker_state`
+ )
+ ).toEqual([{ next_dispatch_at: 0 }])
+ expect(await context.database.query(`SELECT * FROM relay_assignment_migrations`)).toEqual([])
+ await context.database.close()
+ })
+
+ it('names the skip when the last target is lost between scan and claim', async () => {
+ const database = await openInMemoryRelayDatabase()
+ const context = await setup({
+ database,
+ wrap: (delegate) =>
+ hookAfterCandidateScan(delegate, async (transaction) => {
+ await transaction.query(`UPDATE relay_cells SET enabled = 0 WHERE cell_id = ?`, [
+ target.id
+ ])
+ })
+ })
+ await activatePreferredSource(context, {
+ userId: 'user-1',
+ relayHostId: 'abcdefghijklmnop'
+ })
+
+ const warnings = collectEventWarnings('orca_relay_regional_rehome_candidates_skipped')
+ try {
+ expect(await context.store.claimRegionalRehome()).toBeNull()
+ } finally {
+ warnings.restore()
+ }
+ expect(warnings.entries).toMatchObject([
+ { skips: [{ reason: 'no_eligible_target', candidates: 1 }] }
+ ])
+ expect(await context.store.inspectRegionalRehomeControl()).toMatchObject({
+ generation: 1,
+ enabled: true
+ })
+ expect(await database.query(`SELECT * FROM relay_assignment_migrations`)).toEqual([])
+ await database.close()
+ })
+
+ it('leaves a host alone until its cooldown expires, then moves it back', async () => {
+ const context = await setup({ hostCooldownMs: 3 * 24 * 60 * 60_000 })
+ const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
+ const targetControl = await completeRehomeToTarget(context, identity)
+ // Past the dispatch interval the earlier claim charged, so the next tick
+ // really does scan and the cooldown is the only thing holding this host.
+ context.advance(10_000)
+ // The desktop's region probe now says us-central1 again.
+ await context.store.assign(identity, 'us-central1')
+
+ const warnings = collectEventWarnings('orca_relay_regional_rehome_candidates_skipped')
+ try {
+ expect(await context.store.claimRegionalRehome()).toBeNull()
+ } finally {
+ warnings.restore()
+ }
+ expect(warnings.entries).toEqual([])
+ expect(await context.store.resolve(identity)).toMatchObject({ cellId: target.id })
+
+ context.advance(3 * 24 * 60 * 60_000)
+ await freshHeartbeats(context)
+ await context.store.renewControlActivity(identity, {
+ activityId: targetControl,
+ cellId: target.id,
+ expiresAt: context.now() + 90_000
+ })
+ await context.store.assign(identity, 'us-central1')
+
+ const attempt = await context.store.claimRegionalRehome()
+ expect(attempt).toMatchObject({
+ preferredRegion: 'us-central1',
+ sourceCellId: target.id,
+ targetCellId: source.id
+ })
+ await context.database.close()
+ })
+
+ it('rejects a host whose attempt lands between the scan and the claim', async () => {
+ const database = await openInMemoryRelayDatabase()
+ const identity = { userId: 'user-1', relayHostId: 'abcdefghijklmnop' }
+ const context = await setup({
+ database,
+ wrap: (delegate) =>
+ hookAfterCandidateScan(delegate, async (transaction) => {
+ await transaction.query(
+ `INSERT INTO relay_region_rehome_attempts
+ (attempt_id, user_id, relay_host_id, preferred_region, source_cell_id,
+ source_cell_incarnation, target_cell_id, target_cell_incarnation,
+ previous_epoch, assignment_epoch, drain_grace_ms, send_attempts,
+ created_at, updated_at)
+ VALUES ('raced', ?, ?, 'asia-east2', ?, ?, ?, ?, 0, 1, 0, 0, ?, ?)`,
+ [
+ identity.userId,
+ identity.relayHostId,
+ source.id,
+ sourceIncarnation,
+ target.id,
+ targetIncarnation,
+ context.now(),
+ context.now()
+ ]
+ )
+ })
+ })
+ await activatePreferredSource(context, identity)
+
+ const warnings = collectEventWarnings('orca_relay_regional_rehome_candidates_skipped')
+ try {
+ expect(await context.store.claimRegionalRehome()).toBeNull()
+ } finally {
+ warnings.restore()
+ }
+ expect(warnings.entries).toMatchObject([
+ { skips: [{ reason: 'host_cooldown', candidates: 1 }] }
+ ])
+ expect(await database.query(`SELECT * FROM relay_assignment_migrations`)).toEqual([])
+ await database.close()
+ })
+
+ it('does not scan a candidate whose preferred region has no drainable cell', async () => {
+ // A cell without the drain protocol cannot be a target: the host would land
+ // where no later rehome could move it out again. The candidate query drops
+ // it, so the tick stays idle instead of paying for an inventory scan.
+ const context = await setup({ targetProtocol: 0 })
+ await activatePreferredSource(context, {
+ userId: 'user-1',
+ relayHostId: 'abcdefghijklmnop'
+ })
+
+ const warnings = collectEventWarnings('orca_relay_regional_rehome_candidates_skipped')
+ try {
+ expect(await context.store.claimRegionalRehome()).toBeNull()
+ } finally {
+ warnings.restore()
+ }
+ expect(warnings.entries).toEqual([])
+ expect(
+ await context.database.query(
+ `SELECT next_dispatch_at FROM relay_region_rehome_worker_state`
+ )
+ ).toEqual([{ next_dispatch_at: 0 }])
+ expect(await context.database.query(`SELECT * FROM relay_assignment_migrations`)).toEqual([])
+ await context.database.close()
+ })
+
it('skips an unclean cell without latching the control off', async () => {
const context = await setup()
await activatePreferredSource(context, {
@@ -457,7 +674,7 @@ describe('regional rehome assignment state', () => {
databasePoolWaitersMax: 0,
databasePoolWaitMsMax: 0
})
- await heartbeat(context.store, target, targetIncarnation, 0, 2, {
+ await heartbeat(context.store, target, targetIncarnation, 1, 2, {
observedAt: context.now(),
sqlFailures: 0,
reconnects: 0,
@@ -477,6 +694,7 @@ describe('regional rehome assignment state', () => {
notBefore: context.now(),
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60_000
})
const retry = await context.store.claimRegionalRehome()
@@ -547,7 +765,7 @@ describe('regional rehome assignment state', () => {
await activatePreferredSource(context, identity)
await context.store.claimRegionalRehome()
context.advance(6 * 60_000)
- await heartbeat(context.store, target, targetIncarnation, 0, 2)
+ await heartbeat(context.store, target, targetIncarnation, 1, 2)
expect(await context.store.refreshRegionalRehomeLeases()).toBe(0)
expect(await context.store.abortExpiredEvacuations()).toBe(0)
@@ -1549,10 +1767,16 @@ function collectDisableWarnings() {
}
async function setup(
- options: { sourceProtocol?: number; wrap?: (database: RelayDatabase) => RelayDatabase } = {}
+ options: {
+ sourceProtocol?: number
+ targetProtocol?: number
+ hostCooldownMs?: number
+ database?: RelayDatabase
+ wrap?: (database: RelayDatabase) => RelayDatabase
+ } = {}
) {
let clock = 1_000_000
- const database = await openInMemoryRelayDatabase()
+ const database = options.database ?? (await openInMemoryRelayDatabase())
const store = new RelayAssignmentStore(options.wrap?.(database) ?? database, () => clock, {
requireLiveCells: true,
heartbeatTtlMs: 45_000
@@ -1565,11 +1789,12 @@ async function setup(
notBefore: clock,
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: options.hostCooldownMs ?? 7 * 24 * 60 * 60_000,
drainGraceMs: 60 * 60_000
})
await store.reconcileCells([source, target])
await heartbeat(store, source, sourceIncarnation, options.sourceProtocol ?? 1)
- await heartbeat(store, target, targetIncarnation, 0)
+ await heartbeat(store, target, targetIncarnation, options.targetProtocol ?? 1)
return {
database,
store,
@@ -1671,7 +1896,7 @@ async function freshHeartbeats(context: Context): Promise {
}
// The clock doubles as a strictly-increasing connection inclusion watermark.
await heartbeat(context.store, source, sourceIncarnation, 1, context.now(), safety)
- await heartbeat(context.store, target, targetIncarnation, 0, context.now(), safety)
+ await heartbeat(context.store, target, targetIncarnation, 1, context.now(), safety)
}
async function activatePreferredSource(
@@ -1688,6 +1913,68 @@ async function activatePreferredSource(
return control
}
+// Runs a hook inside the claim transaction, right after the candidate scan, so
+// a scan-versus-claim race is deterministic instead of timing-dependent.
+function hookAfterCandidateScan(
+ database: RelayDatabase,
+ hook: (transaction: RelayDatabase) => Promise
+): RelayDatabase {
+ let fired = false
+ const decorate = (delegate: RelayDatabase): RelayDatabase => ({
+ query: async (sql, params) => {
+ const rows = await delegate.query(sql, params)
+ if (!fired && sql.includes('FROM relay_assignment_region_preferences preference')) {
+ fired = true
+ await hook(delegate)
+ }
+ return rows
+ },
+ queryLocked: async (sql, params, lockOptions) =>
+ await delegate.queryLocked(sql, params, lockOptions),
+ transaction: async (operation, transactionOptions) =>
+ await delegate.transaction(
+ async (transaction) => await operation(decorate(transaction)),
+ transactionOptions
+ ),
+ close: async () => undefined
+ })
+ return decorate(database)
+}
+
+async function completeRehomeToTarget(
+ context: Context,
+ identity: { userId: string; relayHostId: string }
+): Promise {
+ const sourceControl = await activatePreferredSource(context, identity)
+ const attempt = await context.store.claimRegionalRehome()
+ const targetControl = await context.store.activateControl(identity, {
+ cellId: target.id,
+ assignmentEpoch: attempt!.assignmentEpoch,
+ generation: 1
+ })
+ await context.store.markMigrationTargetRegistered(identity, {
+ cellId: target.id,
+ assignmentEpoch: attempt!.assignmentEpoch
+ })
+ await context.store.releaseActivity(identity, sourceControl)
+ await context.store.completeReadyRegionalRehomes()
+ return targetControl
+}
+
+async function activateReversePreferredSource(
+ context: Context,
+ identity: { userId: string; relayHostId: string }
+): Promise {
+ const assignment = await context.store.assign(identity, undefined, 'asia-east2')
+ const control = await context.store.activateControl(identity, {
+ cellId: target.id,
+ assignmentEpoch: assignment.assignmentEpoch,
+ generation: 1
+ })
+ await context.store.assign(identity, 'us-central1')
+ return control
+}
+
async function activateSource(
context: Context,
identity: { userId: string; relayHostId: string }
diff --git a/cloud/apps/relay/src/regional-rehome-target-selection.test.ts b/cloud/apps/relay/src/regional-rehome-target-selection.test.ts
index e21901687356..493eaa50a617 100644
--- a/cloud/apps/relay/src/regional-rehome-target-selection.test.ts
+++ b/cloud/apps/relay/src/regional-rehome-target-selection.test.ts
@@ -36,6 +36,7 @@ async function setup() {
notBefore: clock,
ratePerMinute: 10,
preferenceMaxAgeMs: 24 * 60 * 60_000,
+ hostCooldownMs: 7 * 24 * 60 * 60_000,
drainGraceMs: 60 * 60_000
})
await store.reconcileCells([source, noHeadroom, unclean, highLoad, lowLoad])
@@ -100,22 +101,22 @@ describe('regional rehome target selection', () => {
sqlFailures: 0
})
// Lowest load but the connection hard cap is exhausted.
- await context.beat(noHeadroom, 2, 0, {
+ await context.beat(noHeadroom, 2, 1, {
observedRequests: 0,
enforcedConnections: 999,
sqlFailures: 0
})
- await context.beat(unclean, 3, 0, {
+ await context.beat(unclean, 3, 1, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: UNCLEAN
})
- await context.beat(highLoad, 4, 0, {
+ await context.beat(highLoad, 4, 1, {
observedRequests: 50,
enforcedConnections: 0,
sqlFailures: 0
})
- await context.beat(lowLoad, 5, 0, {
+ await context.beat(lowLoad, 5, 1, {
observedRequests: 10,
enforcedConnections: 0,
sqlFailures: 0
@@ -134,22 +135,22 @@ describe('regional rehome target selection', () => {
enforcedConnections: 0,
sqlFailures: 0
})
- await context.beat(noHeadroom, 2, 0, {
+ await context.beat(noHeadroom, 2, 1, {
observedRequests: 0,
enforcedConnections: 999,
sqlFailures: 0
})
- await context.beat(unclean, 3, 0, {
+ await context.beat(unclean, 3, 1, {
observedRequests: 0,
enforcedConnections: 0,
sqlFailures: UNCLEAN
})
- await context.beat(highLoad, 4, 0, {
+ await context.beat(highLoad, 4, 1, {
observedRequests: 50,
enforcedConnections: 0,
sqlFailures: 0
})
- await context.beat(lowLoad, 5, 0, {
+ await context.beat(lowLoad, 5, 1, {
observedRequests: 10,
enforcedConnections: 0,
sqlFailures: UNCLEAN
diff --git a/cloud/apps/relay/src/relay-observability.test.ts b/cloud/apps/relay/src/relay-observability.test.ts
index fc8a4fcb4af3..ea6734412be4 100644
--- a/cloud/apps/relay/src/relay-observability.test.ts
+++ b/cloud/apps/relay/src/relay-observability.test.ts
@@ -1,7 +1,9 @@
+import { RELAY_REGION_METRIC_SEGMENTS, RELAY_REGIONS } from '@orca-cloud/relay-contract'
import { describe, expect, it, vi } from 'vitest'
import type { RelayDatabase } from './database.js'
import { observeRelayDatabase } from './observed-relay-database.js'
import {
+ CONTROL_RTT_RESERVOIR_LIMIT,
observedRelayRequests,
RelayObservability,
type RelayProcessCounts
@@ -22,6 +24,37 @@ const counts: RelayProcessCounts = {
databasePoolWaitMsMax: 1_250
}
+// Two schema keys legitimately spell a policed word: the abandoned-accept bucket
+// is keyed by stage name and one stage is `credential`. Rename those exact keys in
+// a clone instead of rewriting the JSON, so a stray raw field or value anywhere
+// else still trips the guard below.
+const SCHEMA_KEY_ALIASES: Record = {
+ clientAcceptCredentialMsP95: 'clientAcceptStageTwoMsP95'
+}
+
+function scrubSchemaKeys(entries: Array>): string {
+ return JSON.stringify(
+ entries.map((entry) =>
+ Object.fromEntries(
+ Object.entries(entry).map(([key, value]) => [
+ SCHEMA_KEY_ALIASES[key] ?? key,
+ key === 'clientAcceptsAbandonedByStageDelta' ? renameStageKeys(value) : value
+ ])
+ )
+ )
+ )
+}
+
+function renameStageKeys(bucket: unknown): unknown {
+ if (bucket === null || typeof bucket !== 'object') return bucket
+ return Object.fromEntries(
+ Object.entries(bucket).map(([stage, count]) => [
+ stage === 'credential' ? 'stageTwo' : stage,
+ count
+ ])
+ )
+}
+
describe('relay observability', () => {
it('emits safe readiness dependency outcomes', () => {
const entries: Array> = []
@@ -106,14 +139,31 @@ describe('relay observability', () => {
requestedRegionsDelta: { 'asia-east2': 1, unhinted: 1 },
selectedRegionsDelta: { 'us-central1': 1 },
regionFallbacksDelta: { 'asia-east2': 1 },
- unavailableRegionsDelta: { 'asia-east2': 1 }
+ unavailableRegionsDelta: { 'asia-east2': 1 },
+ // Flat per-region siblings the log-based metrics extract; `unhinted` stays map-only.
+ requestedRegionUsCentral1Delta: 0,
+ requestedRegionAsiaEast2Delta: 1,
+ selectedRegionUsCentral1Delta: 1,
+ selectedRegionAsiaEast2Delta: 0
})
expect(entries[1]).toMatchObject({
requestedRegionsDelta: {},
selectedRegionsDelta: {},
regionFallbacksDelta: {},
- unavailableRegionsDelta: {}
+ unavailableRegionsDelta: {},
+ // Zeros keep publishing so an idle window cannot drop a series out of the skew join.
+ requestedRegionUsCentral1Delta: 0,
+ requestedRegionAsiaEast2Delta: 0,
+ selectedRegionUsCentral1Delta: 0,
+ selectedRegionAsiaEast2Delta: 0
})
+ // A region added to the contract has to reach the flat keys, or the skew alert's
+ // denominator silently misses it.
+ for (const segment of Object.values(RELAY_REGION_METRIC_SEGMENTS)) {
+ expect(entries[0]).toHaveProperty(`requestedRegion${segment}Delta`)
+ expect(entries[0]).toHaveProperty(`selectedRegion${segment}Delta`)
+ }
+ expect(Object.keys(RELAY_REGION_METRIC_SEGMENTS).sort()).toEqual([...RELAY_REGIONS].sort())
})
it('emits bounded aggregate runtime signals without identities or credentials', () => {
@@ -181,7 +231,7 @@ describe('relay observability', () => {
controlActivityRecoveryFailuresDelta: 0,
httpLatencyMsMax: 0
})
- expect(JSON.stringify(entries)).not.toMatch(/token|credential|userId|relayHostId/)
+ expect(scrubSchemaKeys(entries)).not.toMatch(/token|credential|userId|relayHostId/i)
})
it('aggregates control and splice closes as bounded per-reason deltas', () => {
@@ -215,6 +265,117 @@ describe('relay observability', () => {
})
})
+ it('summarises completed client accepts and control round trips per window', () => {
+ const entries: Array> = []
+ const observability = new RelayObservability(
+ { role: 'cell', cellId: 'production-gce-c28', region: 'asia-east2' },
+ (entry) => entries.push(entry)
+ )
+ observability.recordClientAcceptCompleted({
+ totalMs: 812.4567,
+ stageMs: { assignment: 120, credential: 90, activity: 40, attach: 500, basis: 62 }
+ })
+ observability.recordClientAcceptCompleted({
+ totalMs: 6_400,
+ stageMs: { assignment: 4_100, credential: 95, activity: 60, attach: 2_000, basis: 145 }
+ })
+ observability.recordControlRtt(28)
+ observability.recordControlRtt(240)
+ observability.recordControlRtt(31)
+ observability.flush(counts)
+ observability.flush(counts)
+
+ expect(entries[0]).toMatchObject({
+ clientAcceptCompletedDelta: 2,
+ clientAcceptTotalMsP50: 812.457,
+ clientAcceptTotalMsP95: 6_400,
+ clientAcceptTotalMsMax: 6_400,
+ clientAcceptAssignmentMsP95: 4_100,
+ clientAcceptCredentialMsP95: 95,
+ clientAcceptActivityMsP95: 60,
+ clientAcceptAttachMsP95: 2_000,
+ clientAcceptBasisMsP95: 145,
+ controlRttSamplesDelta: 3,
+ controlRttMsP50: 31,
+ controlRttMsP95: 240,
+ controlRttMsMax: 240
+ })
+ // Only-add: the pre-existing fields still read the same after the extension.
+ expect(entries[0]).toMatchObject({
+ event: 'orca_relay_runtime_metrics',
+ metricVersion: 2,
+ clientAcceptsAbandonedByStageDelta: {},
+ clientAcceptAbandonedMsMax: 0
+ })
+ // An empty window publishes counts only: a zero percentile point is
+ // indistinguishable from a real zero once Cloud Logging aggregates it.
+ expect(entries[1]).toMatchObject({ clientAcceptCompletedDelta: 0, controlRttSamplesDelta: 0 })
+ for (const omitted of [
+ 'clientAcceptTotalMsP50',
+ 'clientAcceptTotalMsP95',
+ 'clientAcceptTotalMsMax',
+ 'clientAcceptAssignmentMsP95',
+ 'clientAcceptCredentialMsP95',
+ 'clientAcceptActivityMsP95',
+ 'clientAcceptAttachMsP95',
+ 'clientAcceptBasisMsP95',
+ 'controlRttMsP50',
+ 'controlRttMsP95',
+ 'controlRttMsMax'
+ ]) {
+ expect(entries[1]).not.toHaveProperty(omitted)
+ expect(entries[0]).toHaveProperty(omitted)
+ }
+ expect(scrubSchemaKeys(entries)).not.toMatch(/token|credential|userId|relayHostId/i)
+ })
+
+ it('caps the control round-trip reservoir and reports what it dropped', () => {
+ const entries: Array> = []
+ const observability = new RelayObservability(
+ { role: 'cell', cellId: 'production-gce-c28', region: 'asia-east2' },
+ (entry) => entries.push(entry)
+ )
+ const flooded = CONTROL_RTT_RESERVOIR_LIMIT * 20
+ for (let sample = 0; sample < flooded; sample++) {
+ observability.recordControlRtt(10 + (sample % 40))
+ }
+ observability.flush(counts)
+
+ // Dropped is observed minus retained, so this pins the retained window at the cap.
+ expect(entries[0]).toMatchObject({
+ controlRttSamplesDelta: flooded,
+ controlRttSamplesDroppedDelta: flooded - CONTROL_RTT_RESERVOIR_LIMIT
+ })
+ // The kept samples are real observations, not a truncated or synthesised window.
+ expect(entries[0]!.controlRttMsP50 as number).toBeGreaterThanOrEqual(10)
+ expect(entries[0]!.controlRttMsMax as number).toBeLessThanOrEqual(49)
+
+ observability.flush(counts)
+ expect(entries[1]).toMatchObject({
+ controlRttSamplesDelta: 0,
+ controlRttSamplesDroppedDelta: 0
+ })
+ expect(entries[1]).not.toHaveProperty('controlRttMsP50')
+ })
+
+ it('samples the whole flooded window rather than its first samples', () => {
+ const entries: Array> = []
+ const observability = new RelayObservability(
+ { role: 'cell', cellId: 'production-gce-c28', region: 'asia-east2' },
+ (entry) => entries.push(entry)
+ )
+ const half = CONTROL_RTT_RESERVOIR_LIMIT * 10
+ for (let sample = 0; sample < half; sample++) observability.recordControlRtt(10)
+ for (let sample = 0; sample < half; sample++) observability.recordControlRtt(900)
+ observability.flush(counts)
+
+ // Keeping the first N instead would publish a window of nothing but 10s. Each
+ // reservoir slot ends up drawn from the late half with ~1/2 probability, so
+ // fewer than the 5% the p95 needs is out of reach of this suite.
+ expect(entries[0]!.controlRttMsP95).toBe(900)
+ expect(entries[0]!.controlRttMsMax).toBe(900)
+ })
+
it('observes successful and failed database calls including transactions', async () => {
const recordSql = vi.fn()
const underlying: RelayDatabase = {
diff --git a/cloud/apps/relay/src/relay-observability.ts b/cloud/apps/relay/src/relay-observability.ts
index 59ff437e40b8..5e85758ca560 100644
--- a/cloud/apps/relay/src/relay-observability.ts
+++ b/cloud/apps/relay/src/relay-observability.ts
@@ -1,5 +1,5 @@
import { monitorEventLoopDelay, performance } from 'node:perf_hooks'
-import type { RelayRegion } from '@orca-cloud/relay-contract'
+import { RELAY_REGION_METRIC_SEGMENTS, type RelayRegion } from '@orca-cloud/relay-contract'
import type { ControlRenewalOutcome } from './assignment-store.js'
import type { CellInventoryHoldCounts } from './cell-inventory-hold-samples.js'
import type { PostgresPoolPressureCounts } from './postgres-pool-pressure.js'
@@ -65,11 +65,31 @@ export interface RelayRuntimeObserver {
recordControlClose?(code: number): void
recordSpliceClose?(trigger: string): void
recordClientAcceptAbandoned?(stage: RelayClientAcceptStage, elapsedMs: number): void
+ recordClientAcceptCompleted?(sample: RelayClientAcceptSample): void
+ recordControlRtt?(rttMs: number): void
}
// Which serialized accept step the phone had already hung up behind.
export type RelayClientAcceptStage = 'assignment' | 'credential' | 'activity'
+// The attach window and the basis writes that follow it are only measurable once
+// the host data leg lands, so they join the serialized pre-attach steps on
+// completed accepts only.
+export type RelayClientAcceptTimedStage = RelayClientAcceptStage | 'attach' | 'basis'
+
+export const RELAY_CLIENT_ACCEPT_TIMED_STAGES = [
+ 'assignment',
+ 'credential',
+ 'activity',
+ 'attach',
+ 'basis'
+] as const satisfies readonly RelayClientAcceptTimedStage[]
+
+export type RelayClientAcceptSample = {
+ totalMs: number
+ stageMs: Record
+}
+
type RelayMetricDeltas = {
forwardedBytes: number
authSuccesses: number
@@ -93,12 +113,20 @@ type RelayMetricDeltas = {
spliceClosesByTrigger: Record
clientAcceptsAbandonedByStage: Record
clientAcceptAbandonedMsMax: number
+ clientAcceptTotalsMs: number[]
+ clientAcceptStageSamplesMs: Record
+ controlRttSamplesMs: number[]
+ controlRttObserved: number
controlRenewalLatenciesMs: number[]
controlRenewalsByOutcome: Record
controlActivityRecoveries: number
controlActivityRecoveryFailures: number
}
+// A host chooses how often it answers a ping, so the process-wide window is a
+// reservoir: the heap cost of a flood is capped and the percentiles stay unbiased.
+export const CONTROL_RTT_RESERVOIR_LIMIT = 1024
+
type MetricWriter = (entry: Record) => void
const emptyDeltas = (): RelayMetricDeltas => ({
@@ -124,18 +152,42 @@ const emptyDeltas = (): RelayMetricDeltas => ({
spliceClosesByTrigger: {},
clientAcceptsAbandonedByStage: {},
clientAcceptAbandonedMsMax: 0,
+ clientAcceptTotalsMs: [],
+ clientAcceptStageSamplesMs: {
+ assignment: [],
+ credential: [],
+ activity: [],
+ attach: [],
+ basis: []
+ },
+ controlRttSamplesMs: [],
+ controlRttObserved: 0,
controlRenewalLatenciesMs: [],
controlRenewalsByOutcome: {},
controlActivityRecoveries: 0,
controlActivityRecoveryFailures: 0
})
-function percentile(values: number[], percentileRank: number): number {
+export function percentile(values: number[], percentileRank: number): number {
if (values.length === 0) return 0
const sorted = [...values].sort((left, right) => left - right)
return sorted[Math.ceil(percentileRank * sorted.length) - 1] ?? 0
}
+function roundMs(value: number): number {
+ return Number(value.toFixed(3))
+}
+
+// Spreading a window into Math.max blows the stack once a busy cell samples
+// enough of it, so the maximum is folded instead.
+function latencySummary(samples: number[]): { p50: number; p95: number; max: number } {
+ return {
+ p50: roundMs(percentile(samples, 0.5)),
+ p95: roundMs(percentile(samples, 0.95)),
+ max: roundMs(samples.reduce((highest, sample) => Math.max(highest, sample), 0))
+ }
+}
+
export class RelayObservability implements RelayRuntimeObserver {
private readonly eventLoop = monitorEventLoopDelay({ resolution: 20 })
private deltas = emptyDeltas()
@@ -244,6 +296,25 @@ export class RelayObservability implements RelayRuntimeObserver {
)
}
+ recordClientAcceptCompleted(sample: RelayClientAcceptSample): void {
+ this.deltas.clientAcceptTotalsMs.push(sample.totalMs)
+ for (const stage of RELAY_CLIENT_ACCEPT_TIMED_STAGES) {
+ this.deltas.clientAcceptStageSamplesMs[stage].push(sample.stageMs[stage])
+ }
+ }
+
+ recordControlRtt(rttMs: number): void {
+ const samples = this.deltas.controlRttSamplesMs
+ const observedBefore = this.deltas.controlRttObserved++
+ if (samples.length < CONTROL_RTT_RESERVOIR_LIMIT) {
+ samples.push(rttMs)
+ return
+ }
+ // Algorithm R: every round trip in the window keeps an equal chance of being kept.
+ const slot = Math.floor(Math.random() * (observedBefore + 1))
+ if (slot < CONTROL_RTT_RESERVOIR_LIMIT) samples[slot] = rttMs
+ }
+
start(readCounts: () => RelayProcessCounts, intervalMs = 30_000): void {
if (this.timer) return
this.eventLoop.enable()
@@ -277,6 +348,11 @@ export class RelayObservability implements RelayRuntimeObserver {
controlActivityRecoveryFailures: deltas.controlActivityRecoveryFailures
}
this.deltas = emptyDeltas()
+ const acceptTotals = latencySummary(deltas.clientAcceptTotalsMs)
+ const acceptStageP95 = (stage: RelayClientAcceptTimedStage): number =>
+ roundMs(percentile(deltas.clientAcceptStageSamplesMs[stage], 0.95))
+ const controlRtt = latencySummary(deltas.controlRttSamplesMs)
+ const controlRenewal = latencySummary(deltas.controlRenewalLatenciesMs)
const memory = process.memoryUsage()
const p99 = this.eventLoop.count === 0 ? 0 : this.eventLoop.percentile(99) / 1_000_000
this.eventLoop.reset()
@@ -301,15 +377,43 @@ export class RelayObservability implements RelayRuntimeObserver {
placementRejectionsByReasonDelta: deltas.placementRejectionsByReason,
requestedRegionsDelta: deltas.requestedRegions,
selectedRegionsDelta: deltas.selectedRegions,
+ ...regionCounterFields('requestedRegion', deltas.requestedRegions),
+ ...regionCounterFields('selectedRegion', deltas.selectedRegions),
regionFallbacksDelta: deltas.regionFallbacks,
unavailableRegionsDelta: deltas.unavailableRegions,
controlClosesByCodeDelta: deltas.controlClosesByCode,
spliceClosesByTriggerDelta: deltas.spliceClosesByTrigger,
clientAcceptsAbandonedByStageDelta: deltas.clientAcceptsAbandonedByStage,
- clientAcceptAbandonedMsMax: Number(deltas.clientAcceptAbandonedMsMax.toFixed(3)),
+ clientAcceptAbandonedMsMax: roundMs(deltas.clientAcceptAbandonedMsMax),
+ clientAcceptCompletedDelta: deltas.clientAcceptTotalsMs.length,
+ // Accepts are sparse: publishing a zero percentile for every empty window
+ // would pin the p50 at 0 forever and collapse the p95 at low accept rates.
+ ...(deltas.clientAcceptTotalsMs.length === 0
+ ? {}
+ : {
+ clientAcceptTotalMsP50: acceptTotals.p50,
+ clientAcceptTotalMsP95: acceptTotals.p95,
+ clientAcceptTotalMsMax: acceptTotals.max,
+ clientAcceptAssignmentMsP95: acceptStageP95('assignment'),
+ clientAcceptCredentialMsP95: acceptStageP95('credential'),
+ clientAcceptActivityMsP95: acceptStageP95('activity'),
+ clientAcceptAttachMsP95: acceptStageP95('attach'),
+ clientAcceptBasisMsP95: acceptStageP95('basis')
+ }),
+ // Every round trip observed in the window, including the ones the reservoir
+ // above declined to keep; the percentiles summarise only what it kept.
+ controlRttSamplesDelta: deltas.controlRttObserved,
+ controlRttSamplesDroppedDelta: deltas.controlRttObserved - deltas.controlRttSamplesMs.length,
+ ...(deltas.controlRttSamplesMs.length === 0
+ ? {}
+ : {
+ controlRttMsP50: controlRtt.p50,
+ controlRttMsP95: controlRtt.p95,
+ controlRttMsMax: controlRtt.max
+ }),
sqlQueriesDelta: deltas.sqlQueries,
sqlFailuresDelta: deltas.sqlFailures,
- sqlLatencyMsMax: Number(deltas.sqlLatencyMsMax.toFixed(3)),
+ sqlLatencyMsMax: roundMs(deltas.sqlLatencyMsMax),
controlRenewalsByOutcomeDelta: deltas.controlRenewalsByOutcome,
controlRenewalsDelta: deltas.controlRenewalLatenciesMs.length,
controlRenewalSuccessesDelta: deltas.controlRenewalsByOutcome.renewed ?? 0,
@@ -317,16 +421,10 @@ export class RelayObservability implements RelayRuntimeObserver {
deltas.controlRenewalsByOutcome.control_activity_not_found ?? 0,
controlActivityRecoveriesDelta: deltas.controlActivityRecoveries,
controlActivityRecoveryFailuresDelta: deltas.controlActivityRecoveryFailures,
- controlRenewalLatencyMsP50: Number(
- percentile(deltas.controlRenewalLatenciesMs, 0.5).toFixed(3)
- ),
- controlRenewalLatencyMsP95: Number(
- percentile(deltas.controlRenewalLatenciesMs, 0.95).toFixed(3)
- ),
- controlRenewalLatencyMsMax: Number(
- Math.max(0, ...deltas.controlRenewalLatenciesMs).toFixed(3)
- ),
- httpLatencyMsMax: Number(deltas.httpLatencyMsMax.toFixed(3)),
+ controlRenewalLatencyMsP50: controlRenewal.p50,
+ controlRenewalLatencyMsP95: controlRenewal.p95,
+ controlRenewalLatencyMsMax: controlRenewal.max,
+ httpLatencyMsMax: roundMs(deltas.httpLatencyMsMax),
heapUsedBytes: memory.heapUsed,
heapTotalBytes: memory.heapTotal,
eventLoopDelayMsP99: Number(p99.toFixed(3))
@@ -334,6 +432,22 @@ export class RelayObservability implements RelayRuntimeObserver {
}
}
+// Flat siblings of the nested region maps, always emitted for every region including zeros.
+// A log-based metric cannot reach `requestedRegionsDelta."asia-east2"` without a quoted field
+// path, and an absent key would drop a series out of the inner join the region-skew alert does.
+// The maps stay authoritative and keep carrying anything outside the catalog, such as `unhinted`.
+function regionCounterFields(
+ prefix: 'requestedRegion' | 'selectedRegion',
+ counts: Record
+): Record {
+ return Object.fromEntries(
+ Object.entries(RELAY_REGION_METRIC_SEGMENTS).map(([region, segment]) => [
+ `${prefix}${segment}Delta`,
+ counts[region] ?? 0
+ ])
+ )
+}
+
function increment(counts: Record, key: string): void {
counts[key] = (counts[key] ?? 0) + 1
}
diff --git a/cloud/apps/relay/src/relay-server.ts b/cloud/apps/relay/src/relay-server.ts
index 6331b584b1bf..77a15a1d259d 100644
--- a/cloud/apps/relay/src/relay-server.ts
+++ b/cloud/apps/relay/src/relay-server.ts
@@ -2,7 +2,9 @@ import { createAdaptorServer } from '@hono/node-server'
import {
hasAdmissionCapacity,
HostDataAuthSchema,
+ parseRelayHostCapabilities,
RELAY_ADMISSION_BUDGETS,
+ RELAY_HOST_CAPABILITIES_HEADER,
RELAY_CLOSE_CODE,
RELAY_DEFAULT_REGION,
RELAY_PROTOCOL_LIMITS,
@@ -486,7 +488,8 @@ export function createRelayServer(
sessions.acceptControl(
webSocket,
identity,
- controlUpgrade?.inclusionWatermark
+ controlUpgrade?.inclusionWatermark,
+ parseRelayHostCapabilities(request.headers[RELAY_HOST_CAPABILITIES_HEADER])
)
})
} catch {
diff --git a/cloud/apps/relay/src/relay.blackbox.test.ts b/cloud/apps/relay/src/relay.blackbox.test.ts
index 38134213e76f..0202d964ee73 100644
--- a/cloud/apps/relay/src/relay.blackbox.test.ts
+++ b/cloud/apps/relay/src/relay.blackbox.test.ts
@@ -9,7 +9,9 @@ import { fileURLToPath } from 'node:url'
import { exportJWK, generateKeyPair, jwtVerify, SignJWT } from 'jose'
import {
buildHostProofMacInput,
- HOST_CHALLENGE_PLAINTEXT_DOMAIN
+ HOST_CHALLENGE_PLAINTEXT_DOMAIN,
+ RELAY_HOST_CAPABILITIES_HEADER,
+ RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS
} from '@orca-cloud/relay-contract'
import nacl from 'tweetnacl'
import { afterAll, beforeAll, describe, expect, it } from 'vitest'
@@ -282,11 +284,17 @@ async function openHostControl(input?: {
previousGeneration?: number
keyPair?: nacl.BoxKeyPair
assignmentEpoch?: number
+ capabilities?: string
}): Promise<{ socket: WebSocket; ack: Record; keyPair: nacl.BoxKeyPair }> {
const keyPair = input?.keyPair ?? nacl.box.keyPair()
const hostId = createHash('sha256').update(keyPair.publicKey).digest('base64url').slice(0, 16)
const socket = new WebSocket(`${relayUrl.replace('http:', 'ws:')}/v1/host/control`, {
- headers: { authorization: `Bearer ${await relayToken('orca-relay', hostId)}` },
+ headers: {
+ authorization: `Bearer ${await relayToken('orca-relay', hostId)}`,
+ ...(input?.capabilities
+ ? { [RELAY_HOST_CAPABILITIES_HEADER]: input.capabilities }
+ : {})
+ },
perMessageDeflate: false
})
await new Promise((resolveOpen, reject) => {
@@ -653,6 +661,69 @@ describe('served relay URL', () => {
expect(result.reason).not.toContain('http')
})
+ it('restates a pending connection to the rebound control, detailed only when advertised', async () => {
+ // The one link the unit tests cannot reach: an upgrade that really carries
+ // x-orca-host-capabilities must reach acceptControl and change the ack. A
+ // typo in the header name here passes every other test in the suite.
+ const host = await openHostControl()
+ const hostId = createHash('sha256')
+ .update(host.keyPair.publicKey)
+ .digest('base64url')
+ .slice(0, 16)
+ const inviteResponse = nextMessage(host.socket)
+ host.socket.send(
+ JSON.stringify({
+ type: 'invite-create',
+ reqId: 'capability-invite',
+ relayDeviceId: 'capability-device'
+ })
+ )
+ const invite = await inviteResponse
+ const phone = new WebSocket(`${relayUrl.replace('http:', 'ws:')}/v1/connect/${hostId}`, {
+ headers: forwardedHeaders()
+ })
+ await new Promise((resolveOpen, reject) => {
+ phone.once('open', resolveOpen)
+ phone.once('error', reject)
+ })
+ const connectionPromise = nextMessage(host.socket)
+ phone.send(
+ JSON.stringify({ type: 'relay-auth', v: 1, mode: 'connect', credential: invite.inviteToken })
+ )
+ // Never attached: the connection stays pending, which is what the ack restates.
+ const connection = await connectionPromise
+ expect(connection.type).toBe('conn-open')
+
+ const capable = await openHostControl({
+ keyPair: host.keyPair,
+ controlResumeSecret: String(host.ack.controlResumeSecret),
+ previousGeneration: 1,
+ capabilities: RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS
+ })
+ expect(capable.ack.pendingConns).toEqual([
+ {
+ connId: connection.connId,
+ connTicket: connection.connTicket,
+ kind: 'invite',
+ relayDeviceId: 'capability-device'
+ }
+ ])
+
+ const legacy = await openHostControl({
+ keyPair: host.keyPair,
+ controlResumeSecret: String(capable.ack.controlResumeSecret),
+ previousGeneration: 1
+ })
+ // A shipped host parses these entries strictly, so an unannounced key would
+ // fail the whole ack and kill a control that was working.
+ expect(legacy.ack.pendingConns).toEqual([
+ { connId: connection.connId, connTicket: connection.connTicket }
+ ])
+
+ phone.close()
+ legacy.socket.close()
+ })
+
it('keeps a pending attach usable after a bad ticket and rejects ticket replay', async () => {
const host = await openHostControl()
const hostId = createHash('sha256')
diff --git a/cloud/dev/fixtures/terraform-root-partition/families.json b/cloud/dev/fixtures/terraform-root-partition/families.json
index dfe100fd2dd1..dd6f69443226 100644
--- a/cloud/dev/fixtures/terraform-root-partition/families.json
+++ b/cloud/dev/fixtures/terraform-root-partition/families.json
@@ -133,14 +133,17 @@
"google_logging_metric.relay_snapshot",
"google_monitoring_alert_policy.relay_assignment_5xx",
"google_monitoring_alert_policy.relay_assignment_edge_429",
+ "google_monitoring_alert_policy.relay_cell_control_rtt",
"google_monitoring_alert_policy.relay_cell_process_exit",
"google_monitoring_alert_policy.relay_cloud_nat_port_drops",
"google_monitoring_alert_policy.relay_cloud_sql_backends",
"google_monitoring_alert_policy.relay_cloud_sql_checkpoint_loop",
"google_monitoring_alert_policy.relay_cloud_sql_disk",
"google_monitoring_alert_policy.relay_custom",
+ "google_monitoring_alert_policy.relay_far_cell_accept_latency",
"google_monitoring_alert_policy.relay_gce_connection_headroom",
"google_monitoring_alert_policy.relay_postgres_retry_exhausted",
+ "google_monitoring_alert_policy.relay_region_hint_skew",
"google_monitoring_dashboard.relay_incident",
"google_project_iam_custom_role.github_production_relay_capacity_mutation",
"google_project_iam_custom_role.github_relay_asia_topology_mutation",
diff --git a/cloud/dev/scripts/cloud-sql-rollout-lock-census.mjs b/cloud/dev/scripts/cloud-sql-rollout-lock-census.mjs
index 76193746f2c2..2f7157d823c8 100644
--- a/cloud/dev/scripts/cloud-sql-rollout-lock-census.mjs
+++ b/cloud/dev/scripts/cloud-sql-rollout-lock-census.mjs
@@ -283,6 +283,8 @@ export const LEASED_WORKFLOWS = named([
'operate-relay-production-rehome.yml',
production({ leaseFiles: ['operate-relay-production-rehome-job.yml'] })
],
+ // The gateway applies its schema at startup, so its deploy revision is the schema step.
+ ['push-deploy.yml', production()],
['deploy-relay-asia-topology.yml', eitherEnvironment()],
['operate-relay-asia-admission.yml', eitherEnvironment()],
['deploy-relay-staging.yml', staging()],
diff --git a/cloud/dev/scripts/operate-relay-regional-rehome.mjs b/cloud/dev/scripts/operate-relay-regional-rehome.mjs
index 3887408520fa..94b21220fc0e 100644
--- a/cloud/dev/scripts/operate-relay-regional-rehome.mjs
+++ b/cloud/dev/scripts/operate-relay-regional-rehome.mjs
@@ -45,6 +45,7 @@ export function parseRegionalRehomeArguments(argv, environment = process.env) {
'not-before',
'rate-per-minute',
'preference-max-age-ms',
+ 'host-cooldown-ms',
'drain-grace-ms',
'confirmation'
]
@@ -117,6 +118,11 @@ export function parseRegionalRehomeArguments(argv, environment = process.env) {
'--preference-max-age-ms',
{ minimum: 60_000, maximum: 30 * 24 * 60 * 60_000 }
),
+ hostCooldownMs: integer(
+ values['host-cooldown-ms'],
+ '--host-cooldown-ms',
+ { minimum: 60_000, maximum: 30 * 24 * 60 * 60_000 }
+ ),
drainGraceMs: integer(values['drain-grace-ms'], '--drain-grace-ms', {
minimum: 60_000,
maximum: 60 * 60_000
@@ -147,6 +153,11 @@ function assertControl(control, expected) {
!Number.isSafeInteger(control.notBefore) ||
!Number.isSafeInteger(control.ratePerMinute) ||
!Number.isSafeInteger(control.preferenceMaxAgeMs) ||
+ // A director predating the per-host cooldown does not report it. Reading
+ // the control and both emergency brakes must keep working against that
+ // image; only enable requires the field.
+ (control.hostCooldownMs !== undefined &&
+ !Number.isSafeInteger(control.hostCooldownMs)) ||
!Number.isSafeInteger(control.drainGraceMs)
) throw new Error('director returned an invalid regional rehome control')
if (expected.enabled !== undefined && control.enabled !== expected.enabled) {
@@ -155,6 +166,12 @@ function assertControl(control, expected) {
return control
}
+// Echo the cooldown only when the director already reports it: a legacy
+// director rejects the unknown key outright and would refuse every brake.
+function cooldownField(before, value) {
+ return before.hostCooldownMs === undefined ? {} : { hostCooldownMs: value }
+}
+
async function verifiedDisabledControl(post, generation) {
return assertControl((await post('/v1/admin/regional-rehome-control', {
v: 1,
@@ -171,6 +188,7 @@ async function applyDisabledControl(post, before) {
notBefore: before.notBefore,
ratePerMinute: before.ratePerMinute,
preferenceMaxAgeMs: before.preferenceMaxAgeMs,
+ ...cooldownField(before, before.hostCooldownMs),
drainGraceMs: before.drainGraceMs,
confirmation: 'DISABLE_REGIONAL_REHOMING'
})).control, { generation: before.generation + 1, enabled: false })
@@ -270,6 +288,11 @@ export async function operateRegionalRehome(config, dependencies = {}) {
throw new Error('regional rehome is already paused')
}
const enabled = config.mode === 'enable'
+ if (enabled && before.hostCooldownMs === undefined) {
+ throw new Error(
+ 'director does not report a per-host rehome cooldown; deploy a director that supports it before enabling'
+ )
+ }
const applied = await post('/v1/admin/regional-rehome-control', {
v: 1,
action: 'apply',
@@ -278,6 +301,7 @@ export async function operateRegionalRehome(config, dependencies = {}) {
notBefore: config.notBefore,
ratePerMinute: config.ratePerMinute,
preferenceMaxAgeMs: config.preferenceMaxAgeMs,
+ ...cooldownField(before, config.hostCooldownMs),
drainGraceMs: config.drainGraceMs,
confirmation: enabled
? 'ENABLE_REGIONAL_REHOMING'
diff --git a/cloud/dev/scripts/operate-relay-regional-rehome.test.mjs b/cloud/dev/scripts/operate-relay-regional-rehome.test.mjs
index bfea6769ec46..51c132aa7c47 100644
--- a/cloud/dev/scripts/operate-relay-regional-rehome.test.mjs
+++ b/cloud/dev/scripts/operate-relay-regional-rehome.test.mjs
@@ -26,6 +26,7 @@ function argumentsFor(mode, confirmation) {
'--not-before', '2000000000000',
'--rate-per-minute', '10',
'--preference-max-age-ms', '86400000',
+ '--host-cooldown-ms', '604800000',
'--drain-grace-ms', '60000',
'--confirmation', confirmation
])
@@ -40,10 +41,29 @@ function control(generation, enabled) {
notBefore: 2_000_000_000_000,
ratePerMinute: 10,
preferenceMaxAgeMs: 86_400_000,
+ hostCooldownMs: 604_800_000,
drainGraceMs: 60_000
}
}
+// The control a director predating the per-host cooldown reports.
+function legacyControl(generation, enabled) {
+ const { hostCooldownMs: _absent, ...rest } = control(generation, enabled)
+ return rest
+}
+
+function legacyDirector(controls) {
+ const requests = []
+ const post = async (path, body) => {
+ requests.push({ path, body })
+ if (path === '/v1/admin/admission-selector/status') {
+ return { selector: { generation: 11, membership } }
+ }
+ return { v: 1, control: controls.shift() }
+ }
+ return { requests, post }
+}
+
test('parses exact selector and typed control confirmation', () => {
const parsed = parseRegionalRehomeArguments(
argumentsFor('enable', 'ENABLE_REGIONAL_REHOMING'),
@@ -52,6 +72,17 @@ test('parses exact selector and typed control confirmation', () => {
assert.equal(parsed.expectedSelectorGeneration, 11)
assert.equal(parsed.expectedControlGeneration, 4)
assert.equal(parsed.ratePerMinute, 10)
+ assert.equal(parsed.hostCooldownMs, 604_800_000)
+ assert.throws(
+ () => parseRegionalRehomeArguments(
+ argumentsFor('enable', 'ENABLE_REGIONAL_REHOMING').filter(
+ (value, index, all) =>
+ value !== '--host-cooldown-ms' && all[index - 1] !== '--host-cooldown-ms'
+ ),
+ { ORCA_RELAY_ADMIN_ID_TOKEN: 'token' }
+ ),
+ /complete durable control shape/
+ )
assert.throws(
() => parseRegionalRehomeArguments(
argumentsFor('pause', 'DISABLE_REGIONAL_REHOMING'),
@@ -79,6 +110,7 @@ test('binds enable to exact selector and durable control generations', async ()
notBefore: 0,
ratePerMinute: 10,
preferenceMaxAgeMs: 86_400_000,
+ hostCooldownMs: 604_800_000,
drainGraceMs: 60_000,
...control
}))
@@ -96,6 +128,7 @@ test('binds enable to exact selector and durable control generations', async ()
}
})
assert.equal(result.control.generation, 5)
+ assert.equal(result.control.hostCooldownMs, 604_800_000)
assert.deepEqual(requests[2].body, {
v: 1,
action: 'apply',
@@ -104,11 +137,82 @@ test('binds enable to exact selector and durable control generations', async ()
notBefore: 2_000_000_000_000,
ratePerMinute: 10,
preferenceMaxAgeMs: 86_400_000,
+ hostCooldownMs: 604_800_000,
drainGraceMs: 60_000,
confirmation: 'ENABLE_REGIONAL_REHOMING'
})
})
+test('inspects a director that predates the per-host cooldown', async () => {
+ const director = legacyDirector([legacyControl(4, true)])
+ const config = parseRegionalRehomeArguments(
+ argumentsFor('inspect'),
+ { ORCA_RELAY_ADMIN_ID_TOKEN: 'token' }
+ )
+
+ const result = await operateRegionalRehome(config, { post: director.post })
+
+ assert.equal(result.control.generation, 4)
+ assert.equal(result.control.hostCooldownMs, undefined)
+})
+
+for (const [mode, confirmation, enabledBefore] of [
+ ['pause', 'PAUSE_REGIONAL_REHOMING', true],
+ ['disable', 'DISABLE_REGIONAL_REHOMING', false]
+]) {
+ test(`${mode} still brakes a director that predates the cooldown`, async () => {
+ const director = legacyDirector([
+ legacyControl(4, enabledBefore),
+ legacyControl(5, false),
+ legacyControl(5, false)
+ ])
+ const config = parseRegionalRehomeArguments(
+ argumentsFor(mode, confirmation),
+ { ORCA_RELAY_ADMIN_ID_TOKEN: 'token' }
+ )
+
+ const result = await operateRegionalRehome(config, { post: director.post })
+
+ assert.equal(result.control.generation, 5)
+ // The unknown key would be refused by that director's strict schema.
+ assert.equal('hostCooldownMs' in director.requests[2].body, false)
+ assert.equal(director.requests[2].body.confirmation, 'DISABLE_REGIONAL_REHOMING')
+ })
+}
+
+test('failed-enable recovery brakes a director that predates the cooldown', async () => {
+ const requests = []
+ let current = legacyControl(7, true)
+ const result = await recoverRegionalRehomeEnable({
+ mode: 'recover-enable',
+ expectedControlGeneration: 4
+ }, async (_path, body) => {
+ requests.push(body)
+ if (body.action === 'inspect') return { control: current }
+ current = legacyControl(8, false)
+ return { control: current }
+ })
+
+ assert.equal(result.control.generation, 8)
+ assert.equal('hostCooldownMs' in requests[1], false)
+})
+
+test('refuses to enable a director that does not report the cooldown', async () => {
+ const director = legacyDirector([legacyControl(4, false)])
+ const config = parseRegionalRehomeArguments(
+ argumentsFor('enable', 'ENABLE_REGIONAL_REHOMING'),
+ { ORCA_RELAY_ADMIN_ID_TOKEN: 'token' }
+ )
+
+ await assert.rejects(
+ operateRegionalRehome(config, { post: director.post }),
+ /per-host rehome cooldown/
+ )
+ // Read-only: selector status and the control inspect, and nothing else.
+ assert.equal(director.requests.length, 2)
+ assert.equal(director.requests.every(({ body }) => body.action !== 'apply'), true)
+})
+
test('fails closed on selector drift before reading or mutating control', async () => {
let calls = 0
const config = parseRegionalRehomeArguments(
@@ -150,6 +254,7 @@ test('failed-enable recovery CAS-disables an advanced enabled generation', async
notBefore: 2_000_000_000_000,
ratePerMinute: 10,
preferenceMaxAgeMs: 86_400_000,
+ hostCooldownMs: 604_800_000,
drainGraceMs: 60_000,
confirmation: 'DISABLE_REGIONAL_REHOMING'
})
diff --git a/cloud/dev/scripts/probe-relay-rehome-trust.mjs b/cloud/dev/scripts/probe-relay-rehome-trust.mjs
index 9a7d505d4bbd..2208131e58ad 100644
--- a/cloud/dev/scripts/probe-relay-rehome-trust.mjs
+++ b/cloud/dev/scripts/probe-relay-rehome-trust.mjs
@@ -1,7 +1,9 @@
import { pathToFileURL } from 'node:url'
import { fetchAdminOnceMore } from './relay-admin-transient-retry.mjs'
-const PRODUCTION_CELL = /^production-gce-c(?:7|8|9|10|13|14|15|16|19|20|21|22|23|24|25|26)$/
+// Every general cell that carries the rehome identity: the sixteen US cells and the
+// three asia-east2 cells that drain mis-homed hosts back the other way.
+const PRODUCTION_CELL = /^production-gce-c(?:7|8|9|10|13|14|15|16|19|20|21|22|23|24|25|26|27|28|29)$/
const DIRECTOR_ORIGIN = 'https://relay.onorca.dev'
export function parseRehomeTrustProbeArguments(argv, environment = process.env) {
diff --git a/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs b/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs
index 7d7b2cd95ac5..789d33c40b62 100644
--- a/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs
+++ b/cloud/dev/scripts/probe-relay-rehome-trust.test.mjs
@@ -111,3 +111,23 @@ test('fails when both trust-probe attempts return a transient 503', async () =>
)
assert.equal(calls, 2)
})
+
+test('approves the asia-east2 rehome sources and still rejects unlisted cells', () => {
+ for (const cellId of ['production-gce-c27', 'production-gce-c28', 'production-gce-c29']) {
+ const parsed = parseRehomeTrustProbeArguments(
+ argv.map((value) => (value === 'production-gce-c7' ? cellId : value)),
+ environment
+ )
+ assert.equal(parsed.cellId, cellId)
+ }
+ for (const cellId of ['production-gce-c1', 'production-gce-c17', 'production-gce-c30']) {
+ assert.throws(
+ () =>
+ parseRehomeTrustProbeArguments(
+ argv.map((value) => (value === 'production-gce-c7' ? cellId : value)),
+ environment
+ ),
+ /--cell-id is not approved/
+ )
+ }
+})
diff --git a/cloud/dev/scripts/relay-production-identity-boundaries.test.mjs b/cloud/dev/scripts/relay-production-identity-boundaries.test.mjs
index 7e8ea2a05c18..f97e742215bf 100644
--- a/cloud/dev/scripts/relay-production-identity-boundaries.test.mjs
+++ b/cloud/dev/scripts/relay-production-identity-boundaries.test.mjs
@@ -32,7 +32,8 @@ test('no workflow names the retired generic production deploy identity', async (
'deploy-relay-production.yml',
'operate-relay-asia-admission.yml',
'operate-relay-production-rehome-job.yml',
- 'publish-relay-production.yml'
+ 'publish-relay-production.yml',
+ 'push-deploy.yml'
].map((name) => relayWorkflowFile(name)).sort())
})
diff --git a/cloud/dev/scripts/relay-public-workflow-contract.test.mjs b/cloud/dev/scripts/relay-public-workflow-contract.test.mjs
index 56393d07bd1f..d25ffb221f4a 100644
--- a/cloud/dev/scripts/relay-public-workflow-contract.test.mjs
+++ b/cloud/dev/scripts/relay-public-workflow-contract.test.mjs
@@ -20,7 +20,7 @@ const UNGATED = relayWorkflowFile('verify.yml')
const relayWorkflows = () => workflowFiles().filter((file) => file !== UNGATED)
test('the copy carries every relay workflow', () => {
- assert.equal(relayWorkflows().length, 24)
+ assert.equal(relayWorkflows().length, 25)
})
// Why: workflow_run chains match by display name, not filename. Renaming a file is safe; renaming
diff --git a/cloud/dev/scripts/relay-region-hint-metrics.test.mjs b/cloud/dev/scripts/relay-region-hint-metrics.test.mjs
new file mode 100644
index 000000000000..8f331efce18e
--- /dev/null
+++ b/cloud/dev/scripts/relay-region-hint-metrics.test.mjs
@@ -0,0 +1,84 @@
+import assert from 'node:assert/strict'
+import { readFileSync } from 'node:fs'
+import test from 'node:test'
+import { fileURLToPath } from 'node:url'
+
+// Why: the region-skew alert compares asia-east2's share of assignment hints against its share of
+// actual placements. Both shares are sums over one log-based metric per region, and the region
+// list is written out by hand in Terraform. A region added to the contract without matching
+// metrics would silently drop out of both denominators and move the ratio the alert fires on.
+
+const read = (relative) => readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8')
+const collapse = (text) => text.replaceAll(/\s+/g, ' ')
+
+const contractRegions = (() => {
+ const source = read('../../packages/relay-contract/src/relay-regions.ts')
+ const literal = /export const RELAY_REGIONS = \[([^\]]*)\]/.exec(source)
+ assert.ok(literal, 'RELAY_REGIONS literal not found in relay-regions.ts')
+ return [...literal[1].matchAll(/'([^']+)'/g)].map((match) => match[1])
+})()
+
+const terraform = read('../../infra/terraform/relay-observability.tf')
+
+const terraformRegions = (() => {
+ const literal = /relay_region_keys = \[([^\]]*)\]/.exec(terraform)
+ assert.ok(literal, 'relay_region_keys not found in relay-observability.tf')
+ return [...literal[1].matchAll(/"([^"]+)"/g)].map((match) => match[1])
+})()
+
+// Both sides now spell the field-name segments out, so the test compares the two declared maps
+// rather than two source expressions. Reformatting either file cannot break this, and a literal
+// expected value below still catches an identical wrong edit made to both.
+const declaredSegments = (source, open, close) => {
+ const body = source.slice(source.indexOf(open) + open.length, source.indexOf(close, source.indexOf(open)))
+ return Object.fromEntries(
+ [...body.matchAll(/'?"?([a-z0-9-]+)'?"?\s*[:=]\s*'?"?([A-Za-z0-9]+)'?"?/g)].map((match) => [
+ match[1],
+ match[2]
+ ])
+ )
+}
+
+const terraformSegments = declaredSegments(terraform, 'relay_region_field_segments = {', '}')
+const contractSegments = declaredSegments(
+ read('../../packages/relay-contract/src/relay-regions.ts'),
+ 'RELAY_REGION_METRIC_SEGMENTS = {',
+ '}'
+)
+
+test('terraform covers exactly the regions the contract can hint or select', () => {
+ assert.deepEqual([...terraformRegions].sort(), [...contractRegions].sort())
+})
+
+test('terraform and the contract declare the same flat field segments', () => {
+ assert.deepEqual(terraformSegments, contractSegments)
+ // Pinned literally so the same wrong edit applied to both sides still fails.
+ assert.deepEqual(terraformSegments, { 'us-central1': 'UsCentral1', 'asia-east2': 'AsiaEast2' })
+ assert.deepEqual(Object.keys(terraformSegments).sort(), [...contractRegions].sort())
+})
+
+test('the skew query compares a catalogued region against itself', () => {
+ const columns = terraformRegions.map((region) => region.replaceAll('-', '_'))
+ const hint = /hint_share: req_([a-z0-9_]+) \//.exec(terraform)
+ const placement = /placement_share: sel_([a-z0-9_]+) \//.exec(terraform)
+ assert.ok(hint && placement, 'skew query share columns not found')
+ assert.equal(hint[1], placement[1], 'the two shares must be about the same region')
+ assert.ok(columns.includes(hint[1]), `${hint[1]} is not one of ${columns.join(', ')}`)
+})
+
+test('the skew condition never divides by the placement share', () => {
+ // A zero-placement hour is the worst skew there is; MQL drops the row on x/0, so the ratio form
+ // silences exactly the case the alert exists for.
+ assert.ok(
+ !/hint_share \/ placement_share/.test(terraform),
+ 'cross-multiply instead: hint_share > 2 * placement_share'
+ )
+ assert.match(collapse(terraform), /condition hint_share > 2 \* placement_share/)
+})
+
+test('the unhinted bucket stays out of the skew denominators', () => {
+ assert.ok(
+ !terraformRegions.includes('unhinted'),
+ 'unhinted requests are a client-side choice, not a region; including them moves the share'
+ )
+})
diff --git a/cloud/dev/scripts/relay-same-cap-script-census.test.mjs b/cloud/dev/scripts/relay-same-cap-script-census.test.mjs
index 743aef7fc2dc..7d5e4fee73e2 100644
--- a/cloud/dev/scripts/relay-same-cap-script-census.test.mjs
+++ b/cloud/dev/scripts/relay-same-cap-script-census.test.mjs
@@ -179,9 +179,10 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
it('validates a correct plan for every wave cell at that cell\'s rehome protocol', () => {
for (const cellId of SAME_CAP_CELLS) {
- const [region, cap] = resolveCellShape(cellId).stdout.trim().split(' ')
+ const [, cap] = resolveCellShape(cellId).stdout.trim().split(' ')
const protocol = REHOME_SOURCE_CELLS.has(cellId) ? 1 : 0
- assert.equal(protocol, region === 'us-central1' ? 1 : 0, cellId)
+ // Every reviewed serving cell carries rehome trust now, in either region.
+ assert.equal(protocol, 1, cellId)
const config = {
mode: 'same-cap-cell',
cellId,
@@ -211,6 +212,29 @@ describe('same-cap roll scripts accept every same-cap cell', () => {
}
})
+ it('validates a protocol-0 plan for a cell outside the rehome source list', () => {
+ const cellId = 'production-gce-c17'
+ assert.equal(REHOME_SOURCE_CELLS.has(cellId), false)
+ const config = {
+ mode: 'same-cap-cell',
+ cellId,
+ hardCap: 1000,
+ unobservedBound: 60,
+ image: TARGET_IMAGE,
+ rollbackImage: ROLLBACK_IMAGE,
+ rehomeDirectorServiceAccount: DIRECTOR_IDENTITY,
+ rehomeAudience: AUDIENCE,
+ regionalRehomeProtocol: '0'
+ }
+ const plan = rollPlan({ cellId, cap: 1000, protocol: 0 })
+ assert.deepEqual(validateCapacityPlan(plan, config), { mode: 'same-cap-cell', changes: 2 })
+ // Protocol 1 must reject a plan with no rehome lines, or the absent-line rule decides nothing.
+ assert.throws(
+ () => validateCapacityPlan(plan, { ...config, regionalRehomeProtocol: '1' }),
+ /reviewed image and capacity/
+ )
+ })
+
it('leaves the US-only capacity job on the default allowlist', () => {
assert.doesNotMatch(capacityWorkflow, /--approved-cells/)
})
diff --git a/cloud/docs/orca-relay-operations.md b/cloud/docs/orca-relay-operations.md
index 0f8eb7a50fbb..cd989b58e94e 100644
--- a/cloud/docs/orca-relay-operations.md
+++ b/cloud/docs/orca-relay-operations.md
@@ -464,6 +464,18 @@ Once a target control is registered, do not force the pre-registration rollback.
After a deployment traffic shift, preserve the old revision/tag until metrics and live reconnect checks pass. If the new revision is unhealthy, shift traffic back only while old controls are still valid, then issue a strictly newer director migration rather than reusing a prior epoch.
+## Regional rehoming
+
+Rehoming moves a host to a general cell in the region its desktop last reported, in either
+direction. Both roles need the drain protocol: a cell without it can be neither a source nor a
+target, and it is not part of the fleet whose telemetry gates the worker. Until the asia-east2
+cells run `regionalRehomeProtocol` 1 they are none of the three, so no host is moved into or out
+of Asia and an Asia cell in distress does not pause the worker.
+
+`host-cooldown-ms` is the minimum gap between two rehomes of one host. It bounds the damage from
+a desktop whose region probe flips: without it the host would be dragged back across the ocean on
+every flip, since the preference age never expires while the host keeps reconnecting.
+
## Game-day matrix
Run and record each scenario in staging before launch:
diff --git a/cloud/docs/relay-incident-monitor.md b/cloud/docs/relay-incident-monitor.md
index 8a8dfda14959..696efb85296d 100644
--- a/cloud/docs/relay-incident-monitor.md
+++ b/cloud/docs/relay-incident-monitor.md
@@ -121,6 +121,79 @@ durably marked consumed before mutation and cannot authorize another run.
Expected enabled cells must also have a powered runtime, healthy and ready endpoints, fresh
heartbeats, and matching live admission.
+## Region placement alert policies
+
+Cloud Monitoring alert policies, not monitor freeze bars: these page from
+`cloud/infra/terraform/relay-observability.tf` on the shared relay channel in
+`relay_alert_notification_channels`, and they do not gate any workflow. All
+three exist because US desktops sat on asia-east2 cells for weeks in 2026-08
+with every existing bar green.
+
+| Alert policy | Condition |
+| --- | ---: |
+| Orca Relay: far-cell phone accept latency | per cell, median 30-second `clientAcceptTotalMsP95` over 15 minutes above 2,000 ms with at least 20 completed accepts |
+| Orca Relay: cell control round trip | per cell, median `controlRttMsP50` over one hour above 150 ms with at least 500 samples |
+| Orca Relay: region hint skew | fleet-wide, asia-east2 share of hinted requests over one hour more than 2x and more than 15 points above its share of actual placements, with at least 500 hinted requests |
+
+Threshold basis:
+
+- Accept latency. An in-region phone accept completes in 0.3-0.6 s and a
+ cross-Pacific one in 5-10 s, so 2,000 ms sits outside in-region noise and
+ well under the far-cell floor. The 20-accept minimum keeps one slow accept
+ on a quiet cell off the pager. The p95 is the published value, so the
+ window aggregate is its median, not its max.
+- Control round trip. In-region is tens of milliseconds; a US desktop on an
+ asia-east2 cell is 200 ms or more. Only the p50 is used. The desktop echoes
+ the pong on its main thread, so the published p95 and max track renderer
+ stalls rather than distance. 500 samples per hour is about two
+ continuously connected hosts at the 15-second control ping. Tuning risk: EU
+ desktops on us-central1 sit at 100-130 ms, so a cell whose population is
+ mostly European can approach the bar while correctly homed. Check where the
+ hosts are before reading a first breach as mis-homing.
+- Region hint skew. This compares two shares of the same hour rather than
+ testing one absolute share, because an absolute bar is wrong at both ends.
+ Measured over twelve hours on 2026-09-07, while the desktop region probe
+ was still mis-picking: asia-east2 was 33.8% of the 33,800 hinted requests
+ and only 7.9% of the 45,364 assignments, a divergence of 4.27x and a gap of
+ 25.9 points. A fixed 40% bar would have stayed silent through that, and
+ once the probe is fixed the genuine APAC share climbs past any such bar and
+ pages forever on the correct end state. The 2x and 15-point bars sit inside
+ the broken state and outside a healthy one. `unhinted` requests are
+ excluded from the denominator: they were 27% of all requests, so a client
+ change that always sends a hint would move the number with no behaviour
+ change at all. The two bars are cross-multiplied rather than divided. An
+ hour that placed nobody in the region is the most extreme skew there is,
+ and it happens whenever the region is drained, fenced, or at capacity, but
+ dividing by that zero placement share makes MQL drop the row and lose the
+ series before any other clause runs.
+
+Expect the skew alert to stay lit after a client fix until the mis-homed
+backlog is rehomed. Sticky assignment never re-consults the hint, so a
+desktop already on an asia cell keeps being placed there whatever it now
+asks for; the ratio clears only once the rehome sweep has drained.
+
+All three conditions are written in MQL rather than the metric filters the
+other relay policies use. Every runtime metric is a DELTA DISTRIBUTION, and
+the only scalar aligners a filter condition can apply to one are percentiles;
+each of these alerts needs the sum of the extracted values as a volume floor,
+which is `sum(value.)` in MQL and unreachable otherwise. None of the
+metrics they read exists in the project yet, so what was checked against
+production is the query shape: the same MQL run over existing metrics of the
+same kind confirmed the distribution sum, the join arity, the unit literals,
+and the condition clause.
+
+The skew shares are built from one log-based metric per region for hints and
+one per region for placements. They read flat `requestedRegionDelta`
+and `selectedRegionDelta` fields that the relay publishes as zeros in
+every interval, not the nested region maps: a log-based metric would need a
+quoted field path to reach a hyphenated map key, and an absent key would drop
+a series out of the inner join. The region list lives in Terraform as
+`relay_region_keys` and is pinned to relay-contract's `RELAY_REGIONS` by
+`dev/scripts/relay-region-hint-metrics.test.mjs`. Both sides spell the field
+name segments out as literal maps rather than deriving them, so the same test
+compares the two declarations directly. Adding a region to the contract
+without its segment is a compile error in relay-contract, not a silent gap.
+
## Implementation log
- Recalibrated the relay pool freezes from 30 waiters / 1,000 ms to
diff --git a/cloud/infra/terraform/environments/production.tfvars b/cloud/infra/terraform/environments/production.tfvars
index 8e442c759003..e1522b3827e8 100644
--- a/cloud/infra/terraform/environments/production.tfvars
+++ b/cloud/infra/terraform/environments/production.tfvars
@@ -402,7 +402,11 @@ relay_region_rehome_source_cell_ids = [
"production-gce-c23",
"production-gce-c24",
"production-gce-c25",
- "production-gce-c26"
+ "production-gce-c26",
+ # Asia cells carry the same trust so mis-homed hosts can be drained back off them.
+ "production-gce-c27",
+ "production-gce-c28",
+ "production-gce-c29"
]
# Slack #orca-relay-alerts, created out of band on 2026-08-05. Declared here because an apply
diff --git a/cloud/infra/terraform/relay-gce-cells.tf b/cloud/infra/terraform/relay-gce-cells.tf
index a4505ba2e378..5023b7e1d504 100644
--- a/cloud/infra/terraform/relay-gce-cells.tf
+++ b/cloud/infra/terraform/relay-gce-cells.tf
@@ -82,14 +82,15 @@ check "relay_gce_fixed_one_topology" {
assert {
condition = alltrue([
+ # Region is not asserted here: the director's own rehome source and target predicates
+ # own eligibility, so this pins only cell shape.
for cell_id in var.relay_region_rehome_source_cell_ids : try(
- var.relay_gce_cells[cell_id].region == var.region &&
var.relay_gce_cells[cell_id].connection_hard_cap != null &&
!contains(var.relay_gce_fenced_cells, cell_id),
false
)
])
- error_message = "Regional rehome sources must be configured, unfenced primary-region GCE cells with explicit connection limits."
+ error_message = "Regional rehome sources must be configured, unfenced GCE cells with explicit connection limits."
}
assert {
diff --git a/cloud/infra/terraform/relay-observability.tf b/cloud/infra/terraform/relay-observability.tf
index 6bc938100c61..498c342f7c21 100644
--- a/cloud/infra/terraform/relay-observability.tf
+++ b/cloud/infra/terraform/relay-observability.tf
@@ -65,6 +65,20 @@ locals {
control_renewal_lease_misses = { field = "controlRenewalLeaseMissesDelta", description = "Control renewals that found their activity lease missing." }
control_activity_recoveries = { field = "controlActivityRecoveriesDelta", description = "Control activity leases recovered after a renewal miss." }
control_activity_recovery_failures = { field = "controlActivityRecoveryFailuresDelta", description = "Control activity lease recovery attempts that failed." }
+ control_rtt_ms_p50 = { field = "controlRttMsP50", description = "Control-socket ping round trip p50 in the interval. The desktop echoes the pong on its main thread, so only the median reads as distance; the p95 and max below are dominated by desktop stalls." }
+ control_rtt_ms_p95 = { field = "controlRttMsP95", description = "Control-socket ping round trip p95 in the interval; a desktop-stall signal, not a distance one." }
+ control_rtt_ms_max = { field = "controlRttMsMax", description = "Maximum control-socket ping round trip in the interval; a desktop-stall signal, not a distance one." }
+ control_rtt_samples = { field = "controlRttSamplesDelta", description = "Control-socket round trips observed in the interval, one per ping answered; the percentiles above are omitted when this is zero." }
+ control_rtt_samples_dropped = { field = "controlRttSamplesDroppedDelta", description = "Observed round trips the bounded percentile reservoir did not keep; non-zero means the percentiles above summarise a uniform sample of the interval." }
+ client_accepts_completed = { field = "clientAcceptCompletedDelta", description = "Phone accepts that reached relay-hello in the interval; the percentiles below are omitted when this is zero." }
+ client_accept_total_ms_p50 = { field = "clientAcceptTotalMsP50", description = "Successful phone-accept duration p50, dial to relay-hello." }
+ client_accept_total_ms_p95 = { field = "clientAcceptTotalMsP95", description = "Successful phone-accept duration p95, dial to relay-hello." }
+ client_accept_total_ms_max = { field = "clientAcceptTotalMsMax", description = "Maximum successful phone-accept duration in the interval." }
+ client_accept_assignment_ms_p95 = { field = "clientAcceptAssignmentMsP95", description = "Accept stage p95: resume/invite lookup plus assignment resolve." }
+ client_accept_credential_ms_p95 = { field = "clientAcceptCredentialMsP95", description = "Accept stage p95: outer credential reservation." }
+ client_accept_activity_ms_p95 = { field = "clientAcceptActivityMsP95", description = "Accept stage p95: credential activity lease acquisition." }
+ client_accept_attach_ms_p95 = { field = "clientAcceptAttachMsP95", description = "Accept stage p95: conn-open sent until the desktop's data leg authenticated." }
+ client_accept_basis_ms_p95 = { field = "clientAcceptBasisMsP95", description = "Accept stage p95: splice lease and connection-basis writes between the data leg and relay-hello." }
heap_used_bytes = { field = "heapUsedBytes", description = "Node.js heap bytes used by the relay process." }
event_loop_ms_p99 = { field = "eventLoopDelayMsP99", description = "Node.js event-loop delay p99 in milliseconds." }
forwarded_bytes = { field = "forwardedBytesDelta", description = "Ciphertext bytes admitted for forwarding." }
@@ -80,6 +94,80 @@ locals {
db_oldest_wait_ms = { field = "databasePoolOldestWaitMs", description = "Current oldest PostgreSQL pool waiter age." }
db_wait_ms_max = { field = "databasePoolWaitMsMax", description = "Maximum PostgreSQL pool wait during the interval." }
}
+
+ # Regions the director can hint or select. Pinned to relay-contract's RELAY_REGIONS by
+ # dev/scripts/relay-region-hint-metrics.test.mjs, which also checks the flat field names below
+ # against the emitter. A region missing here drops out of both shares the skew alert compares.
+ relay_region_keys = ["us-central1", "asia-east2"]
+ # Flat emitter fields, not the nested `requestedRegionsDelta` map: a log-based metric would need
+ # a quoted field path to reach a hyphenated map key, and the relay publishes these as zeros in
+ # every interval so no series can drop out of the alert's inner join. Spelled out rather than
+ # derived, so this literal and relay-contract's RELAY_REGION_METRIC_SEGMENTS can be compared
+ # directly; reformatting either side cannot break the check and neither can drift alone.
+ relay_region_field_segments = {
+ "us-central1" = "UsCentral1"
+ "asia-east2" = "AsiaEast2"
+ }
+ relay_region_columns = { for key in local.relay_region_keys : key => replace(key, "-", "_") }
+ relay_region_share_metrics = merge(
+ {
+ for key in local.relay_region_keys :
+ "requested_regions_${local.relay_region_columns[key]}" => {
+ field = "requestedRegion${local.relay_region_field_segments[key]}Delta"
+ description = "Assignment requests that hinted ${key}."
+ }
+ },
+ {
+ for key in local.relay_region_keys :
+ "selected_regions_${local.relay_region_columns[key]}" => {
+ field = "selectedRegion${local.relay_region_field_segments[key]}Delta"
+ description = "Assignments that placed a host in ${key}."
+ }
+ }
+ )
+ relay_region_hinted_total = join(" + ", [for key in local.relay_region_keys : "req_${local.relay_region_columns[key]}"])
+ relay_region_selected_total = join(" + ", [for key in local.relay_region_keys : "sel_${local.relay_region_columns[key]}"])
+ # MQL, not a filter condition: every runtime metric is a DELTA DISTRIBUTION, and the only scalar
+ # aligners a `condition_threshold` can apply to one are percentiles. Both shares need the sum of
+ # the extracted values, which is `sum(value.)` in MQL and unreachable otherwise.
+ relay_region_hint_skew_query = join("\n", concat(
+ ["{"],
+ flatten([
+ for index, entry in [
+ for key in local.relay_region_keys : { metric = "requested_regions_${local.relay_region_columns[key]}", column = "req_${local.relay_region_columns[key]}" }
+ ] : [
+ index == 0 ? "" : ";",
+ " fetch cloud_run_revision::logging.googleapis.com/user/orca_relay_${entry.metric}",
+ " | align delta(1h) | every 1h",
+ " | group_by [], [${entry.column}: sum(value.orca_relay_${entry.metric})]"
+ ]
+ ]),
+ flatten([
+ for key in local.relay_region_keys : [
+ ";",
+ " fetch cloud_run_revision::logging.googleapis.com/user/orca_relay_selected_regions_${local.relay_region_columns[key]}",
+ " | align delta(1h) | every 1h",
+ " | group_by [], [sel_${local.relay_region_columns[key]}: sum(value.orca_relay_selected_regions_${local.relay_region_columns[key]})]"
+ ]
+ ]),
+ [
+ "}",
+ "| join",
+ "| value [",
+ " hint_share: req_asia_east2 / (${local.relay_region_hinted_total}),",
+ " placement_share: sel_asia_east2 / (${local.relay_region_selected_total}),",
+ " hinted_requests: ${local.relay_region_hinted_total}",
+ " ]",
+ # Cross-multiplied, never a plain ratio of the two shares: an hour that placed nobody in the
+ # region makes that ratio 0/0 or x/0, and MQL drops the row instead of yielding a number, so
+ # the whole series vanishes before the other clauses run. That hour is the worst skew there
+ # is - every desktop asking for a region the director is putting nobody in - and it happens
+ # whenever the region is drained, fenced, or at capacity. Both forms were run read-only
+ # against production surrogates with a zero denominator: the ratio returned no rows, this
+ # returned the series with the condition true.
+ "| condition hint_share > 2 * placement_share && hint_share - placement_share > 0.15 '1' && hinted_requests > 500 '1'"
+ ]
+ ))
relay_custom_alerts = {
connection_headroom = {
pages_oncall = true
@@ -201,7 +289,9 @@ locals {
}
resource "google_logging_metric" "relay_snapshot" {
- for_each = local.relay_runtime_metrics
+ # Region-request metrics ride the same event and shape; merging adds map entries only, so the
+ # existing metric instances are untouched (a label change, not a new key, is what recreates them).
+ for_each = merge(local.relay_runtime_metrics, local.relay_region_share_metrics)
project = var.project_id
name = "orca_relay_${each.key}"
@@ -211,14 +301,14 @@ resource "google_logging_metric" "relay_snapshot" {
label_extractors = {
role = "EXTRACT(jsonPayload.role)"
cell_id = "EXTRACT(jsonPayload.cellId)"
- # No region label: adding one replaces all 21 live metrics (label change = delete+create),
+ # No region label: adding one replaces all 42 live metrics (label change = delete+create),
# which resets history and blanks the relay alert policies during the swap.
}
metric_descriptor {
metric_kind = "DELTA"
value_type = "DISTRIBUTION"
- unit = contains(["sql_latency_ms", "control_renewal_latency_ms_p50", "control_renewal_latency_ms_p95", "control_renewal_latency_ms_max", "http_latency_ms", "event_loop_ms_p99", "db_oldest_wait_ms", "db_wait_ms_max"], each.key) ? "ms" : each.key == "queued_bytes" || each.key == "heap_used_bytes" || each.key == "forwarded_bytes" ? "By" : "1"
+ unit = contains(["sql_latency_ms", "control_rtt_ms_p50", "control_rtt_ms_p95", "control_rtt_ms_max", "client_accept_total_ms_p50", "client_accept_total_ms_p95", "client_accept_total_ms_max", "client_accept_assignment_ms_p95", "client_accept_credential_ms_p95", "client_accept_activity_ms_p95", "client_accept_attach_ms_p95", "client_accept_basis_ms_p95", "control_renewal_latency_ms_p50", "control_renewal_latency_ms_p95", "control_renewal_latency_ms_max", "http_latency_ms", "event_loop_ms_p99", "db_oldest_wait_ms", "db_wait_ms_max"], each.key) ? "ms" : each.key == "queued_bytes" || each.key == "heap_used_bytes" || each.key == "forwarded_bytes" ? "By" : "1"
labels {
key = "role"
@@ -672,6 +762,128 @@ resource "google_monitoring_alert_policy" "relay_cell_process_exit" {
depends_on = [google_logging_metric.relay_incident]
}
+# Why: nothing fired while US desktops sat on asia-east2 cells for weeks in 2026-08. The two
+# per-cell policies below read that as distance, and the fleet-wide one reads it as a bad region
+# hint. All three are MQL because each needs the sum of a DELTA DISTRIBUTION as a volume floor,
+# and the only scalar aligners a `condition_threshold` can apply to a distribution are percentiles.
+# `join` is an inner join and the relay omits its percentile fields on an empty interval, so an
+# idle cell drops out rather than alerting on nothing. The per-cell arms fetch `gce_instance`
+# only: production runs no Cloud Run cells (`relay_cells` is empty), and a future one would need
+# its own arm here. None of the metrics these query exist in the project yet, so what was checked
+# against production is the query shape: the same MQL run over existing metrics of the same kind
+# confirmed the distribution sum, the join arity, the unit literals, and the condition clause.
+resource "google_monitoring_alert_policy" "relay_far_cell_accept_latency" {
+ project = var.project_id
+ display_name = "Orca Relay: far-cell phone accept latency"
+ combiner = "OR"
+ enabled = true
+ notification_channels = var.relay_alert_notification_channels
+
+ conditions {
+ display_name = "Phone accept p95 above 2 s for 15 minutes"
+
+ condition_monitoring_query_language {
+ # percentile(..., 50) over the window, not max: the published value is already a p95, so the
+ # median of the interval p95s reads as sustained slowness instead of one bad 30-second flush.
+ query = <<-EOT
+ {
+ fetch gce_instance::logging.googleapis.com/user/orca_relay_client_accept_total_ms_p95
+ | align delta(15m) | every 15m
+ | group_by [metric.cell_id], [accept_p95_ms: percentile(value.orca_relay_client_accept_total_ms_p95, 50)]
+ ;
+ fetch gce_instance::logging.googleapis.com/user/orca_relay_client_accepts_completed
+ | align delta(15m) | every 15m
+ | group_by [metric.cell_id], [accepts: sum(value.orca_relay_client_accepts_completed)]
+ }
+ | join
+ | condition accept_p95_ms > 2000 'ms' && accepts >= 20 '1'
+ EOT
+ duration = "0s"
+
+ trigger {
+ count = 1
+ }
+ }
+ }
+
+ documentation {
+ content = "Phones on this cell are taking over two seconds to reach relay-hello. Measured separation: an in-region accept completes in 0.3-0.6 s and a cross-Pacific one in 5-10 s, so 2 s sits well outside in-region noise and well below the far-cell floor. The 20-accept floor over 15 minutes keeps a single slow accept on a quiet cell from paging. Check which regions the cell's hosts are actually in before touching capacity: the 2026-08 cause was desktops requesting the wrong region, not a slow cell. Read the per-stage `orca_relay_client_accept_*_ms_p95` metrics to separate distance from assignment, credential, or attach work."
+ mime_type = "text/markdown"
+ }
+
+ depends_on = [google_logging_metric.relay_snapshot]
+}
+
+resource "google_monitoring_alert_policy" "relay_cell_control_rtt" {
+ project = var.project_id
+ display_name = "Orca Relay: cell control round trip"
+ combiner = "OR"
+ enabled = true
+ notification_channels = var.relay_alert_notification_channels
+
+ conditions {
+ display_name = "Control ping p50 above 150 ms for an hour"
+
+ condition_monitoring_query_language {
+ # p50 only. The desktop echoes the pong on its main thread, so the published p95 and max
+ # track renderer stalls, not distance; the median is the only column that reads as distance.
+ query = <<-EOT
+ {
+ fetch gce_instance::logging.googleapis.com/user/orca_relay_control_rtt_ms_p50
+ | align delta(1h) | every 1h
+ | group_by [metric.cell_id], [control_rtt_p50_ms: percentile(value.orca_relay_control_rtt_ms_p50, 50)]
+ ;
+ fetch gce_instance::logging.googleapis.com/user/orca_relay_control_rtt_samples
+ | align delta(1h) | every 1h
+ | group_by [metric.cell_id], [samples: sum(value.orca_relay_control_rtt_samples)]
+ }
+ | join
+ | condition control_rtt_p50_ms > 150 'ms' && samples >= 500 '1'
+ EOT
+ duration = "0s"
+
+ trigger {
+ count = 1
+ }
+ }
+ }
+
+ documentation {
+ content = "The median desktop on this cell is more than 150 ms away from it, which is a mis-homed population rather than a cell fault: an in-region control ping is tens of milliseconds and a US desktop on an asia-east2 cell is 200 ms or more. This is the signal that was missing while roughly 226 of 332 hosts on the asia cells were non-APAC for weeks in 2026-08. Confirm with the assignment table which regions those hosts requested, then rehome; do not restart or drain the cell on this alert alone. The 500-sample floor is about two continuously connected hosts at the 15-second control ping, so a nearly idle cell cannot alert on one desktop. Tuning risk: EU desktops on us-central1 sit at 100-130 ms, so a cell whose population is mostly European can approach 150 ms while correctly homed. Check where the hosts are before treating a first breach as mis-homing, and raise the bar only with that evidence."
+ mime_type = "text/markdown"
+ }
+
+ depends_on = [google_logging_metric.relay_snapshot]
+}
+
+resource "google_monitoring_alert_policy" "relay_region_hint_skew" {
+ project = var.project_id
+ display_name = "Orca Relay: region hint skew"
+ combiner = "OR"
+ enabled = true
+ notification_channels = var.relay_alert_notification_channels
+
+ conditions {
+ display_name = "asia-east2 hint share above 2x its placement share for an hour"
+
+ condition_monitoring_query_language {
+ query = local.relay_region_hint_skew_query
+ duration = "0s"
+
+ trigger {
+ count = 1
+ }
+ }
+ }
+
+ documentation {
+ content = "Desktops are asking the director for asia-east2 far more often than the director actually places them there, which is what silently homed US desktops on asia cells through 2026-08. The alert compares two shares of the same hour and never an absolute share, because an absolute bar is wrong at both ends: measured over twelve hours on 2026-09-07, while the desktop region probe was still mis-picking, asia-east2 was 33.8% of the 33,800 hinted requests but only 7.9% of the 45,364 assignments, and once the probe is fixed the genuine APAC share will climb past any fixed bar that would have caught this. Divergence was 4.27x with a 25.9-point gap, so the 2x and 15-point bars sit well inside the broken state and well outside a healthy one. `unhinted` requests are excluded from the denominator: they were 27% of all requests, and a client change that always sends a hint would move this number without any behaviour changing. Expect this to stay lit until the mis-homed backlog is rehomed, because sticky assignment never re-consults the hint, so a desktop already on an asia cell keeps being placed there no matter what it now asks for. Investigate the desktop region probe first, not relay placement."
+ mime_type = "text/markdown"
+ }
+
+ depends_on = [google_logging_metric.relay_snapshot]
+}
+
# Why: the four signals that had to be assembled by hand during the 2026-09-04 incident.
resource "google_monitoring_dashboard" "relay_incident" {
project = var.project_id
diff --git a/cloud/infra/terraform/variables.tf b/cloud/infra/terraform/variables.tf
index 91f67e8ebe0a..57d73fe75b48 100644
--- a/cloud/infra/terraform/variables.tf
+++ b/cloud/infra/terraform/variables.tf
@@ -245,7 +245,7 @@ variable "relay_regional_placement_enabled" {
variable "relay_region_rehome_source_cell_ids" {
type = set(string)
- description = "Reviewed US Relay cells allowed to advertise and accept the regional rehome source protocol."
+ description = "Reviewed Relay cells, in any configured region, allowed to advertise and accept the regional rehome source protocol."
default = []
}
diff --git a/cloud/package.json b/cloud/package.json
index 62dbadc74553..242bbbd824c3 100644
--- a/cloud/package.json
+++ b/cloud/package.json
@@ -20,7 +20,7 @@
"load:relay:model": "node dev/scripts/run-relay-load-model.mjs",
"load:relay:recovery-gate": "node dev/scripts/run-relay-recovery-wave-gate.mjs",
"ops:relay": "pnpm --filter @orca-cloud/relay-ops dev",
- "pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-admission-workflow.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs",
+ "pretest": "node --test dev/scripts/capture-terraform-plan-baseline.test.mjs dev/scripts/operate-relay-asia-admission.test.mjs dev/scripts/prepare-relay-asia-director-cells.test.mjs dev/scripts/prepare-relay-asia-topology-input.test.mjs dev/scripts/production-cloud-sql-rollout-lock.test.mjs dev/scripts/read-relay-serving-regional-placement-version.test.mjs dev/scripts/relay-asia-admission-workflow.test.mjs dev/scripts/relay-asia-rollout-evidence.test.mjs dev/scripts/relay-asia-topology-workflow.test.mjs dev/scripts/relay-cloud-sql-connection-budget.test.mjs dev/scripts/relay-load-reader-evidence.test.mjs dev/scripts/relay-region-hint-metrics.test.mjs dev/scripts/relay-staging-deploy-identity.test.mjs dev/scripts/sanitize-relay-asia-admission-result.test.mjs dev/scripts/terraform-root-partition.test.mjs dev/scripts/validate-relay-asia-topology-plan.test.mjs ../.github/actions/cloud-sql-rollout-lease/action-contract.test.mjs ../.github/actions/cloud-sql-rollout-lease/storage-lease.test.mjs",
"test": "pnpm -r test && node --test dev/scripts/classify-relay-production-capacity-director.test.mjs dev/scripts/classify-relay-staging-bootstrap.test.mjs dev/scripts/deploy-relay-blue-green.test.mjs dev/scripts/deploy-relay-gce-candidate.test.mjs dev/scripts/deploy-relay-gce-multi-target.test.mjs dev/scripts/github-smoke-token.test.mjs dev/scripts/infra.test.mjs dev/scripts/operate-relay-regional-rehome.test.mjs dev/scripts/power-staging-relay.test.mjs dev/scripts/prepare-relay-capacity-canary.test.mjs dev/scripts/prepare-relay-production-capacity-canary.test.mjs dev/scripts/probe-relay-legacy-admission.test.mjs dev/scripts/probe-relay-rehome-trust.test.mjs dev/scripts/production-cell-image-digest-consistency.test.mjs dev/scripts/read-relay-production-capacity-identity.test.mjs dev/scripts/relay-admin-endpoint-retry-workflow.test.mjs dev/scripts/relay-admin-transient-retry.test.mjs dev/scripts/relay-admission-selector.test.mjs dev/scripts/relay-gce-terraform-fence.test.mjs dev/scripts/relay-load-connection-failure.test.mjs dev/scripts/relay-load-control-peer.test.mjs dev/scripts/relay-load-director-capacity-gate.test.mjs dev/scripts/relay-load-model.test.mjs dev/scripts/relay-load-phase-barrier.test.mjs dev/scripts/relay-load-placement-boundary.test.mjs dev/scripts/relay-load-profile.test.mjs dev/scripts/relay-load-rebind-boundary.test.mjs dev/scripts/relay-load-region-behavior.test.mjs dev/scripts/relay-load-request-unit-boundary.test.mjs dev/scripts/relay-load-run-lifecycle.test.mjs dev/scripts/relay-monitor-evidence.test.mjs dev/scripts/relay-production-capacity-wave.test.mjs dev/scripts/relay-production-capacity-workflow.test.mjs dev/scripts/relay-production-identity-boundaries.test.mjs dev/scripts/relay-production-same-cap-wave.test.mjs dev/scripts/relay-public-workflow-contract.test.mjs dev/scripts/relay-recovery-wave-gate.test.mjs dev/scripts/relay-region-observation-evidence.test.mjs dev/scripts/relay-regional-rehome-workflow.test.mjs dev/scripts/relay-rehome-aggregate-evidence.test.mjs dev/scripts/relay-repository.test.mjs dev/scripts/relay-same-cap-script-census.test.mjs dev/scripts/relay-staging-c4-refresh-workflow.test.mjs dev/scripts/relay-staging-capacity-identity.test.mjs dev/scripts/staging-relay-apply-guard.test.mjs dev/scripts/validate-relay-capacity-plan.test.mjs dev/scripts/verify-relay-capacity-transition.test.mjs dev/scripts/verify-relay-legacy-bootstrap.test.mjs dev/scripts/workload-identity-attribute-conditions.test.mjs",
"typecheck": "pnpm -r typecheck"
},
diff --git a/cloud/packages/relay-contract/src/contract.test.ts b/cloud/packages/relay-contract/src/contract.test.ts
index 805cb8ea698d..391a0877c651 100644
--- a/cloud/packages/relay-contract/src/contract.test.ts
+++ b/cloud/packages/relay-contract/src/contract.test.ts
@@ -6,7 +6,10 @@ import {
HostChallengeSchema,
HostDataAuthSchema,
HostHelloAckSchema,
- HostHelloSchema
+ HostHelloSchema,
+ parseRelayHostCapabilities,
+ RELAY_HOST_CAPABILITIES_HEADER,
+ RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS
} from './control-messages.js'
import {
DeviceCredentialInstallSchema,
@@ -345,3 +348,47 @@ describe('relay protocol contract', () => {
).toBe(false)
})
})
+
+describe('pending connection details capability', () => {
+ it('reads a pending entry with or without the stated kind and device', () => {
+ const ack = {
+ v: 1 as const,
+ generation: 3,
+ controlResumeSecret: 'R'.repeat(43),
+ leaseExpiresAt: 1_800_000_000_000,
+ activeConnIds: []
+ }
+ const identifiers = { connId: 'conn-1', connTicket: 'T'.repeat(43) }
+ expect(HostHelloAckSchema.safeParse({ ...ack, pendingConns: [identifiers] }).success).toBe(true)
+ expect(
+ HostHelloAckSchema.safeParse({
+ ...ack,
+ pendingConns: [{ ...identifiers, kind: 'resume', relayDeviceId: 'device-1' }]
+ }).success
+ ).toBe(true)
+ // Still strict otherwise: an unannounced key must not slip through as data.
+ expect(
+ HostHelloAckSchema.safeParse({
+ ...ack,
+ pendingConns: [{ ...identifiers, reservationId: 'injected' }]
+ }).success
+ ).toBe(false)
+ })
+
+ it('pins the header and token the desktop mirrors by hand', () => {
+ // The desktop cannot import this package; drift silently disables the
+ // feature, so both literals are asserted on each side.
+ expect(RELAY_HOST_CAPABILITIES_HEADER).toBe('x-orca-host-capabilities')
+ expect(RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS).toBe('pending-conn-details')
+ })
+
+ it('reads the advertised capabilities from a control upgrade header', () => {
+ expect(
+ parseRelayHostCapabilities(` ${RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS} , future-thing`)
+ ).toEqual(new Set([RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS, 'future-thing']))
+ // A host that predates the header sends nothing; absence is never capable.
+ expect(parseRelayHostCapabilities(undefined).size).toBe(0)
+ expect(parseRelayHostCapabilities('').size).toBe(0)
+ expect(parseRelayHostCapabilities('x'.repeat(65)).size).toBe(0)
+ })
+})
diff --git a/cloud/packages/relay-contract/src/control-messages.ts b/cloud/packages/relay-contract/src/control-messages.ts
index 0d5f8d1b8518..0daf21e7c281 100644
--- a/cloud/packages/relay-contract/src/control-messages.ts
+++ b/cloud/packages/relay-contract/src/control-messages.ts
@@ -44,8 +44,35 @@ export const HostChallengeAckSchema = z
.object({ challengeId: OpaqueIdSchema, proofB64: Base6432ByteSchema })
.strict()
+// Advertised on the control upgrade rather than in host-hello: HostHelloSchema
+// is strict, so a new hello key is refused by every already-deployed cell.
+export const RELAY_HOST_CAPABILITIES_HEADER = 'x-orca-host-capabilities'
+// The host accepts kind/relayDeviceId on a pendingConns entry. A host that does
+// not advertise this parses those entries strictly and would drop the whole ack.
+export const RELAY_HOST_CAPABILITY_PENDING_CONN_DETAILS = 'pending-conn-details'
+
+export function parseRelayHostCapabilities(
+ header: string | string[] | undefined
+): ReadonlySet {
+ const raw = Array.isArray(header) ? header.join(',') : (header ?? '')
+ return new Set(
+ raw
+ .split(',')
+ .map((token) => token.trim())
+ .filter((token) => token.length > 0 && token.length <= 64)
+ .slice(0, 16)
+ )
+}
+
+// kind/relayDeviceId are optional so an entry stays readable by a host that
+// predates them; the cell only emits them to a host that advertised support.
const PendingConnectionSchema = z
- .object({ connId: OpaqueIdSchema, connTicket: Base64Url32ByteSchema })
+ .object({
+ connId: OpaqueIdSchema,
+ connTicket: Base64Url32ByteSchema,
+ kind: ConnectionKindSchema.optional(),
+ relayDeviceId: OpaqueIdSchema.optional()
+ })
.strict()
export const HostHelloAckSchema = z
diff --git a/cloud/packages/relay-contract/src/relay-regions.ts b/cloud/packages/relay-contract/src/relay-regions.ts
index 38ac36cd7380..6b8837829df0 100644
--- a/cloud/packages/relay-contract/src/relay-regions.ts
+++ b/cloud/packages/relay-contract/src/relay-regions.ts
@@ -8,6 +8,15 @@ export type RelayRegion = z.infer
export const RELAY_DEFAULT_REGION: RelayRegion = 'us-central1'
+// Field-name segment for the flat per-region runtime counters, spelled out rather than derived so
+// the Terraform side can hold the same literal and a test can compare the two. `satisfies` makes a
+// new region a compile error here, which is the point: a region with no segment would silently
+// drop out of the region-skew alert's denominators.
+export const RELAY_REGION_METRIC_SEGMENTS = {
+ 'us-central1': 'UsCentral1',
+ 'asia-east2': 'AsiaEast2'
+} as const satisfies Record
+
const RelayProbeOriginSchema = z.string().url().max(2_048).refine(isCanonicalHttpsOrigin)
export const RelayRegionCatalogResponseSchema = z
diff --git a/cloud/pnpm-lock.yaml b/cloud/pnpm-lock.yaml
index 27fdd29071aa..3598ae549308 100644
--- a/cloud/pnpm-lock.yaml
+++ b/cloud/pnpm-lock.yaml
@@ -24,14 +24,14 @@ importers:
apps/relay:
dependencies:
'@hono/node-server':
- specifier: ^1.19.14
- version: 1.19.14(hono@4.12.27)
+ specifier: ^1.19.17
+ version: 1.19.17(hono@4.13.7)
'@orca-cloud/relay-contract':
specifier: workspace:*
version: link:../../packages/relay-contract
hono:
- specifier: ^4.12.27
- version: 4.12.27
+ specifier: ^4.13.7
+ version: 4.13.7
jose:
specifier: ^6.1.3
version: 6.2.3
@@ -42,8 +42,8 @@ importers:
specifier: ^1.0.3
version: 1.0.3
ws:
- specifier: ^8.18.3
- version: 8.21.0
+ specifier: ^8.21.3
+ version: 8.21.3
zod:
specifier: ^3.25.76
version: 3.25.76
@@ -70,11 +70,11 @@ importers:
apps/relay-fence-broker:
dependencies:
'@hono/node-server':
- specifier: ^1.19.14
- version: 1.19.14(hono@4.12.27)
+ specifier: ^1.19.17
+ version: 1.19.17(hono@4.13.7)
hono:
- specifier: ^4.12.27
- version: 4.12.27
+ specifier: ^4.13.7
+ version: 4.13.7
zod:
specifier: ^3.25.76
version: 3.25.76
@@ -95,11 +95,11 @@ importers:
apps/relay-ops:
dependencies:
'@hono/node-server':
- specifier: ^1.19.14
- version: 1.19.14(hono@4.12.27)
+ specifier: ^1.19.17
+ version: 1.19.17(hono@4.13.7)
hono:
- specifier: ^4.12.27
- version: 4.12.27
+ specifier: ^4.13.7
+ version: 4.13.7
zod:
specifier: ^3.25.76
version: 3.25.76
@@ -300,8 +300,8 @@ packages:
cpu: [x64]
os: [win32]
- '@hono/node-server@1.19.14':
- resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==}
+ '@hono/node-server@1.19.17':
+ resolution: {integrity: sha512-dSneS5qhiauZWGDCeK4o695Xd9nUNjviSZCMQrj10eetr8Uln1ucn6bbphOM6UynAMMtNIzZNSpL9vnASJwrPQ==}
engines: {node: '>=18.14.1'}
peerDependencies:
hono: ^4
@@ -507,8 +507,8 @@ packages:
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
os: [darwin]
- hono@4.12.27:
- resolution: {integrity: sha512-1yrb/+w6HWQJrUCLkJ2IF5jNIPvvFkblV5RNOYl6bV+OA6p9GLcMpHFFGTosSvHvcAUibuUukRqhlYI4z32C7Q==}
+ hono@4.13.7:
+ resolution: {integrity: sha512-c8/gF9ac8Y78/agExVocyLevgR+JlpNB444Py0FSX8pJoPdYUfUzRcXtYEYGwt6l19qIlVZPN5Mfsw9jFShmQQ==}
engines: {node: '>=16.9.0'}
jose@6.2.3:
@@ -587,8 +587,8 @@ packages:
magic-string@0.30.21:
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
- nanoid@3.3.13:
- resolution: {integrity: sha512-sPdqC6ByMVVGvF1ynvvMo0/o+oD1VX7DaHhijt1bFgjvBkHBib4t49GoNDhf2NDta4oeUNlaGbSt5K7qjZ955Q==}
+ nanoid@3.3.18:
+ resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true
@@ -640,8 +640,8 @@ packages:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
- postcss@8.5.15:
- resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
+ postcss@8.5.28:
+ resolution: {integrity: sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A==}
engines: {node: ^10 || ^12 || >=14}
postgres-array@2.0.0:
@@ -805,8 +805,8 @@ packages:
engines: {node: '>=8'}
hasBin: true
- ws@8.21.0:
- resolution: {integrity: sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==}
+ ws@8.21.3:
+ resolution: {integrity: sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==}
engines: {node: '>=10.0.0'}
peerDependencies:
bufferutil: ^4.0.1
@@ -920,9 +920,9 @@ snapshots:
'@esbuild/win32-x64@0.28.1':
optional: true
- '@hono/node-server@1.19.14(hono@4.12.27)':
+ '@hono/node-server@1.19.17(hono@4.13.7)':
dependencies:
- hono: 4.12.27
+ hono: 4.13.7
'@jridgewell/sourcemap-codec@1.5.5': {}
@@ -1109,7 +1109,7 @@ snapshots:
fsevents@2.3.3:
optional: true
- hono@4.12.27: {}
+ hono@4.13.7: {}
jose@6.2.3: {}
@@ -1166,7 +1166,7 @@ snapshots:
dependencies:
'@jridgewell/sourcemap-codec': 1.5.5
- nanoid@3.3.13: {}
+ nanoid@3.3.18: {}
obug@2.1.3: {}
@@ -1211,9 +1211,9 @@ snapshots:
picomatch@4.0.4: {}
- postcss@8.5.15:
+ postcss@8.5.28:
dependencies:
- nanoid: 3.3.13
+ nanoid: 3.3.18
picocolors: 1.1.1
source-map-js: 1.2.1
@@ -1288,7 +1288,7 @@ snapshots:
dependencies:
lightningcss: 1.32.0
picomatch: 4.0.4
- postcss: 8.5.15
+ postcss: 8.5.28
rolldown: 1.0.3
tinyglobby: 0.2.17
optionalDependencies:
@@ -1329,7 +1329,7 @@ snapshots:
siginfo: 2.0.0
stackback: 0.0.2
- ws@8.21.0: {}
+ ws@8.21.3: {}
xtend@4.0.2: {}
diff --git a/config/fork-ownership.json b/config/fork-ownership.json
index 52bc9cd35e04..208743395aac 100644
--- a/config/fork-ownership.json
+++ b/config/fork-ownership.json
@@ -659,7 +659,7 @@
"kind": "registration",
"lines": [
"} from './fork-agent-composer/AgentComposer'",
- "import { pushHistory, seedHistory } from './fork-agent-composer/agent-composer-history'"
+ "import { seedHistory } from './fork-agent-composer/agent-composer-history'"
]
},
{
@@ -741,7 +741,11 @@
"path": "src/renderer/src/components/native-chat/native-chat-composer-state.ts",
"feature": "agent-composer",
"kind": "registration",
- "lines": [" discovery.skills,", " if (!leadsDraft && !hasSlashSkills) {"]
+ "lines": [
+ " discovery.skills,",
+ " if (!leadsDraft && !hasSlashSkills) {",
+ "} from './fork-agent-composer/agent-composer-history'"
+ ]
},
{
"path": "src/renderer/src/components/native-chat/use-native-chat-live-session-pending.test.ts",
@@ -789,7 +793,7 @@
"path": "src/renderer/src/components/native-chat/native-chat-picker-items.ts",
"feature": "skill-plugin-attribution",
"kind": "registration",
- "lines": [" pluginName?: string", " id: `skill:${token}`,"]
+ "lines": [" pluginName?: string", " namespacePluginSkills = false"]
},
{
"path": "src/renderer/src/components/native-chat/native-chat-pty-session-options.ts",
@@ -1304,7 +1308,7 @@
"kind": "passthrough",
"lines": [
"import { paneFocusOwnershipArgs } from './fork-terminal-dock/pane-focus-ownership-args'",
- " repairedDpr = resumeTerminalVisibilityHeavy(manager, isActive, paneFocusOwnershipArgs(tabId))"
+ " paneFocusOwnershipArgs(tabId)"
]
},
{
@@ -1364,7 +1368,7 @@
"kind": "passthrough",
"lines": [
" tabId: string",
- " }, [isActiveRef, isVisible, isVisibleRef, managerRef, panePtyBindingsRef, tabId])"
+ " }, [isActiveRef, isChatViewMode, isVisible, isVisibleRef, managerRef, panePtyBindingsRef, tabId])"
]
},
{
@@ -2218,7 +2222,7 @@
]
},
{
- "path": "src/renderer/src/components/right-sidebar/AiVaultSessionVirtualList.tsx",
+ "path": "src/renderer/src/components/right-sidebar/AiVaultVirtualRow.tsx",
"feature": "session-handoff",
"kind": "import-swap",
"lines": [
@@ -2423,6 +2427,131 @@
" getSidebarSectionSeparatorEntry(),"
]
},
+ {
+ "path": "src/main/providers/ssh-pty-provider.ts",
+ "feature": "terminal-dock",
+ "kind": "registration",
+ "lines": [
+ " writeAcknowledged = (id: string, data: string): Promise =>",
+ " this.rpcOperations.writeAcknowledged(id, data)"
+ ]
+ },
+ {
+ "path": "src/main/runtime/mobile-session-terminal-projection.ts",
+ "feature": "terminal-dock",
+ "kind": "registration",
+ "lines": [
+ "import type { TerminalDockPaneState } from '../../shared/fork-terminal-dock/terminal-dock-pane-state'",
+ " const terminalDockByPaneKey = terminalDockByPaneKeyByTabId.get(tab.id)"
+ ]
+ },
+ {
+ "path": "src/main/runtime/orca-runtime-publish-pty-backed-mobile-session-terminal.ts",
+ "feature": "terminal-dock",
+ "kind": "passthrough",
+ "lines": [
+ " const terminalDockByPaneKey =",
+ " ...(terminalDockByPaneKey ? { terminalDockByPaneKey } : {}),"
+ ]
+ },
+ {
+ "path": "src/main/runtime/orca-runtime-runtime-id.ts",
+ "feature": "terminal-dock",
+ "kind": "registration",
+ "lines": [
+ "import type { TerminalDockPaneState } from '../../shared/fork-terminal-dock/terminal-dock-pane-state'",
+ " rendererDockByPaneKeyByParentTabId: ReadonlyMap<"
+ ]
+ },
+ {
+ "path": "src/main/runtime/runtime-mobile-session-projection.ts",
+ "feature": "terminal-dock",
+ "kind": "passthrough",
+ "lines": [
+ " ...(tab.terminalDockByPaneKey ? { terminalDockByPaneKey: tab.terminalDockByPaneKey } : {}),"
+ ]
+ },
+ {
+ "path": "src/preload/index.ts",
+ "feature": "session-handoff",
+ "kind": "registration",
+ "lines": [
+ "import { buildForkSessionHandoffApi } from './fork-session-handoff/session-handoff-preload-api'",
+ " forkSessionHandoff: buildForkSessionHandoffApi(),"
+ ]
+ },
+ {
+ "path": "src/preload/index.ts",
+ "feature": "session-info",
+ "kind": "registration",
+ "lines": [
+ "import { buildForkSessionInfoApi } from './fork-session-info/session-info-preload-api'",
+ " forkSessionInfo: buildForkSessionInfoApi(),"
+ ]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/NativeChatMessageList.tsx",
+ "feature": "native-chat-relay",
+ "kind": "import-swap",
+ "lines": [
+ "import type { NativeChatLiveSession } from './fork-native-chat-relay/use-native-chat-live-session'"
+ ]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/NativeChatMessageList.tsx",
+ "feature": "native-chat-width",
+ "kind": "registration",
+ "lines": [
+ "import { useNativeChatWidthClassName } from './fork-native-chat-width/use-native-chat-width'",
+ " const widthClassName = useNativeChatWidthClassName()"
+ ]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/NativeChatToolRun.tsx",
+ "feature": "native-chat-coloring",
+ "kind": "registration",
+ "lines": [
+ " ",
+ " "
+ ]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts",
+ "feature": "agent-composer",
+ "kind": "import-swap",
+ "lines": [
+ "import { pushHistory, type HistoryState } from './fork-agent-composer/agent-composer-history'",
+ "import type { AgentComposerImageAttachment as NativeChatComposerImageAttachment } from './fork-agent-composer/AgentComposerField'"
+ ]
+ },
+ {
+ "path": "src/renderer/src/lib/pane-manager/terminal-scroll-intent-key-retention.test.ts",
+ "feature": "terminal-dock",
+ "kind": "passthrough",
+ "lines": [" dockContainer: {} as never,"]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.tsx",
+ "feature": "agent-composer",
+ "kind": "import-swap",
+ "lines": [
+ "import type { AgentComposerImageAttachment as NativeChatComposerImageAttachment } from './fork-agent-composer/AgentComposerField'"
+ ]
+ },
+ {
+ "path": "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.test.tsx",
+ "feature": "agent-composer",
+ "kind": "import-swap",
+ "lines": [
+ "import type { AgentComposerImageAttachment as NativeChatComposerImageAttachment } from './fork-agent-composer/AgentComposerField'"
+ ]
+ },
+ {
+ "path": "src/shared/structured-agent-session-options.ts",
+ "feature": "native-chat-session-options",
+ "kind": "passthrough",
+ "lines": [" undefined,"]
+ },
{
"path": "src/main/persistence/applying-settings/ui-state-read.ts",
"feature": "workspace-activity-window",
@@ -3152,42 +3281,12 @@
"status": "permanent",
"deleted": true
},
- {
- "path": "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.tsx",
- "reason": "Chip renderer for the deleted NativeChatComposerField, whose attachment type it imports; the fork's AgentComposerField renders AgentComposerAttachmentThumbnail instead, so upstream's copy is unreferenced and does not compile.",
- "status": "permanent",
- "deleted": true
- },
- {
- "path": "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.test.tsx",
- "reason": "Tests the deleted NativeChatImageAttachmentPreview above.",
- "status": "permanent",
- "deleted": true
- },
{
"path": "src/renderer/src/components/native-chat/native-chat-composer-autogrow.test.tsx",
"reason": "Superseded by the fork copy's test in fork-agent-composer.",
"status": "permanent",
"deleted": true
},
- {
- "path": "src/renderer/src/components/native-chat/native-chat-composer-scope-cache.test.ts",
- "reason": "Superseded by the fork scope cache's own test.",
- "status": "permanent",
- "deleted": true
- },
- {
- "path": "src/renderer/src/components/native-chat/native-chat-composer-scope-cache.ts",
- "reason": "Superseded by agent-composer-scope-cache, which keys by composer scope rather than chat pane.",
- "status": "permanent",
- "deleted": true
- },
- {
- "path": "src/renderer/src/components/native-chat/native-chat-draft-cache.test.ts",
- "reason": "Superseded by the fork draft cache's own test.",
- "status": "permanent",
- "deleted": true
- },
{
"path": "src/renderer/src/components/native-chat/native-chat-runtime-image-send.ts",
"status": "permanent",
@@ -3200,24 +3299,6 @@
"deleted": true,
"reason": "Upstream's chat-only PTY send hook; fork-agent-composer's useAgentComposerSend already serves both the chat pane and the terminal dock."
},
- {
- "path": "src/renderer/src/components/native-chat/native-chat-draft-cache.ts",
- "reason": "Superseded by agent-composer-draft-cache, which subscribes so concurrent composer hosts converge.",
- "status": "permanent",
- "deleted": true
- },
- {
- "path": "src/renderer/src/components/native-chat/use-native-chat-composer-keydown.test.tsx",
- "reason": "Superseded by the fork copy's test in fork-agent-composer.",
- "status": "permanent",
- "deleted": true
- },
- {
- "path": "src/renderer/src/components/native-chat/use-native-chat-composer-keydown.ts",
- "reason": "Replaced by useAgentComposerKeydown in fork-agent-composer, which adds the docked composer's history recall.",
- "status": "permanent",
- "deleted": true
- },
{
"path": "src/renderer/src/components/native-chat/use-native-chat-draft.ts",
"reason": "Superseded by useAgentComposerDraft, which serves both the chat pane and the dock.",
@@ -3316,29 +3397,10 @@
"reason": "Encodes the fork's own (tagless) release-history shape for cross-version wire tests.",
"status": "permanent"
},
- {
- "path": "src/shared/native-file-drop.ts",
- "reason": "Gives the composer drop payload the tabId/paneLeafId its terminal sibling already carries, so a broadcast drop reaches only the composer it landed on. Upstream's shared drop contract, read by preload, main, and every drop consumer — a forked copy would not reroute them.",
- "status": "pending-upstream",
- "ledger": "docs/fork-upstreaming.md#composer-file-drop-pane-scoping"
- },
- {
- "path": "src/renderer/src/components/native-chat/use-native-chat-file-attachment-actions.ts",
- "reason": "Ignores a composer file drop addressed to a different composer; unaddressed payloads still attach. Correctness fix to upstream routing, not fork-specific behavior.",
- "status": "pending-upstream",
- "ledger": "docs/fork-upstreaming.md#composer-file-drop-pane-scoping"
- },
{
"path": "src/renderer/src/components/native-chat/use-native-chat-file-attachment-actions.test.tsx",
- "reason": "Covers the composer file-drop pane scoping fix: addressed, cross-pane, cross-tab, and unaddressed drops. Ships with the fix it exercises.",
- "status": "pending-upstream",
- "ledger": "docs/fork-upstreaming.md#composer-file-drop-pane-scoping"
- },
- {
- "path": "src/shared/native-file-drop.test.ts",
- "reason": "Adds composer tab/pane routing cases alongside the existing terminal ones, covering the composer drop payload change in the same file.",
- "status": "pending-upstream",
- "ledger": "docs/fork-upstreaming.md#composer-file-drop-pane-scoping"
+ "reason": "Hook-level coverage of upstream's composer drop scoping against the fork's own composer field, which publishes the scope key in place of the deleted upstream field.",
+ "status": "permanent"
},
{
"path": "src/renderer/src/components/native-chat/use-native-chat-paste-bridge.ts",
@@ -3567,7 +3629,7 @@
"removed": 0
},
"src/main/providers/ssh-pty-provider-rpc-operations.ts": {
- "added": 6,
+ "added": 8,
"removed": 0
},
"src/main/rate-limits/service/service-fetch-policy.ts": {
@@ -3779,8 +3841,8 @@
"removed": 83
},
"src/renderer/src/components/native-chat/NativeChatComposer.tsx": {
- "added": 232,
- "removed": 282
+ "added": 201,
+ "removed": 277
},
"src/renderer/src/components/native-chat/NativeChatInteractiveCard.test.tsx": {
"added": 21,
@@ -3800,7 +3862,7 @@
},
"src/renderer/src/components/native-chat/NativeChatResolvedView.tsx": {
"added": 16,
- "removed": 18
+ "removed": 17
},
"src/renderer/src/components/native-chat/NativeChatView.test.tsx": {
"added": 4,
@@ -3815,11 +3877,11 @@
"removed": 1
},
"src/renderer/src/components/native-chat/native-chat-composer-state.test.ts": {
- "added": 139,
+ "added": 141,
"removed": 49
},
"src/renderer/src/components/native-chat/native-chat-composer-state.ts": {
- "added": 33,
+ "added": 35,
"removed": 41
},
"src/renderer/src/components/native-chat/native-chat-composer-types.ts": {
@@ -3831,7 +3893,7 @@
"removed": 1
},
"src/renderer/src/components/native-chat/native-chat-picker-items.ts": {
- "added": 48,
+ "added": 71,
"removed": 15
},
"src/renderer/src/components/native-chat/native-chat-pty-session-options.ts": {
@@ -3859,8 +3921,8 @@
"removed": 2
},
"src/renderer/src/components/native-chat/native-chat-structured-send-composition-clear.test.tsx": {
- "added": 2,
- "removed": 2
+ "added": 12,
+ "removed": 3
},
"src/renderer/src/components/native-chat/use-native-chat-composer-attachments.test.tsx": {
"added": 78,
@@ -3922,7 +3984,7 @@
"added": 2,
"removed": 0
},
- "src/renderer/src/components/right-sidebar/AiVaultSessionVirtualList.tsx": {
+ "src/renderer/src/components/right-sidebar/AiVaultVirtualRow.tsx": {
"added": 3,
"removed": 4
},
@@ -4112,7 +4174,7 @@
},
"src/renderer/src/components/terminal-pane/pane-helpers.ts": {
"added": 25,
- "removed": 6
+ "removed": 8
},
"src/renderer/src/components/terminal-pane/terminal-input-quarantine.ts": {
"added": 84,
@@ -4139,11 +4201,11 @@
"removed": 0
},
"src/renderer/src/components/terminal-pane/terminal-visibility-resume.test.ts": {
- "added": 67,
- "removed": 0
+ "added": 73,
+ "removed": 1
},
"src/renderer/src/components/terminal-pane/terminal-visibility-resume.ts": {
- "added": 15,
+ "added": 19,
"removed": 6
},
"src/renderer/src/components/terminal-pane/use-terminal-pane-global-effects.ts": {
@@ -4167,7 +4229,7 @@
"removed": 0
},
"src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.test.ts": {
- "added": 12,
+ "added": 13,
"removed": 0
},
"src/renderer/src/components/terminal-pane/use-terminal-window-wake-recovery.ts": {
@@ -4498,6 +4560,58 @@
"added": 2,
"removed": 0
},
+ "src/main/providers/ssh-pty-provider.ts": {
+ "added": 4,
+ "removed": 0
+ },
+ "src/main/runtime/mobile-session-terminal-projection.ts": {
+ "added": 12,
+ "removed": 0
+ },
+ "src/main/runtime/orca-runtime-publish-pty-backed-mobile-session-terminal.ts": {
+ "added": 11,
+ "removed": 0
+ },
+ "src/main/runtime/orca-runtime-runtime-id.ts": {
+ "added": 8,
+ "removed": 0
+ },
+ "src/main/runtime/runtime-mobile-session-projection.ts": {
+ "added": 1,
+ "removed": 0
+ },
+ "src/preload/index.ts": {
+ "added": 4,
+ "removed": 0
+ },
+ "src/renderer/src/components/native-chat/NativeChatMessageList.tsx": {
+ "added": 5,
+ "removed": 2
+ },
+ "src/renderer/src/components/native-chat/NativeChatToolRun.tsx": {
+ "added": 7,
+ "removed": 2
+ },
+ "src/renderer/src/components/native-chat/use-native-chat-structured-composer-send.ts": {
+ "added": 2,
+ "removed": 2
+ },
+ "src/renderer/src/lib/pane-manager/terminal-scroll-intent-key-retention.test.ts": {
+ "added": 1,
+ "removed": 0
+ },
+ "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.tsx": {
+ "added": 1,
+ "removed": 1
+ },
+ "src/renderer/src/components/native-chat/NativeChatImageAttachmentPreview.test.tsx": {
+ "added": 1,
+ "removed": 1
+ },
+ "src/shared/structured-agent-session-options.ts": {
+ "added": 1,
+ "removed": 0
+ },
"src/main/persistence/applying-settings/ui-state-read.ts": {
"added": 6,
"removed": 0
diff --git a/config/max-lines-baseline.txt b/config/max-lines-baseline.txt
index 1b3ca7cdbddd..6d4e17066c8c 100644
--- a/config/max-lines-baseline.txt
+++ b/config/max-lines-baseline.txt
@@ -9,6 +9,3 @@ inline src/main/ssh/ssh-relay-deploy.ts
inline src/main/ssh/ssh-relay-session.ts
inline src/relay/pty-handler.ts
inline src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts
-mobile-config app/h/*/source-control/*.tsx
-mobile-config app/index.tsx
-mobile-config src/transport/rpc-client.ts
diff --git a/config/oxlint-performance-audit.json b/config/oxlint-performance-audit.json
index 2912c6b8e032..15d3fcd0f688 100644
--- a/config/oxlint-performance-audit.json
+++ b/config/oxlint-performance-audit.json
@@ -28,6 +28,7 @@
"app-store-performance/require-selector": "warn",
"app-store-performance/no-identity-selector": "warn",
"app-store-performance/no-fresh-selector-result": "warn",
+ "app-store-performance/no-nested-fresh-under-shallow": "warn",
"quadratic-buffer-concat/no-loop-carried-concat": "warn",
"sort-comparator-performance/no-repeated-collator": "warn"
},
diff --git a/config/oxlint-plugins/app-store-performance.mjs b/config/oxlint-plugins/app-store-performance.mjs
index 9da732f5825e..d8bfe4131d9e 100644
--- a/config/oxlint-plugins/app-store-performance.mjs
+++ b/config/oxlint-plugins/app-store-performance.mjs
@@ -8,6 +8,19 @@ const ALLOCATING_METHODS = new Set([
'toSpliced',
'with'
])
+const ALLOCATING_OBJECT_STATICS = new Set([
+ 'assign',
+ 'create',
+ 'entries',
+ 'fromEntries',
+ 'keys',
+ 'values'
+])
+const FUNCTION_NODES = new Set([
+ 'ArrowFunctionExpression',
+ 'FunctionDeclaration',
+ 'FunctionExpression'
+])
function identifierName(node) {
return node?.type === 'Identifier' ? node.name : null
@@ -25,8 +38,12 @@ function propertyName(node) {
: null
}
+function functionNode(node) {
+ return FUNCTION_NODES.has(node?.type) ? node : null
+}
+
function returnedExpressions(selector) {
- if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
+ if (!functionNode(selector)) {
return []
}
if (selector.body.type !== 'BlockStatement') {
@@ -37,10 +54,7 @@ function returnedExpressions(selector) {
if (!node || typeof node !== 'object') {
return
}
- if (
- node !== selector.body &&
- ['ArrowFunctionExpression', 'FunctionDeclaration', 'FunctionExpression'].includes(node.type)
- ) {
+ if (node !== selector.body && FUNCTION_NODES.has(node.type)) {
return
}
if (node.type === 'ReturnStatement') {
@@ -76,10 +90,7 @@ function unwrapShallowSelector(selector, shallowHooks) {
}
function isIdentitySelector(selector) {
- if (selector?.type !== 'ArrowFunctionExpression' && selector?.type !== 'FunctionExpression') {
- return false
- }
- const parameter = selector.params[0]
+ const parameter = functionNode(selector)?.params[0]
if (parameter?.type !== 'Identifier') {
return false
}
@@ -88,14 +99,23 @@ function isIdentitySelector(selector) {
)
}
-function isAllocatingExpression(expression) {
- if (expression?.type === 'ConditionalExpression') {
- return (
- isAllocatingExpression(expression.consequent) || isAllocatingExpression(expression.alternate)
- )
- }
- if (expression?.type === 'LogicalExpression') {
- return isAllocatingExpression(expression.left) || isAllocatingExpression(expression.right)
+/**
+ * `everyBranch` decides how a conditional counts. An inline selector is flagged
+ * when ANY branch allocates; a helper the selector delegates to must allocate on
+ * EVERY branch, so the `cache.get(k) ?? build(state)` identity-caching shape is
+ * not a false positive.
+ */
+function allocates(expression, everyBranch) {
+ const branches =
+ expression?.type === 'ConditionalExpression'
+ ? [expression.consequent, expression.alternate]
+ : expression?.type === 'LogicalExpression'
+ ? [expression.left, expression.right]
+ : null
+ if (branches) {
+ return everyBranch
+ ? branches.every((branch) => allocates(branch, true))
+ : branches.some((branch) => allocates(branch, false))
}
if (
expression?.type === 'ArrayExpression' ||
@@ -107,44 +127,104 @@ function isAllocatingExpression(expression) {
if (expression?.type !== 'CallExpression') {
return false
}
- const method = propertyName(expression.callee)
- if (method && ALLOCATING_METHODS.has(method)) {
- return true
- }
const callee = expression.callee
+ const method = propertyName(callee)
return (
- callee.type === 'MemberExpression' &&
- identifierName(callee.object) === 'Object' &&
- ['assign', 'create', 'entries', 'fromEntries', 'keys', 'values'].includes(propertyName(callee))
+ ALLOCATING_METHODS.has(method) ||
+ (identifierName(callee.object) === 'Object' && ALLOCATING_OBJECT_STATICS.has(method))
)
}
-function importedLocalName(specifier, importedName) {
- if (specifier.type !== 'ImportSpecifier' || identifierName(specifier.imported) !== importedName) {
- return null
+function isAllocatingExpression(expression) {
+ return allocates(expression, false)
+}
+
+// Project-local zustand hooks follow the useStore convention; React's
+// useSyncExternalStore matches that shape but is not a store subscription.
+const STORE_HOOK_NAME = /^use[A-Z][A-Za-z0-9]*Store$/
+const NON_STORE_HOOKS = new Set(['useSyncExternalStore'])
+
+function isLocalModuleSource(source) {
+ return typeof source === 'string' && (source.startsWith('.') || source.startsWith('@/'))
+}
+
+/** Module scope only: a component-local helper must not shadow a same-named import. */
+function isModuleScope(node) {
+ const parent = node.parent
+ return (
+ parent?.type === 'Program' ||
+ (parent?.type === 'ExportNamedDeclaration' && parent.parent?.type === 'Program')
+ )
+}
+
+/** Records module-scope `const selectX = (state) => ...` so identifier selectors resolve. */
+function recordNamedSelector(node, state) {
+ if (!isModuleScope(node)) {
+ return
}
- return identifierName(specifier.local)
+ const declared =
+ node.type === 'FunctionDeclaration'
+ ? [[node.id, node]]
+ : node.declarations.map((declarator) => [declarator.id, declarator.init])
+ for (const [id, initializer] of declared) {
+ const name = identifierName(id)
+ if (name && functionNode(initializer)) {
+ state.namedSelectors.set(name, initializer)
+ }
+ }
+}
+
+/** Inline function, or a module-scope selector referenced by name. */
+function resolveSelector(argument, state) {
+ return functionNode(argument) ?? state.namedSelectors.get(identifierName(argument)) ?? null
+}
+
+/**
+ * One hop: a selector that delegates to a module-scope helper is the idiomatic
+ * shape here, and neither the inline-body check nor a reviewer reading the call
+ * site can see what that helper returns. An unresolvable helper is left alone.
+ */
+function expandThroughNamedHelper(expression, state) {
+ const helper =
+ expression?.type === 'CallExpression'
+ ? state.namedSelectors.get(identifierName(expression.callee))
+ : undefined
+ const returned = helper ? returnedExpressions(helper) : []
+ return returned.length > 0 && returned.every((entry) => allocates(entry, true))
+ ? returned
+ : [expression]
}
function createRuleState() {
return {
appStoreHooks: new Set(),
- shallowHooks: new Set()
+ shallowHooks: new Set(),
+ namedSelectors: new Map(),
+ deferredCalls: []
}
}
function recordImports(node, state) {
- if (node.source?.value === 'zustand/react/shallow') {
- for (const specifier of node.specifiers) {
- const localName = importedLocalName(specifier, 'useShallow')
- if (localName) {
- state.shallowHooks.add(localName)
- }
- }
- }
+ const source = node.source?.value
for (const specifier of node.specifiers) {
- const localName = importedLocalName(specifier, 'useAppStore')
- if (localName) {
+ if (specifier.type !== 'ImportSpecifier') {
+ continue
+ }
+ const imported = identifierName(specifier.imported)
+ const localName = identifierName(specifier.local)
+ if (!imported || !localName) {
+ continue
+ }
+ if (source === 'zustand/react/shallow' && imported === 'useShallow') {
+ state.shallowHooks.add(localName)
+ }
+ // useAppStore is the app store wherever it is re-exported from; sibling
+ // stores are trusted by naming convention only when they come from this codebase.
+ if (
+ STORE_HOOK_NAME.test(imported) &&
+ !NON_STORE_HOOKS.has(imported) &&
+ (imported === 'useAppStore' || isLocalModuleSource(source))
+ ) {
state.appStoreHooks.add(localName)
}
}
@@ -176,52 +256,107 @@ function requireSelectorRule() {
}
}
-function noIdentitySelectorRule() {
+/**
+ * Selector arguments are collected during traversal and judged at Program:exit so a
+ * selector hoisted below its call site still resolves.
+ */
+function deferredSelectorRule(inspect) {
const state = createRuleState()
return {
ImportDeclaration(node) {
recordImports(node, state)
},
+ FunctionDeclaration(node) {
+ recordNamedSelector(node, state)
+ },
+ VariableDeclaration(node) {
+ recordNamedSelector(node, state)
+ },
CallExpression(node) {
- if (!isAppStoreCall(node, state)) {
- return
+ if (isAppStoreCall(node, state)) {
+ state.deferredCalls.push(node)
}
- const { selector } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
- if (isIdentitySelector(selector)) {
- this.report({
- node: selector,
- message:
- 'Select the smallest required fields instead of subscribing to the entire app store.'
+ },
+ 'Program:exit'() {
+ for (const node of state.deferredCalls) {
+ const { selector: argument, shallow } = unwrapShallowSelector(
+ node.arguments[0],
+ state.shallowHooks
+ )
+ const report = inspect({
+ selector: resolveSelector(argument, state),
+ shallow,
+ state
})
+ if (report) {
+ this.report(report)
+ }
}
}
}
}
+function noIdentitySelectorRule() {
+ return deferredSelectorRule(({ selector }) =>
+ isIdentitySelector(selector)
+ ? {
+ node: selector,
+ message:
+ 'Select the smallest required fields instead of subscribing to the entire app store.'
+ }
+ : null
+ )
+}
+
function noFreshSelectorResultRule() {
- const state = createRuleState()
- return {
- ImportDeclaration(node) {
- recordImports(node, state)
- },
- CallExpression(node) {
- if (!isAppStoreCall(node, state)) {
- return
- }
- const { selector, shallow } = unwrapShallowSelector(node.arguments[0], state.shallowHooks)
- if (shallow) {
- return
- }
- const freshResult = returnedExpressions(selector).find(isAllocatingExpression)
- if (freshResult) {
- this.report({
+ return deferredSelectorRule(({ selector, shallow, state }) => {
+ if (shallow || !selector) {
+ return null
+ }
+ const freshResult = returnedExpressions(selector)
+ .flatMap((expression) => expandThroughNamedHelper(expression, state))
+ .find(isAllocatingExpression)
+ return freshResult
+ ? {
node: freshResult,
message:
'This selector returns a fresh reference on every store write; select a stable field, cache the result, or use useShallow.'
- })
- }
- }
+ }
+ : null
+ })
+}
+
+/** useShallow compares one level deep, so a fresh reference nested inside its result never matches. */
+function nestedFreshValues(expression) {
+ if (expression?.type === 'ObjectExpression') {
+ return expression.properties
+ .map((property) => (property.type === 'Property' ? property.value : null))
+ .filter(Boolean)
+ }
+ if (expression?.type === 'ArrayExpression') {
+ return expression.elements.filter(Boolean)
}
+ return []
+}
+
+function noNestedFreshUnderShallowRule() {
+ return deferredSelectorRule(({ selector, shallow, state }) => {
+ if (!shallow || !selector) {
+ return null
+ }
+ const nestedFresh = returnedExpressions(selector)
+ .flatMap((expression) => expandThroughNamedHelper(expression, state))
+ .flatMap(nestedFreshValues)
+ .flatMap((expression) => expandThroughNamedHelper(expression, state))
+ .find(isAllocatingExpression)
+ return nestedFresh
+ ? {
+ node: nestedFresh,
+ message:
+ 'useShallow compares only one level deep, so this nested fresh reference changes on every store write and defeats the memo; project the primitives the component actually renders.'
+ }
+ : null
+ })
}
function bindContext(createVisitors) {
@@ -239,6 +374,7 @@ export default {
rules: {
'require-selector': { create: bindContext(requireSelectorRule) },
'no-identity-selector': { create: bindContext(noIdentitySelectorRule) },
- 'no-fresh-selector-result': { create: bindContext(noFreshSelectorResultRule) }
+ 'no-fresh-selector-result': { create: bindContext(noFreshSelectorResultRule) },
+ 'no-nested-fresh-under-shallow': { create: bindContext(noNestedFreshUnderShallowRule) }
}
}
diff --git a/config/patches/@vscode__windows-process-tree@0.8.0.patch b/config/patches/@vscode__windows-process-tree@0.8.0.patch
index fe5e4be44b18..7c930a5fca56 100644
--- a/config/patches/@vscode__windows-process-tree@0.8.0.patch
+++ b/config/patches/@vscode__windows-process-tree@0.8.0.patch
@@ -1,5 +1,5 @@
diff --git a/binding.gyp b/binding.gyp
-index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..33774e7ae296f0de39dd94156673c9e773638bf4 100644
+index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..0bb2af7923b6e6f1f0da40cae8067304cd1fea14 100644
--- a/binding.gyp
+++ b/binding.gyp
@@ -3,7 +3,6 @@
@@ -10,7 +10,8 @@ index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..33774e7ae296f0de39dd94156673c9e7
],
"conditions": [
['OS=="win"', {
-@@ -15,12 +14,11 @@
+@@ -14,13 +13,12 @@
+ "src/process_worker.cc",
"src/process_commandline.cc"
],
- "include_dirs": [],
@@ -26,314 +27,207 @@ index 855bd4b86f0a3c18c7594212c0e42b6e35bc4001..33774e7ae296f0de39dd94156673c9e7
"AdditionalOptions": [
"/guard:cf",
"/sdl",
+diff --git a/lib/index.js b/lib/index.js
+index 9747a7402600cd252859144d32580ed45c8c93f7..001e81fa8bc89091971d06aaf9d051ba20906615 100644
+--- a/lib/index.js
++++ b/lib/index.js
+@@ -7,11 +7,13 @@ Object.defineProperty(exports, "__esModule", { value: true });
+ exports.getAllProcesses = exports.getProcessTree = exports.getProcessCpuUsage = exports.getProcessList = exports.filterProcessList = exports.buildProcessTree = exports.ProcessDataFlag = void 0;
+ const util_1 = require("util");
+ const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
++exports.supportedProcessDataFlags = native === undefined ? undefined : native.supportedProcessDataFlags;
+ var ProcessDataFlag;
+ (function (ProcessDataFlag) {
+ ProcessDataFlag[ProcessDataFlag["None"] = 0] = "None";
+ ProcessDataFlag[ProcessDataFlag["Memory"] = 1] = "Memory";
+ ProcessDataFlag[ProcessDataFlag["CommandLine"] = 2] = "CommandLine";
++ ProcessDataFlag[ProcessDataFlag["CreationTime"] = 4] = "CreationTime";
+ })(ProcessDataFlag = exports.ProcessDataFlag || (exports.ProcessDataFlag = {}));
+ // requestInProgress is used for any function that uses CreateToolhelp32Snapshot, as multiple calls
+ // to this cannot be done at the same time.
+@@ -66,11 +68,12 @@ function buildProcessTree(rootPid, processList, maxDepth = MAX_FILTER_DEPTH) {
+ // • the properties are inlined/splatted
+ // • the 'ppid' field is omitted
+ // • the depth of the tree is limited by `maxDepth`
+- const buildNode = ({ info: { pid, name, memory, commandLine }, children }, depth) => ({
++ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }, depth) => ({
+ pid,
+ name,
+ memory,
+ commandLine,
++ creationTimeMs,
+ children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
+ });
+ return buildNode(root, maxDepth);
+diff --git a/lib/index.ts b/lib/index.ts
+index f9aa005d9ced9e42885b8a976de5eb5bd61899ee..1b509af0b9065918bcb5cb75f2d7f23821d4a56a 100644
+--- a/lib/index.ts
++++ b/lib/index.ts
+@@ -6,12 +6,15 @@
+ import { promisify } from 'util';
+
+ const native = process.platform === 'win32' ? require('../build/Release/windows_process_tree.node') : undefined;
++/** The flag bits this compiled addon reports; undefined off win32. */
++export const supportedProcessDataFlags: number | undefined = native?.supportedProcessDataFlags;
+ import { IProcessInfo, IProcessTreeNode, IProcessCpuInfo } from '@vscode/windows-process-tree';
+
+ export enum ProcessDataFlag {
+ None = 0,
+ Memory = 1,
+- CommandLine = 2
++ CommandLine = 2,
++ CreationTime = 4
+ }
+
+ type RequestCallback = (processList: IProcessInfo[]) => void;
+@@ -81,11 +84,12 @@ export function buildProcessTree(rootPid: number, processList: Iterable ({
++ const buildNode = ({ info: { pid, name, memory, commandLine, creationTimeMs }, children }: IProcessInfoNode, depth: number): IProcessTreeNode => ({
+ pid,
+ name,
+ memory,
+ commandLine,
++ creationTimeMs,
+ children: depth > 0 ? children.map(c => buildNode(c, depth - 1)) : [],
+ });
+
+diff --git a/src/addon.cc b/src/addon.cc
+index 9214aff281251e797a70ecb9f6e0b52932a0503f..722edd42ddb4740296bfc47582a181bd6d00c464 100644
+--- a/src/addon.cc
++++ b/src/addon.cc
+@@ -53,6 +53,10 @@ void GetProcessCpuUsage(const Napi::CallbackInfo& args) {
+ Napi::Object Init(Napi::Env env, Napi::Object exports) {
+ exports.Set("getProcessList", Napi::Function::New(env, GetProcessList));
+ exports.Set("getProcessCpuUsage", Napi::Function::New(env, GetProcessCpuUsage));
++ // Lets a caller prove THIS BINARY understands CREATIONTIME. The JS enum is
++ // patched source and says nothing about what the .node was compiled from.
++ exports.Set("supportedProcessDataFlags",
++ Napi::Number::New(env, MEMORY | COMMANDLINE | CREATIONTIME));
+ return exports;
+ }
+
diff --git a/src/process.cc b/src/process.cc
-index 3eea92077c4d1d433119361d5c432881859131e9..738775f6fcdfb676054386fe34c0380327ed1863 100644
+index 3eea92077c4d1d433119361d5c432881859131e9..22a47421da919c76e2194280974d39c2287b098d 100644
--- a/src/process.cc
+++ b/src/process.cc
-@@ -1,108 +1,112 @@
--/*---------------------------------------------------------------------------------------------
-- * Copyright (c) Microsoft Corporation. All rights reserved.
-- * Licensed under the MIT License. See License.txt in the project root for license information.
-- *--------------------------------------------------------------------------------------------*/
--
--#include "process.h"
--#include "process_commandline.h"
--
--#include
--#include
--#include
--
--uint32_t GetRawProcessList(std::vector& process_info,
-- DWORD process_data_flags) {
-- // Fetch the PID and PPIDs
-- PROCESSENTRY32 process_entry = { 0 };
-- DWORD parent_pid = 0;
-- uint32_t process_count = 0;
-- HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
-- process_entry.dwSize = sizeof(PROCESSENTRY32);
-- if (Process32First(snapshot_handle, &process_entry)) {
-- do {
-- if (process_entry.th32ProcessID != 0) {
+@@ -21,7 +21,8 @@ uint32_t GetRawProcessList(std::vector& process_info,
+ if (Process32First(snapshot_handle, &process_entry)) {
+ do {
+ if (process_entry.th32ProcessID != 0) {
- ProcessInfo pinfo;
-- pinfo.pid = process_entry.th32ProcessID;
-- pinfo.ppid = process_entry.th32ParentProcessID;
--
-- if (MEMORY & process_data_flags) {
-- GetProcessMemoryUsage(pinfo);
-- }
--
-- if (COMMANDLINE & process_data_flags) {
-- GetProcessCommandLine(pinfo);
-- }
--
-- strcpy(pinfo.name, process_entry.szExeFile);
-- process_info.push_back(std::move(pinfo));
-- process_count++;
-- }
-- } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry));
-- }
--
-- CloseHandle(snapshot_handle);
-- return process_count;
--}
--
--void GetProcessMemoryUsage(ProcessInfo& process_info) {
-- DWORD pid = process_info.pid;
-- HANDLE hProcess;
-- PROCESS_MEMORY_COUNTERS pmc;
--
-- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
--
-- if (hProcess == NULL) {
-- return;
-- }
--
-- if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
-- process_info.memory = (DWORD)pmc.WorkingSetSize;
-- }
--
-- CloseHandle(hProcess);
--}
--
--// Per documentation, it is not recommended to add or subtract values from the FILETIME
--// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows.
--// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead.
--// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
--ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) {
-- ULARGE_INTEGER kt, ut;
-- kt.LowPart = (*kernelTime).dwLowDateTime;
-- kt.HighPart = (*kernelTime).dwHighDateTime;
--
-- ut.LowPart = (*userTime).dwLowDateTime;
-- ut.HighPart = (*userTime).dwHighDateTime;
--
-- return kt.QuadPart + ut.QuadPart;
--}
--
--void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
-- DWORD pid = cpu_info.pid;
-- HANDLE hProcess;
--
-- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
--
-- if (hProcess == NULL) {
-- return;
-- }
--
-- FILETIME creationTime, exitTime, kernelTime, userTime;
-- FILETIME sysIdleTime, sysKernelTime, sysUserTime;
-- if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)
-- && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) {
-- if (first_pass) {
-- cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime);
-- cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime);
-- } else {
-- ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime);
-- ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime);
--
-- cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime);
-- }
-- } else {
-- cpu_info.cpu = std::numeric_limits::quiet_NaN();
-- }
--
-- CloseHandle(hProcess);
-+/*---------------------------------------------------------------------------------------------
-+ * Copyright (c) Microsoft Corporation. All rights reserved.
-+ * Licensed under the MIT License. See License.txt in the project root for license information.
-+ *--------------------------------------------------------------------------------------------*/
-+
-+#include "process.h"
-+#include "process_commandline.h"
-+
-+#include
-+#include
-+#include
-+
-+uint32_t GetRawProcessList(std::vector& process_info,
-+ DWORD process_data_flags) {
-+ // Fetch the PID and PPIDs
-+ PROCESSENTRY32 process_entry = { 0 };
-+ DWORD parent_pid = 0;
-+ uint32_t process_count = 0;
-+ HANDLE snapshot_handle = CreateToolhelp32Snapshot(TH32CS_SNAPPROCESS, 0);
-+ process_entry.dwSize = sizeof(PROCESSENTRY32);
-+ if (Process32First(snapshot_handle, &process_entry)) {
-+ do {
-+ if (process_entry.th32ProcessID != 0) {
+ // Value-initialize: `memory` is otherwise stack garbage when the flag is unset.
+ ProcessInfo pinfo{};
-+ pinfo.pid = process_entry.th32ProcessID;
-+ pinfo.ppid = process_entry.th32ParentProcessID;
-+
-+ if (MEMORY & process_data_flags) {
-+ GetProcessMemoryUsage(pinfo);
-+ }
-+
-+ if (COMMANDLINE & process_data_flags) {
-+ GetProcessCommandLine(pinfo);
+ pinfo.pid = process_entry.th32ProcessID;
+ pinfo.ppid = process_entry.th32ParentProcessID;
+
+@@ -33,23 +34,51 @@ uint32_t GetRawProcessList(std::vector& process_info,
+ GetProcessCommandLine(pinfo);
+ }
+
++ if (CREATIONTIME & process_data_flags) {
++ GetProcessCreationTime(pinfo);
+ }
+
-+ strcpy(pinfo.name, process_entry.szExeFile);
-+ process_info.push_back(std::move(pinfo));
-+ process_count++;
-+ }
+ strcpy(pinfo.name, process_entry.szExeFile);
+ process_info.push_back(std::move(pinfo));
+ process_count++;
+ }
+- } while (process_count < 1024 && Process32Next(snapshot_handle, &process_entry));
+ } while (Process32Next(snapshot_handle, &process_entry));
-+ }
-+
-+ CloseHandle(snapshot_handle);
-+ return process_count;
-+}
-+
-+void GetProcessMemoryUsage(ProcessInfo& process_info) {
-+ DWORD pid = process_info.pid;
-+ HANDLE hProcess;
-+ PROCESS_MEMORY_COUNTERS pmc;
-+
-+ // PROCESS_VM_READ is never used here -- GetProcessMemoryInfo reads counters the
-+ // kernel keeps, not the address space -- and acquiring it is what EDR scores.
-+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
-+
+ }
+
+ CloseHandle(snapshot_handle);
+ return process_count;
+ }
+
++void GetProcessCreationTime(ProcessInfo& process_info) {
++ HANDLE hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, process_info.pid);
+ if (hProcess == NULL) {
+ return;
+ }
+
-+ if (GetProcessMemoryInfo(hProcess, &pmc, sizeof(pmc))) {
-+ process_info.memory = (DWORD)pmc.WorkingSetSize;
++ FILETIME creationTime, exitTime, kernelTime, userTime;
++ if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)) {
++ ULARGE_INTEGER timestamp;
++ timestamp.LowPart = creationTime.dwLowDateTime;
++ timestamp.HighPart = creationTime.dwHighDateTime;
++ constexpr ULONGLONG WINDOWS_EPOCH_OFFSET_100NS = 116444736000000000ULL;
++ constexpr ULONGLONG HUNDRED_NS_PER_MILLISECOND = 10000ULL;
++ if (timestamp.QuadPart >= WINDOWS_EPOCH_OFFSET_100NS) {
++ process_info.creationTimeMs =
++ (timestamp.QuadPart - WINDOWS_EPOCH_OFFSET_100NS) / HUNDRED_NS_PER_MILLISECOND;
++ }
+ }
+
+ CloseHandle(hProcess);
+}
+
-+// Per documentation, it is not recommended to add or subtract values from the FILETIME
-+// structure, or to cast it to ULARGE_INTEGER as this can cause alignment faults on 64-bit Windows.
-+// Copy the high and low part to a ULARGE_INTEGER and peform arithmetic on that instead.
-+// See https://msdn.microsoft.com/en-us/library/windows/desktop/ms724284(v=vs.85).aspx
-+ULONGLONG GetTotalTime(const FILETIME* kernelTime, const FILETIME* userTime) {
-+ ULARGE_INTEGER kt, ut;
-+ kt.LowPart = (*kernelTime).dwLowDateTime;
-+ kt.HighPart = (*kernelTime).dwHighDateTime;
-+
-+ ut.LowPart = (*userTime).dwLowDateTime;
-+ ut.HighPart = (*userTime).dwHighDateTime;
-+
-+ return kt.QuadPart + ut.QuadPart;
-+}
-+
-+void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
-+ DWORD pid = cpu_info.pid;
-+ HANDLE hProcess;
-+
+ void GetProcessMemoryUsage(ProcessInfo& process_info) {
+ DWORD pid = process_info.pid;
+ HANDLE hProcess;
+ PROCESS_MEMORY_COUNTERS pmc;
+
+- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
++ // PROCESS_VM_READ is never used here -- GetProcessMemoryInfo reads counters the
++ // kernel keeps, not the address space -- and acquiring it is what EDR scores.
++ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
+
+ if (hProcess == NULL) {
+ return;
+@@ -81,7 +110,8 @@ void GetCpuUsage(Cpu& cpu_info, bool first_pass) {
+ DWORD pid = cpu_info.pid;
+ HANDLE hProcess;
+
+- hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, false, pid);
+ // GetProcessTimes needs no more than PROCESS_QUERY_LIMITED_INFORMATION.
+ hProcess = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, false, pid);
-+
-+ if (hProcess == NULL) {
-+ return;
-+ }
-+
-+ FILETIME creationTime, exitTime, kernelTime, userTime;
-+ FILETIME sysIdleTime, sysKernelTime, sysUserTime;
-+ if (GetProcessTimes(hProcess, &creationTime, &exitTime, &kernelTime, &userTime)
-+ && GetSystemTimes(&sysIdleTime, &sysKernelTime, &sysUserTime)) {
-+ if (first_pass) {
-+ cpu_info.initialProcRunTime = GetTotalTime(&kernelTime, &userTime);
-+ cpu_info.initialSystemTime = GetTotalTime(&sysKernelTime, &sysUserTime);
-+ } else {
-+ ULONGLONG endProcTime = GetTotalTime(&kernelTime, &userTime);
-+ ULONGLONG endSysTime = GetTotalTime(&sysKernelTime, &sysUserTime);
-+
-+ cpu_info.cpu = 100.0 * (endProcTime - cpu_info.initialProcRunTime) / (endSysTime - cpu_info.initialSystemTime);
-+ }
-+ } else {
-+ cpu_info.cpu = std::numeric_limits::quiet_NaN();
-+ }
-+
-+ CloseHandle(hProcess);
- }
-\ No newline at end of file
+
+ if (hProcess == NULL) {
+ return;
+diff --git a/src/process.h b/src/process.h
+index 82f8e4bcfa742551e5d874a7632736a7611d7aa7..78d1d2c3b2360ed06fd624b4cb2f5042510f7a77 100644
+--- a/src/process.h
++++ b/src/process.h
+@@ -22,18 +22,22 @@ struct ProcessInfo {
+ DWORD ppid;
+ DWORD memory; // Reported in bytes
+ std::string commandLine;
++ ULONGLONG creationTimeMs;
+ };
+
+ enum ProcessDataFlags {
+ NONE = 0,
+ MEMORY = 1,
+- COMMANDLINE = 2
++ COMMANDLINE = 2,
++ CREATIONTIME = 4
+ };
+
+ uint32_t GetRawProcessList(std::vector& process_info, DWORD flags);
+
+ void GetProcessMemoryUsage(ProcessInfo& process_info);
+
++void GetProcessCreationTime(ProcessInfo& process_info);
++
+ void GetCpuUsage(Cpu& cpu_info, bool first_run);
+
+ #endif // SRC_PROCESS_H_
diff --git a/src/process_commandline.cc b/src/process_commandline.cc
index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3210c3cfd 100644
--- a/src/process_commandline.cc
+++ b/src/process_commandline.cc
-@@ -1,67 +1,125 @@
--/*---------------------------------------------------------------------------------------------
-- * Copyright (c) Microsoft Corporation. All rights reserved.
-- * Licensed under the MIT License. See License.txt in the project root for license information.
-- *--------------------------------------------------------------------------------------------*/
--
--#include "process.h"
--#include "process_commandline.h"
--#include
--#include
+@@ -7,61 +7,119 @@
+ #include "process_commandline.h"
+ #include
+ #include
-#include
--
++#include
+
-bool GetProcessCommandLine(ProcessInfo& process_info) {
- HINSTANCE ntdll = GetModuleHandleW(L"ntdll.dll");
-- if (!ntdll) {
-- return false;
-- }
--
-- decltype(NtQueryInformationProcess)* nt_query_information_process =
-- reinterpret_cast(
-- GetProcAddress(ntdll, "NtQueryInformationProcess"));
--
-- if (!nt_query_information_process) {
-- return false;
-- }
--
-- PROCESS_BASIC_INFORMATION pbi{};
-- PEB peb = {NULL};
-- RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL};
--
-- // Get process handle
-- DWORD pid = process_info.pid;
-- HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
-- if (hProcess == INVALID_HANDLE_VALUE) {
-- return false;
-- }
--
-- // Get Process Environment Block (PEB)
-- NTSTATUS status = nt_query_information_process(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr);
-- if (NT_SUCCESS(status) && pbi.PebBaseAddress) {
-- // Read PEB
-- if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) {
-- // Read the processs parameters
-- if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), nullptr)) {
-- if (process_parameters.CommandLine.Length > 0) {
-- std::wstring buffer;
-- buffer.resize(process_parameters.CommandLine.Length / sizeof(wchar_t));
-- if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, &buffer[0], process_parameters.CommandLine.Length, nullptr)) {
-- int wide_length = static_cast(buffer.length());
-- int charcount = WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
-- NULL, 0, NULL, NULL);
-- if (charcount) {
-- process_info.commandLine.resize(static_cast(charcount));
-- WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
-- &process_info.commandLine[0], charcount,
-- NULL, NULL);
-- }
-- CloseHandle(hProcess);
-- return true;
-- }
-- }
-- }
-- }
-- }
--
-- CloseHandle(hProcess);
-- return false;
--}
-+/*---------------------------------------------------------------------------------------------
-+ * Copyright (c) Microsoft Corporation. All rights reserved.
-+ * Licensed under the MIT License. See License.txt in the project root for license information.
-+ *--------------------------------------------------------------------------------------------*/
-+
-+#include "process.h"
-+#include "process_commandline.h"
-+#include
-+#include
-+#include
-+
+namespace {
+
+// Windows 8.1 and later hand back a process's command line as a UNICODE_STRING
@@ -366,7 +260,7 @@ index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3
+// ntdll ships no import library for this entry point; it has to be resolved.
+NtQueryInformationProcessFn ResolveNtQueryInformationProcess() {
+ HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
-+ if (!ntdll) {
+ if (!ntdll) {
+ return nullptr;
+ }
+ return reinterpret_cast(
@@ -385,8 +279,8 @@ index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3
+ int length = static_cast(wide_length);
+ int charcount = WideCharToMultiByte(CP_UTF8, 0, data, length, NULL, 0, NULL, NULL);
+ if (!charcount) {
-+ return false;
-+ }
+ return false;
+ }
+ process_info.commandLine.resize(static_cast(charcount));
+ WideCharToMultiByte(CP_UTF8, 0, data, length, &process_info.commandLine[0], charcount, NULL,
+ NULL);
@@ -394,18 +288,25 @@ index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3
+}
+
+} // namespace
-+
+
+- decltype(NtQueryInformationProcess)* nt_query_information_process =
+- reinterpret_cast(
+- GetProcAddress(ntdll, "NtQueryInformationProcess"));
+bool GetProcessCommandLine(ProcessInfo& process_info) {
+ NtQueryInformationProcessFn query = NtQueryInformationProcessEntry();
+ if (!query) {
+ return false;
+ }
-+
+
+- if (!nt_query_information_process) {
+ HANDLE process = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION, FALSE, process_info.pid);
+ if (process == NULL) {
-+ return false;
-+ }
-+
+ return false;
+ }
+
+- PROCESS_BASIC_INFORMATION pbi{};
+- PEB peb = {NULL};
+- RTL_USER_PROCESS_PARAMETERS process_parameters = {NULL};
+ ULONG size = 0;
+ NTSTATUS status = query(process, kProcessCommandLineInformation, nullptr, 0, &size);
+ if (NT_SUCCESS(status)) {
@@ -421,14 +322,44 @@ index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3
+ CloseHandle(process);
+ return false;
+ }
-+
+
+- // Get process handle
+- DWORD pid = process_info.pid;
+- HANDLE hProcess = OpenProcess(PROCESS_QUERY_INFORMATION | PROCESS_VM_READ, FALSE, pid);
+- if (hProcess == INVALID_HANDLE_VALUE) {
+ std::vector buffer(size);
+ status = query(process, kProcessCommandLineInformation, &buffer[0], size, &size);
+ CloseHandle(process);
+ if (!NT_SUCCESS(status)) {
-+ return false;
-+ }
-+
+ return false;
+ }
+
+- // Get Process Environment Block (PEB)
+- NTSTATUS status = nt_query_information_process(hProcess, ProcessBasicInformation, &pbi, sizeof(pbi), nullptr);
+- if (NT_SUCCESS(status) && pbi.PebBaseAddress) {
+- // Read PEB
+- if (ReadProcessMemory(hProcess, pbi.PebBaseAddress, &peb, sizeof(peb), nullptr)) {
+- // Read the processs parameters
+- if (ReadProcessMemory(hProcess, peb.ProcessParameters, &process_parameters, sizeof(RTL_USER_PROCESS_PARAMETERS), nullptr)) {
+- if (process_parameters.CommandLine.Length > 0) {
+- std::wstring buffer;
+- buffer.resize(process_parameters.CommandLine.Length / sizeof(wchar_t));
+- if (ReadProcessMemory(hProcess, process_parameters.CommandLine.Buffer, &buffer[0], process_parameters.CommandLine.Length, nullptr)) {
+- int wide_length = static_cast(buffer.length());
+- int charcount = WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
+- NULL, 0, NULL, NULL);
+- if (charcount) {
+- process_info.commandLine.resize(static_cast(charcount));
+- WideCharToMultiByte(CP_UTF8, 0, buffer.data(), wide_length,
+- &process_info.commandLine[0], charcount,
+- NULL, NULL);
+- }
+- CloseHandle(hProcess);
+- return true;
+- }
+- }
+- }
+- }
+ // Header and characters arrive in one allocation, but treat the header as
+ // untrusted: a hooked ntdll is the case this reader is written for, and an
+ // unchecked Buffer/Length here would be an over-read encoded straight into JS.
@@ -440,11 +371,70 @@ index ea822b120e8038a4803e34647042f08f4aaf5ca1..25907c0bf542bed6c72b1b462b19bcf3
+ if (chars == nullptr || chars < begin + sizeof(UNICODE_STRING) || chars > end ||
+ command_line->Length > static_cast(end - chars)) {
+ return false;
-+ }
-+
+ }
+
+- CloseHandle(hProcess);
+- return false;
+ // True only when a command line was actually stored, so "empty" and "not
+ // recovered" stay the same answer they were before this reader replaced the
+ // PEB read. `src/process.cc` discards the result either way.
+ return StoreCommandLineUtf8(process_info, command_line->Buffer,
+ command_line->Length / sizeof(wchar_t));
-+}
+ }
+diff --git a/src/process_worker.cc b/src/process_worker.cc
+index c9e3457a759c1acaa2644231a4917d45aed951f8..3f26a354477f062b34bd31fbd17be529e6a2fd7a 100644
+--- a/src/process_worker.cc
++++ b/src/process_worker.cc
+@@ -43,6 +43,11 @@ void GetProcessesWorker::OnOK() {
+ Napi::String::New(env, pinfo.commandLine));
+ }
+
++ if ((CREATIONTIME & process_data_flags_) && pinfo.creationTimeMs != 0) {
++ object.Set("creationTimeMs",
++ Napi::Number::New(env, static_cast(pinfo.creationTimeMs)));
++ }
++
+ result.Set(i, object);
+ }
+
+diff --git a/typings/windows-process-tree.d.ts b/typings/windows-process-tree.d.ts
+index 08bdac2fdc5ead6f0fcfb5ee5a021e2298c7d523..458981566fc45c0084badff566b1e3791ec1b629 100644
+--- a/typings/windows-process-tree.d.ts
++++ b/typings/windows-process-tree.d.ts
+@@ -7,9 +7,17 @@ declare module '@vscode/windows-process-tree' {
+ export enum ProcessDataFlag {
+ None = 0,
+ Memory = 1,
+- CommandLine = 2
++ CommandLine = 2,
++ CreationTime = 4
+ }
+
++ /**
++ * The flag bits the compiled addon actually understands, or undefined off
++ * win32. `ProcessDataFlag` above is source; this is what the binary reports,
++ * so it is the only way to tell a patched build from a stale prebuilt.
++ */
++ export const supportedProcessDataFlags: number | undefined;
++
+ export interface IProcessInfo {
+ pid: number;
+ ppid: number;
+@@ -24,6 +32,9 @@ declare module '@vscode/windows-process-tree' {
+ * The string returned is at most 512 chars, strings exceeding this length are truncated.
+ */
+ commandLine?: string;
++
++ /** Process creation time in Unix milliseconds. */
++ creationTimeMs?: number;
+ }
+
+ export interface IProcessCpuInfo extends IProcessInfo {
+@@ -35,6 +46,7 @@ declare module '@vscode/windows-process-tree' {
+ name: string;
+ memory?: number;
+ commandLine?: string;
++ creationTimeMs?: number;
+ children: IProcessTreeNode[];
+ }
+
diff --git a/config/patches/@xterm__xterm@6.1.0-beta.303.patch b/config/patches/@xterm__xterm@6.1.0-beta.303.patch
index b3e59cc89edb..3dad76bb2c75 100644
--- a/config/patches/@xterm__xterm@6.1.0-beta.303.patch
+++ b/config/patches/@xterm__xterm@6.1.0-beta.303.patch
@@ -1,23 +1,23 @@
diff --git a/lib/xterm.js b/lib/xterm.js
-index 5de883f434d2e60af7181461b1e36cd5e75bdfc0..7305fe19149f5d7c97e514252593d4dcb238bc77 100644
+index 5de883f434d2e60af7181461b1e36cd5e75bdfc0..9a5b36798f09367bbba093e53a5bf0cf58e35b47 100644
--- a/lib/xterm.js
+++ b/lib/xterm.js
@@ -1,2 +1,2 @@
-!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(globalThis,()=>(()=>{"use strict";var e={2840(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new c.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar("\n"))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register((0,f.addDisposableListener)(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register((0,d.toDisposable)(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent=l.tooMuchOutput.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.lines.get(i.ydisp+r),t=[],o=e?.translateToString(!0,void 0,void 0,t)||"",n=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(0===o.length?(a.textContent=" ",this._rowColumns.set(a,[0,1])):(a.textContent=o,this._rowColumns.set(a,t)),a.setAttribute("aria-posinset",n),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent===l.tooMuchOutput.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,o;if(0===t?(r=i,o=this._rowElements.pop(),this._rowContainer.removeChild(o)):(r=this._rowElements.shift(),o=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),o.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(0===this._rowElements.length)return;const e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;const r=({node:e,offset:t})=>{const i=e instanceof Text?e.parentNode:e;let s=parseInt(i?.getAttribute("aria-posinset"),10)-1;if(isNaN(s))return console.warn("row is invalid. Race condition?"),null;const r=this._rowColumns.get(i);if(!r)return console.warn("columns is null. Race condition?"),null;let o=t=this._terminal.cols&&(++s,o=0),{row:s,column:o}},o=r(t),n=r(i);if(o&&n){if(o.row>n.row||o.row===n.row&&o.column>=n.column)throw new Error("invalid range");this._terminal.select(o.column,o.row,(n.row-o.row)*this._terminal.cols-o.column+n.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;ethis._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(I.EventUtils.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(I.EventUtils.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(I.EventUtils.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(I.EventUtils.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register((0,N.toDisposable)(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i;switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=E.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`]${i};${(0,M.toRgbString)(s)}\\`);break;case 1:if("ansi"===e)this._themeService.modifyColors(e=>e.ansi[t.index]=E.channels.toColor(...t.color));else{const i=e;this._themeService.modifyColors(e=>e[i]=E.channels.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index)}}}_reportColorScheme(){if(!this._themeService)return;const e=E.rgb.relativeLuminance(this._themeService.colors.background.rgba>>8)>8)?1:2;this.coreService.triggerDataEvent(`[?997;${e}n`)}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(T.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent("[I"),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent("[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),o=this._renderService.dimensions.css.cell.width*r,n=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=n+"px",this.textarea.style.width=o+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register((0,H.addDisposableListener)(this.element,"copy",e=>{this.hasSelection()&&(0,a.copyHandler)(e,this._selectionService)}));const e=e=>(0,a.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this._register((0,H.addDisposableListener)(this.textarea,"paste",e)),this._register((0,H.addDisposableListener)(this.element,"paste",e)),x.isFirefox?this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>{2===e.button&&(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register((0,H.addDisposableListener)(this.element,"contextmenu",e=>{(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),x.isLinux&&this._register((0,H.addDisposableListener)(this.element,"auxclick",e=>{1===e.button&&(0,a.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register((0,H.addDisposableListener)(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register((0,H.addDisposableListener)(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register((0,H.addDisposableListener)(this.textarea,"compositionend",()=>this._compositionHelper.compositionend())),this._register((0,H.addDisposableListener)(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",e=>this.element.classList.toggle("allow-transparency",e))),e.appendChild(this.element);const t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register((0,H.addDisposableListener)(this.screenElement,"mousemove",e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);const i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",h.promptLabel.get()),x.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocomplete","off"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(g.CoreBrowserService,this.textarea,e.ownerDocument.defaultView??window,this._document??("undefined"!=typeof window?window.document:null))),this._instantiationService.setService(C.ICoreBrowserService,this._coreBrowserService),this._register((0,H.addDisposableListener)(this.textarea,"focus",e=>this._handleTextAreaFocus(e))),this._register((0,H.addDisposableListener)(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(p.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(C.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(k.ThemeService),this._instantiationService.setService(C.IThemeService,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(v.CharacterJoinerService),this._instantiationService.setService(C.ICharacterJoinerService,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(w.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(C.IRenderService,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this._register(this._renderService.onDimensionsChange(e=>this._onDimensionsChange.fire({css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(u.CompositionHelper,this.textarea,this._compositionView),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(S.MouseCoordsService),this._instantiationService.setService(C.IMouseCoordsService,this._mouseCoordsService);const s=this._linkifier.value=this._register(this._instantiationService.createInstance(O.Linkifier,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(e){this._logService.error("onWillOpen handler threw an exception",e)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(c.Viewport,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(y.SelectionService,this.element,this.screenElement,s)),this._instantiationService.setService(C.ISelectionService,this._selectionService),this._mouseService=this._instantiationService.createInstance(b.MouseService),this._instantiationService.setService(C.IMouseService,this._mouseService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(I.EventUtils.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(d.BufferDecorationRenderer,this.screenElement)),this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>this._selectionService.handleMouseDown(e))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(T.AccessibilityManager,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",e=>this._handleScreenReaderModeOptionChange(e)));const r=this.options.scrollbar?.showScrollbar??!0,o=this.options.scrollbar?.width;r&&o&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",e=>{const t=(e?.showScrollbar??!0)&&!!e?.width;!this._overviewRulerRenderer&&t&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:e=>this._viewport?.handleTouchScroll(e)},e=>this._register(e),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(f.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}paste(e){(0,a.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),e.preventDefault(),e.stopPropagation(),!1}if(1===i.type&&this.selectAll(),this._isThirdLevelShift(this.browser,e))return!0;if(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)return!0;if(!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;""!==i.key&&"\r"!==i.key||(this.textarea.value="");const s=this._keyboardService.useWin32InputMode&&W(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return;W(e)||this.focus();const t=this._keyboardService.evaluateKeyUp(e);if(t?.key){const i=this._keyboardService.useWin32InputMode&&W(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;efunction(e){const t=l(e);for(t.animFrameRequested=!1,t.current=t.next,t.next=[],t.inAnimationFrameRunner=!0;t.current.length>0;)t.current.sort(a.sort),t.current.shift().execute();t.inAnimationFrameRunner=!1}(e))),r};const s=i(3132);function r(e){const t=e;if(t?.ownerDocument?.defaultView)return t.ownerDocument.defaultView;const i=e;return i?.view?i.view:window}class o{constructor(e,t,i,s){this._node=e,this._type=t,this._handler=i,this._options=s,e.addEventListener(t,i,s)}dispose(){this._node&&this._handler&&(this._node.removeEventListener(this._type,this._handler,this._options),this._node=null,this._handler=null)}}function n(e,t,i,s){return new o(e,t,i,s)}t.eventType={CLICK:"click",MOUSE_DOWN:"mousedown",MOUSE_OVER:"mouseover",MOUSE_LEAVE:"mouseleave",KEY_DOWN:"keydown",KEY_UP:"keyup",INPUT:"input",BLUR:"blur",FOCUS:"focus",CHANGE:"change",POINTER_DOWN:"pointerdown",POINTER_MOVE:"pointermove",POINTER_UP:"pointerup",MOUSE_WHEEL:"wheel",WHEEL:"wheel"};class a{constructor(e,t){this._runner=e,this.priority=t,this._canceled=!1}dispose(){this._canceled=!0}execute(){if(!this._canceled)try{this._runner()}catch(e){console.error(e)}}static sort(e,t){return t.priority-e.priority}}const h=new Map;function l(e){let t=h.get(e);return t||(t={next:[],current:[],animFrameRequested:!1,inAnimationFrameRunner:!1},h.set(e,t)),t}class c extends s.IntervalTimer{constructor(e){super(),this._defaultTarget=e?r(e):void 0}cancelAndSet(e,t,i){super.cancelAndSet(e,t,i??this._defaultTarget??window)}}t.WindowIntervalTimer=c},8906(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Linkifier=void 0;const o=i(4812),n=i(6501),a=i(7098),h=i(8636),l=i(4159);let c=class extends o.Disposable{get currentLink(){return this._currentLink}constructor(e,t,i,s,r){super(),this._element=e,this._mouseCoordsService=t,this._renderService=i,this._bufferService=s,this._linkProviderService=r,this._linkCacheDisposables=[],this._isMouseOut=!0,this._wasResized=!1,this._activeLine=-1,this._onShowLinkUnderline=this._register(new h.Emitter),this.onShowLinkUnderline=this._onShowLinkUnderline.event,this._onHideLinkUnderline=this._register(new h.Emitter),this.onHideLinkUnderline=this._onHideLinkUnderline.event,this._register((0,o.toDisposable)(()=>{(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0,this._lastMouseEvent=void 0,this._activeProviderReplies?.clear()})),this._register(this._bufferService.onResize(()=>{this._clearCurrentLink(),this._wasResized=!0})),this._register((0,l.addDisposableListener)(this._element,"mouseleave",()=>{this._isMouseOut=!0,this._clearCurrentLink()})),this._register((0,l.addDisposableListener)(this._element,"mousemove",this._handleMouseMove.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mousedown",this._handleMouseDown.bind(this))),this._register((0,l.addDisposableListener)(this._element,"mouseup",this._handleMouseUp.bind(this)))}_handleMouseMove(e){this._lastMouseEvent=e;const t=this._positionFromMouseEvent(e,this._element);if(!t)return;this._isMouseOut=!1;const i=e.composedPath();for(let e=0;e{e?.forEach(e=>{e.link.dispose&&e.link.dispose()})}),this._activeProviderReplies=new Map,this._activeLine=e.y);let i=!1;for(const[s,r]of this._linkProviderService.linkProviders.entries())if(t){const t=this._activeProviderReplies?.get(s);t&&(i=this._checkLinkProviderResult(s,e,i))}else r.provideLinks(e.y,t=>{if(this._isMouseOut)return;const r=t?.map(e=>({link:e}));this._activeProviderReplies?.set(s,r),i=this._checkLinkProviderResult(s,e,i),this._activeProviderReplies?.size===this._linkProviderService.linkProviders.length&&this._removeIntersectingLinks(e.y,this._activeProviderReplies)})}_removeIntersectingLinks(e,t){const i=new Set;for(let s=0;se?this._bufferService.cols:s.link.range.end.x;for(let e=o;e<=n;e++){if(i.has(e)){r.splice(t--,1);break}i.add(e)}}}}_checkLinkProviderResult(e,t,i){if(!this._activeProviderReplies)return i;const s=this._activeProviderReplies.get(e);let r=!1;for(let t=0;tthis._linkAtPosition(e.link,t));e&&(i=!0,this._handleNewLink(e))}if(this._activeProviderReplies.size===this._linkProviderService.linkProviders.length&&!i)for(let e=0;ethis._linkAtPosition(e.link,t));if(s){i=!0,this._handleNewLink(s);break}}return i}_handleMouseDown(){this._mouseDownLink=this._currentLink}_handleMouseUp(e){if(!this._currentLink)return;const t=this._positionFromMouseEvent(e,this._element);var i,s;t&&this._mouseDownLink&&(i=this._mouseDownLink.link,s=this._currentLink.link,i.text===s.text&&i.range.start.x===s.range.start.x&&i.range.start.y===s.range.start.y&&i.range.end.x===s.range.end.x&&i.range.end.y===s.range.end.y)&&this._linkAtPosition(this._currentLink.link,t)&&this._currentLink.link.activate(e,this._currentLink.link.text)}_clearCurrentLink(e,t){this._currentLink&&this._lastMouseEvent&&(!e||!t||this._currentLink.link.range.start.y>=e&&this._currentLink.link.range.end.y<=t)&&(this._linkLeave(this._element,this._currentLink.link,this._lastMouseEvent),this._currentLink=void 0,(0,o.dispose)(this._linkCacheDisposables),this._linkCacheDisposables.length=0)}_handleNewLink(e){if(!this._lastMouseEvent)return;const t=this._positionFromMouseEvent(this._lastMouseEvent,this._element);t&&this._linkAtPosition(e.link,t)&&(this._currentLink=e,this._currentLink.state={decorations:{underline:void 0===e.link.decorations||e.link.decorations.underline,pointerCursor:void 0===e.link.decorations||e.link.decorations.pointerCursor},isHovered:!0},this._linkHover(this._element,e.link,this._lastMouseEvent),e.link.decorations={},Object.defineProperties(e.link.decorations,{pointerCursor:{get:()=>this._currentLink?.state?.decorations.pointerCursor,set:e=>{this._currentLink?.state&&this._currentLink.state.decorations.pointerCursor!==e&&(this._currentLink.state.decorations.pointerCursor=e,this._currentLink.state.isHovered&&this._element.classList.toggle("xterm-cursor-pointer",e))}},underline:{get:()=>this._currentLink?.state?.decorations.underline,set:t=>{this._currentLink?.state&&this._currentLink?.state?.decorations.underline!==t&&(this._currentLink.state.decorations.underline=t,this._currentLink.state.isHovered&&this._fireUnderlineEvent(e.link,t))}}}),this._linkCacheDisposables.push(this._renderService.onRenderedViewportChange(e=>{if(!this._currentLink)return;const t=0===e.start?0:e.start+1+this._bufferService.buffer.ydisp,i=this._bufferService.buffer.ydisp+1+e.end;if(this._currentLink.link.range.start.y>=t&&this._currentLink.link.range.end.y<=i&&(this._clearCurrentLink(t,i),this._lastMouseEvent)){const e=this._positionFromMouseEvent(this._lastMouseEvent,this._element);e&&this._askForLink(e,!1)}})))}_linkHover(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!0,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!0),this._currentLink.state.decorations.pointerCursor&&e.classList.add("xterm-cursor-pointer")),t.hover&&t.hover(i,t.text)}_fireUnderlineEvent(e,t){const i=e.range,s=this._bufferService.buffer.ydisp,r=this._createLinkUnderlineEvent(i.start.x-1,i.start.y-s-1,i.end.x,i.end.y-s-1,void 0);(t?this._onShowLinkUnderline:this._onHideLinkUnderline).fire(r)}_linkLeave(e,t,i){this._currentLink?.state&&(this._currentLink.state.isHovered=!1,this._currentLink.state.decorations.underline&&this._fireUnderlineEvent(t,!1),this._currentLink.state.decorations.pointerCursor&&e.classList.remove("xterm-cursor-pointer")),t.leave&&t.leave(i,t.text)}_linkAtPosition(e,t){const i=e.range.start.y*this._bufferService.cols+e.range.start.x,s=e.range.end.y*this._bufferService.cols+e.range.end.x,r=t.y*this._bufferService.cols+t.x;return i<=r&&r<=s}_positionFromMouseEvent(e,t){const i=this._mouseCoordsService.getCoords(e,t,this._bufferService.cols,this._bufferService.rows);if(i)return{x:i[0],y:i[1]+this._bufferService.buffer.ydisp}}_createLinkUnderlineEvent(e,t,i,s,r){return{x1:e,y1:t,x2:i,y2:s,cols:this._bufferService.cols,fg:r}}};t.Linkifier=c,t.Linkifier=c=s([r(1,a.IMouseCoordsService),r(2,a.IRenderService),r(3,n.IBufferService),r(4,a.ILinkProviderService)],c)},7721(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.tooMuchOutput=t.promptLabel=void 0;let i="Terminal input";const s={get:()=>i,set:e=>i=e};t.promptLabel=s;let r="Too much output to announce, navigate to rows manually to read";const o={get:()=>r,set:e=>r=e};t.tooMuchOutput=o},3285(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkProvider=void 0;const o=i(3055),n=i(6501);let a=class{constructor(e,t,i){this._bufferService=e,this._optionsService=t,this._oscLinkService=i,this._workCell=new o.CellData}provideLinks(e,t){const i=this._bufferService.buffer.lines.get(e-1);if(!i)return void t(void 0);const s=[],r=this._optionsService.rawOptions.linkHandler,o=this._workCell,n=i.getTrimmedLength();let a=-1,l=-1,c=!1;for(let t=0;tr?r.activate(e,t,d):h(0,t),hover:(e,t)=>r?.hover?.(e,t,d),leave:(e,t)=>r?.leave?.(e,t,d)})}c=!1,o.hasExtendedAttrs()&&o.extended.urlId?(l=t,a=o.extended.urlId):(l=-1,a=-1)}}t(s)}_getRangeWithLineWrap(e,t,i,s){let r=e,o=t,n=e,a=i;for(;0===o;){const e=this._bufferService.buffer.lines.get(r-1);if(!e?.isWrapped)break;const t=this._bufferService.buffer.lines.get(r-2);if(!t)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,i-1,s))break;let n=i-1;for(;n>0&&this._hasUrlId(t,n-1,s);)n--;r--,o=n}for(;;){const e=this._bufferService.buffer.lines.get(n-1);if(!e)break;if(a!==e.getTrimmedLength())break;const t=this._bufferService.buffer.lines.get(n);if(!t?.isWrapped)break;const i=t.getTrimmedLength();if(0===i||!this._hasUrlId(t,0,s))break;let r=1;for(;rthis._innerRefresh()),this._animationFrame}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._innerRefresh()))}_innerRefresh(){if(this._animationFrame=void 0,void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return void this._runRefreshCallbacks();const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t),this._runRefreshCallbacks()}_runRefreshCallbacks(){for(const e of this._refreshCallbacks)e(0);this._refreshCallbacks=[]}}},4292(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.TimeBasedDebouncer=void 0,t.TimeBasedDebouncer=class{constructor(e,t=1e3){this._renderCallback=e,this._debounceThresholdMS=t,this._lastRefreshMs=0,this._additionalRefreshRequested=!1}dispose(){this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0),this._additionalRefreshRequested=!1}refresh(e,t,i){this._rowCount=i,e=e??0,t=t??this._rowCount-1,this._rowStart=void 0!==this._rowStart?Math.min(this._rowStart,e):e,this._rowEnd=void 0!==this._rowEnd?Math.max(this._rowEnd,t):t;const s=performance.now();if(s-this._lastRefreshMs>=this._debounceThresholdMS)void 0!==this._refreshTimeoutID&&(clearTimeout(this._refreshTimeoutID),this._refreshTimeoutID=void 0,this._additionalRefreshRequested=!1),this._lastRefreshMs=s,this._innerRefresh();else if(!this._additionalRefreshRequested){const e=s-this._lastRefreshMs,t=this._debounceThresholdMS-e;this._additionalRefreshRequested=!0,this._refreshTimeoutID=window.setTimeout(()=>{this._lastRefreshMs=performance.now(),this._innerRefresh(),this._additionalRefreshRequested=!1,this._refreshTimeoutID=void 0},t)}}_innerRefresh(){if(void 0===this._rowStart||void 0===this._rowEnd||void 0===this._rowCount)return;const e=Math.max(this._rowStart,0),t=Math.min(this._rowEnd,this._rowCount-1);this._rowStart=void 0,this._rowEnd=void 0,this._renderCallback(e,t)}}},9302(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_ANSI_COLORS=void 0;const s=i(4103);t.DEFAULT_ANSI_COLORS=Object.freeze((()=>{const e=[s.css.toColor("#2e3436"),s.css.toColor("#cc0000"),s.css.toColor("#4e9a06"),s.css.toColor("#c4a000"),s.css.toColor("#3465a4"),s.css.toColor("#75507b"),s.css.toColor("#06989a"),s.css.toColor("#d3d7cf"),s.css.toColor("#555753"),s.css.toColor("#ef2929"),s.css.toColor("#8ae234"),s.css.toColor("#fce94f"),s.css.toColor("#729fcf"),s.css.toColor("#ad7fa8"),s.css.toColor("#34e2e2"),s.css.toColor("#eeeeec")],t=[0,95,135,175,215,255];for(let i=0;i<216;i++){const r=t[i/36%6|0],o=t[i/6%6|0],n=t[i%6];e.push({css:s.channels.toCss(r,o,n),rgba:s.channels.toRgba(r,o,n)})}for(let t=0;t<24;t++){const i=8+10*t;e.push({css:s.channels.toCss(i,i,i),rgba:s.channels.toRgba(i,i,i)})}return e})())},4017(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.Viewport=void 0;const o=i(7098),n=i(4812),a=i(6501),h=i(4159),l=i(8566),c=i(8636),d=i(7880);let _=class extends n.Disposable{constructor(e,t,i,s,r,o,a,_,u){super(),this._bufferService=i,this._coreService=r,this._optionsService=_,this._renderService=u,this._onRequestScrollLines=this._register(new c.Emitter),this.onRequestScrollLines=this._onRequestScrollLines.event,this._isSyncing=!1,this._isHandlingScroll=!1,this._suppressOnScrollHandler=!1,this._needsSyncOnRender=!1;const f=this._register(new d.Scrollable({forceIntegerValues:!1,smoothScrollDuration:this._optionsService.rawOptions.smoothScrollDuration,scheduleAtNextAnimationFrame:e=>(0,h.scheduleAtNextAnimationFrame)(s.window,e)}));this._register(this._optionsService.onSpecificOptionChange("smoothScrollDuration",()=>{f.setSmoothScrollDuration(this._optionsService.rawOptions.smoothScrollDuration)})),this._scrollableElement=this._register(new l.SmoothScrollableElement(t,{vertical:1,horizontal:2,useShadows:!1,mouseWheelSmoothScroll:!0,verticalHasArrows:this._optionsService.rawOptions.scrollbar?.showArrows??!1,...this._getChangeOptions()},f)),this._register(this._optionsService.onMultipleOptionChange(["scrollSensitivity","fastScrollSensitivity","scrollbar"],()=>this._scrollableElement.updateOptions(this._getChangeOptions()))),this._register(o.onProtocolChange(e=>{this._scrollableElement.updateOptions({handleMouseWheel:!(16&e)})})),this._scrollableElement.setScrollDimensions({height:0,scrollHeight:0}),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{e.style.backgroundColor=a.colors.background.css,this._scrollableElement.getDomNode().style.backgroundColor=a.colors.background.css})),e.appendChild(this._scrollableElement.getDomNode()),this._register((0,n.toDisposable)(()=>this._scrollableElement.getDomNode().remove())),this._styleElement=s.mainDocument.createElement("style"),t.appendChild(this._styleElement),this._register((0,n.toDisposable)(()=>this._styleElement.remove())),this._register(c.EventUtils.runAndSubscribe(a.onChangeColors,()=>{this._styleElement.textContent=[".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider {",` background: ${a.colors.scrollbarSliderBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider:hover {",` background: ${a.colors.scrollbarSliderHoverBackground.css};`,"}",".xterm .xterm-scrollable-element > .xterm-scrollbar > .xterm-slider.xterm-active {",` background: ${a.colors.scrollbarSliderActiveBackground.css};`,"}"].join("\n")})),this._register(this._bufferService.onResize(()=>this.queueSync())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._latestYDisp=void 0,this.queueSync()})),this._register(this._bufferService.onScroll(()=>this._sync())),this._register(this._renderService.onRender(()=>{this._needsSyncOnRender&&(this._needsSyncOnRender=!1,this._sync())})),this._register(this._scrollableElement.onScroll(e=>this._handleScroll(e)))}scrollLines(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({reuseAnimation:!0,scrollTop:t.scrollTop+e*this._renderService.dimensions.css.cell.height})}scrollToLine(e,t){t&&(this._latestYDisp=e),this._scrollableElement.setScrollPosition({reuseAnimation:!t,scrollTop:e*this._renderService.dimensions.css.cell.height})}_getChangeOptions(){const e=this._optionsService.rawOptions.scrollbar?.showScrollbar??!0,t=this._optionsService.rawOptions.scrollbar?.showArrows??!1,i=e?this._optionsService.rawOptions.scrollbar?.width??14:0;return{mouseWheelScrollSensitivity:this._optionsService.rawOptions.scrollSensitivity,fastScrollSensitivity:this._optionsService.rawOptions.fastScrollSensitivity,vertical:e?1:2,verticalScrollbarSize:i,verticalHasArrows:t}}queueSync(e){void 0!==e&&(this._latestYDisp=e),void 0===this._queuedAnimationFrame&&(this._queuedAnimationFrame=this._renderService.addRefreshCallback(()=>{this._queuedAnimationFrame=void 0,this._sync(this._latestYDisp)}))}_sync(e=this._bufferService.buffer.ydisp){this._renderService&&!this._isSyncing&&(this._coreService.decPrivateModes.synchronizedOutput?this._needsSyncOnRender=!0:(this._isSyncing=!0,this._suppressOnScrollHandler=!0,this._scrollableElement.setScrollDimensions({height:this._renderService.dimensions.css.canvas.height,scrollHeight:this._renderService.dimensions.css.cell.height*this._bufferService.buffer.lines.length}),this._suppressOnScrollHandler=!1,e!==this._latestYDisp&&this._scrollableElement.setScrollPosition({scrollTop:e*this._renderService.dimensions.css.cell.height}),this._isSyncing=!1))}_handleScroll(e){if(!this._renderService)return;if(this._isHandlingScroll||this._suppressOnScrollHandler)return;this._isHandlingScroll=!0;const t=Math.round(e.scrollTop/this._renderService.dimensions.css.cell.height),i=t-this._bufferService.buffer.ydisp;0!==i&&(this._latestYDisp=t,this._onRequestScrollLines.fire(i)),this._isHandlingScroll=!1}handleTouchScroll(e){const t=this._scrollableElement.getScrollPosition();this._scrollableElement.setScrollPosition({scrollTop:t.scrollTop-e})}};t.Viewport=_,t.Viewport=_=s([r(2,a.IBufferService),r(3,o.ICoreBrowserService),r(4,a.ICoreService),r(5,a.IMouseStateService),r(6,o.IThemeService),r(7,a.IOptionsService),r(8,o.IRenderService)],_)},4196(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferDecorationRenderer=void 0;const o=i(7098),n=i(4812),a=i(6501);let h=class extends n.Disposable{constructor(e,t,i,s,r){super(),this._screenElement=e,this._bufferService=t,this._coreBrowserService=i,this._decorationService=s,this._renderService=r,this._decorationElements=new Map,this._altBufferIsActive=!1,this._dimensionsChanged=!1,this._container=document.createElement("div"),this._container.classList.add("xterm-decoration-container"),this._screenElement.appendChild(this._container),this._register(this._renderService.onRenderedViewportChange(()=>this._doRefreshDecorations())),this._register(this._renderService.onDimensionsChange(()=>{this._dimensionsChanged=!0,this._queueRefresh()})),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._altBufferIsActive=this._bufferService.buffer===this._bufferService.buffers.alt})),this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh())),this._register(this._decorationService.onDecorationRemoved(e=>this._removeDecoration(e))),this._register((0,n.toDisposable)(()=>{this._container.remove(),this._decorationElements.clear()}))}_queueRefresh(){void 0===this._animationFrame&&(this._animationFrame=this._renderService.addRefreshCallback(()=>{this._doRefreshDecorations(),this._animationFrame=void 0}))}_doRefreshDecorations(){for(const e of this._decorationService.decorations)this._renderDecoration(e);this._dimensionsChanged=!1}_renderDecoration(e){this._refreshStyle(e),this._dimensionsChanged&&this._refreshXPosition(e)}_createElement(e){const t=this._coreBrowserService.mainDocument.createElement("div");t.classList.add("xterm-decoration"),t.classList.toggle("xterm-decoration-top-layer","top"===e?.options?.layer),t.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,t.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",t.style.top=(e.marker.line-this._bufferService.buffers.active.ydisp)*this._renderService.dimensions.css.cell.height+"px",t.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`;const i=e.options.x??0;return i&&i>this._bufferService.cols&&(t.style.display="none"),this._refreshXPosition(e,t),t}_refreshStyle(e){const t=e.marker.line-this._bufferService.buffers.active.ydisp;if(t<0||t>=this._bufferService.rows)e.element&&(e.element.style.display="none",e.onRenderEmitter.fire(e.element));else{let i=this._decorationElements.get(e);i||(i=this._createElement(e),e.element=i,this._decorationElements.set(e,i),this._container.appendChild(i),e.onDispose(()=>{this._decorationElements.delete(e),i.remove()})),i.style.display=this._altBufferIsActive?"none":"block",this._altBufferIsActive||(i.style.width=`${Math.round((e.options.width||1)*this._renderService.dimensions.css.cell.width)}px`,i.style.height=(e.options.height||1)*this._renderService.dimensions.css.cell.height+"px",i.style.top=t*this._renderService.dimensions.css.cell.height+"px",i.style.lineHeight=`${this._renderService.dimensions.css.cell.height}px`),e.onRenderEmitter.fire(i)}}_refreshXPosition(e,t=e.element){if(!t)return;const i=e.options.x??0;"right"===(e.options.anchor||"left")?t.style.right=i?i*this._renderService.dimensions.css.cell.width+"px":"":t.style.left=i?i*this._renderService.dimensions.css.cell.width+"px":""}_removeDecoration(e){this._decorationElements.get(e)?.remove(),this._decorationElements.delete(e),e.dispose()}};t.BufferDecorationRenderer=h,t.BufferDecorationRenderer=h=s([r(1,a.IBufferService),r(2,o.ICoreBrowserService),r(3,a.IDecorationService),r(4,o.IRenderService)],h)},957(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ColorZoneStore=void 0,t.ColorZoneStore=class{constructor(){this._zones=[],this._zonePool=[],this._zonePoolIndex=0,this._linePadding={full:0,left:0,center:0,right:0}}get zones(){return this._zonePool.length=Math.min(this._zonePool.length,this._zones.length),this._zones}clear(){this._zones.length=0,this._zonePoolIndex=0}addDecoration(e){if(e.options.overviewRulerOptions){for(const t of this._zones)if(t.color===e.options.overviewRulerOptions.color&&t.position===e.options.overviewRulerOptions.position){if(this._lineIntersectsZone(t,e.marker.line))return;if(this._lineAdjacentToZone(t,e.marker.line,e.options.overviewRulerOptions.position))return void this._addLineToZone(t,e.marker.line)}if(this._zonePoolIndex=e.startBufferLine&&t<=e.endBufferLine}_lineAdjacentToZone(e,t,i){return t>=e.startBufferLine-this._linePadding[i||"full"]&&t<=e.endBufferLine+this._linePadding[i||"full"]}_addLineToZone(e,t){e.startBufferLine=Math.min(e.startBufferLine,t),e.endBufferLine=Math.max(e.endBufferLine,t)}}},9925(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OverviewRulerRenderer=void 0;const o=i(957),n=i(7098),a=i(4812),h=i(6501),l={full:0,left:0,center:0,right:0},c={full:0,left:0,center:0,right:0},d={full:0,left:0,center:0,right:0};let _=class extends a.Disposable{get _width(){const e=this._optionsService.rawOptions.scrollbar;return e?.showScrollbar??1?e?.width??0:0}constructor(e,t,i,s,r,n,h,l){super(),this._viewportElement=e,this._screenElement=t,this._bufferService=i,this._decorationService=s,this._renderService=r,this._optionsService=n,this._themeService=h,this._coreBrowserService=l,this._colorZoneStore=new o.ColorZoneStore,this._shouldUpdateDimensions=!0,this._shouldUpdateAnchor=!0,this._lastKnownBufferLength=0,this._canvas=this._coreBrowserService.mainDocument.createElement("canvas"),this._canvas.classList.add("xterm-decoration-overview-ruler"),this._refreshCanvasDimensions(),this._viewportElement.parentElement?.insertBefore(this._canvas,this._viewportElement),this._register((0,a.toDisposable)(()=>this._canvas?.remove()));const c=this._canvas.getContext("2d");if(!c)throw new Error("Ctx cannot be null");this._ctx=c,this._register(this._decorationService.onDecorationRegistered(()=>this._queueRefresh(void 0,!0))),this._register(this._decorationService.onDecorationRemoved(()=>this._queueRefresh(void 0,!0))),this._register(this._renderService.onRenderedViewportChange(()=>this._queueRefresh())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._canvas.style.display=this._bufferService.buffer===this._bufferService.buffers.alt?"none":"block"})),this._register(this._bufferService.onScroll(()=>{this._lastKnownBufferLength!==this._bufferService.buffers.normal.lines.length&&(this._refreshDrawHeightConstants(),this._refreshColorZonePadding())})),this._register(this._renderService.onDimensionsChange(()=>this._queueRefresh(!0))),this._register(this._coreBrowserService.onDprChange(()=>this._queueRefresh(!0))),this._register(this._optionsService.onSpecificOptionChange("scrollbar",()=>this._queueRefresh(!0))),this._register(this._themeService.onChangeColors(()=>this._queueRefresh())),this._register((0,a.toDisposable)(()=>{void 0!==this._animationFrame&&(this._coreBrowserService.window.cancelAnimationFrame(this._animationFrame),this._animationFrame=void 0)})),this._queueRefresh(!0)}_refreshDrawConstants(){const e=Math.floor((this._canvas.width-1)/3),t=Math.ceil((this._canvas.width-1)/3);c.full=this._canvas.width,c.left=e,c.center=t,c.right=e,this._refreshDrawHeightConstants(),d.full=1,d.left=1,d.center=1+c.left,d.right=1+c.left+c.center}_refreshDrawHeightConstants(){l.full=Math.round(2*this._coreBrowserService.dpr);const e=this._canvas.height/this._bufferService.buffer.lines.length,t=Math.round(Math.max(Math.min(e,12),6)*this._coreBrowserService.dpr);l.left=t,l.center=t,l.right=t}_refreshColorZonePadding(){this._colorZoneStore.setPadding({full:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.full),left:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.left),center:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.center),right:Math.floor(this._bufferService.buffers.active.lines.length/(this._canvas.height-1)*l.right)}),this._lastKnownBufferLength=this._bufferService.buffers.normal.lines.length}_refreshCanvasDimensions(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;const e=this._renderService.dimensions.css.canvas.height,t=this._renderService.dimensions.device.canvas.height;this._canvas.style.width=`${this._width}px`,this._canvas.width=Math.round(this._width*this._coreBrowserService.dpr),this._canvas.style.height=`${e}px`,this._canvas.height=t,this._refreshDrawConstants(),this._refreshColorZonePadding()}_refreshDecorations(){if(this._store.isDisposed||!this._renderService.hasRenderer())return;this._shouldUpdateDimensions&&this._refreshCanvasDimensions(),this._ctx.clearRect(0,0,this._canvas.width,this._canvas.height),this._colorZoneStore.clear();for(const e of this._decorationService.decorations)this._colorZoneStore.addDecoration(e);this._ctx.lineWidth=1,this._renderRulerOutline();const e=this._colorZoneStore.zones;for(const t of e)"full"!==t.position&&this._renderColorZone(t);for(const t of e)"full"===t.position&&this._renderColorZone(t);this._shouldUpdateDimensions=!1,this._shouldUpdateAnchor=!1}_renderRulerOutline(){this._ctx.fillStyle=this._themeService.colors.overviewRulerBorder.css,this._ctx.fillRect(0,0,1,this._canvas.height),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showTopBorder&&this._ctx.fillRect(1,0,this._canvas.width-1,1),this._optionsService.rawOptions.scrollbar?.overviewRuler?.showBottomBorder&&this._ctx.fillRect(1,this._canvas.height-1,this._canvas.width-1,this._canvas.height)}_renderColorZone(e){this._ctx.fillStyle=e.color,this._ctx.fillRect(d[e.position||"full"],Math.round((this._canvas.height-1)*(e.startBufferLine/this._bufferService.buffers.active.lines.length)-l[e.position||"full"]/2),c[e.position||"full"],Math.round((this._canvas.height-1)*((e.endBufferLine-e.startBufferLine)/this._bufferService.buffers.active.lines.length)+l[e.position||"full"]))}_queueRefresh(e,t){this._store.isDisposed||(this._shouldUpdateDimensions=e||this._shouldUpdateDimensions,this._shouldUpdateAnchor=t||this._shouldUpdateAnchor,void 0===this._animationFrame&&(this._animationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>{this._store.isDisposed||this._refreshDecorations(),this._animationFrame=void 0})))}};t.OverviewRulerRenderer=_,t.OverviewRulerRenderer=_=s([r(2,h.IBufferService),r(3,h.IDecorationService),r(4,n.IRenderService),r(5,h.IOptionsService),r(6,n.IThemeService),r(7,n.ICoreBrowserService)],_)},3618(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CompositionHelper=void 0;const o=i(7098),n=i(6501);let a=class{get isComposing(){return this._isComposing}constructor(e,t,i,s,r,o){this._textarea=e,this._compositionView=t,this._bufferService=i,this._optionsService=s,this._coreService=r,this._renderService=o,this._isComposing=!1,this._isSendingComposition=!1,this._compositionPosition={start:0,end:0},this._compositionSuffix="",this._dataAlreadySent=""}compositionstart(){this._isComposing=!0;const e=this._textarea.selectionStart??this._textarea.value.length,t=this._textarea.selectionEnd??e;this._compositionPosition.start=Math.min(e,t),this._compositionPosition.end=Math.max(e,t),this._compositionSuffix=this._textarea.value.substring(this._compositionPosition.end),this._compositionView.textContent="",this._dataAlreadySent="",this._compositionView.classList.add("active")}compositionupdate(e){this._compositionView.textContent=`${e.data}`,this.updateCompositionElements(),setTimeout(()=>{const e=this._textarea.selectionEnd??this._textarea.value.length;this._compositionPosition.end=Math.max(this._compositionPosition.start,e)},0)}compositionend(){this._finalizeComposition(!0)}keydown(e){if(this._isComposing||this._isSendingComposition){if(20===e.keyCode||229===e.keyCode)return!1;if(16===e.keyCode||17===e.keyCode||18===e.keyCode)return!1;this._finalizeComposition(!1)}return 229!==e.keyCode||(this._handleAnyTextareaChanges(),!1)}_finalizeComposition(e){if(this._compositionView.classList.remove("active"),this._isComposing=!1,e){const e={start:this._compositionPosition.start,end:this._compositionPosition.end},t=this._compositionSuffix;this._isSendingComposition=!0,setTimeout(()=>{if(this._isSendingComposition){let i;if(this._isSendingComposition=!1,e.start+=this._dataAlreadySent.length,this._isComposing)i=this._textarea.value.substring(e.start,this._compositionPosition.start);else{const s=this._textarea.value,r=t.length>0&&s.endsWith(t)?s.length-t.length:s.length;i=s.substring(e.start,Math.max(e.start,r))}i.length>0&&this._coreService.triggerDataEvent(i,!0)}},0)}else{this._isSendingComposition=!1;const e=this._textarea.value.substring(this._compositionPosition.start,this._compositionPosition.end);this._coreService.triggerDataEvent(e,!0)}}_handleAnyTextareaChanges(){if(this._textareaChangeTimer)return;const e=this._textarea.value;this._textareaChangeTimer=window.setTimeout(()=>{if(this._textareaChangeTimer=void 0,!this._isComposing){const t=this._textarea.value,i=t.replace(e,"");this._dataAlreadySent=i,t.length>e.length?this._coreService.triggerDataEvent(i,!0):t.lengththis.updateCompositionElements(!0),0)}}};t.CompositionHelper=a,t.CompositionHelper=a=s([r(2,n.IBufferService),r(3,n.IOptionsService),r(4,n.ICoreService),r(5,o.IRenderService)],a)},5251(e,t){function i(e,t,i){const s=i.getBoundingClientRect(),r=e.getComputedStyle(i),o=parseInt(r.getPropertyValue("padding-left"),10),n=parseInt(r.getPropertyValue("padding-top"),10);return[t.clientX-s.left-o,t.clientY-s.top-n]}Object.defineProperty(t,"__esModule",{value:!0}),t.getCoordsRelativeToElement=i,t.getCoords=function(e,t,s,r,o,n,a,h,l){if(!n)return;const c=i(e,t,s);return c[0]=Math.ceil((c[0]+(l?a/2:0))/a),c[1]=Math.ceil(c[1]/h),c[0]=Math.min(Math.max(c[0],1),r+(l?1:0)),c[1]=Math.min(Math.max(c[1],1),o),c}},9686(e,t){function i(e,t,i,o){const h=e-s(e,i),l=t-s(t,i),c=Math.abs(h-l)-function(e,t,i){let o=0;const n=e-s(e,i),a=t-s(t,i);for(let s=0;s=0&&et?"A":"B"}function o(e,t,i,s,r,o){let n=e,a=t,h="";for(;(n!==i||a!==s)&&a>=0&&ao.cols-1?(h+=o.buffer.translateBufferLineToString(a,!1,e,n),n=0,e=0,a++):!r&&n<0&&(h+=o.buffer.translateBufferLineToString(a,!1,0,e+1),n=o.cols-1,e=n,a--);return h+o.buffer.translateBufferLineToString(a,!1,e,n)}function n(e,t){return""+(t?"O":"[")+e}function a(e,t){e=Math.floor(e);let i="";for(let s=0;s0?h-s(h,l):t;const _=h,u=function(e,t,r,o,n,a){let h;return h=i(t,o,n,a).length>0?o-s(o,n):t,e=r&&he?"D":"C",a(Math.abs(l-e),n(d,h));d=c>t?"D":"C";const _=Math.abs(c-t);return a(function(e,t){return t.cols-e}(c>t?e:l,r)+(_-1)*r.cols+1+((c>t?l:e)-1),n(d,h))}},6081(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._core.options[e],i=(e,t)=>{this._checkReadonlyOptions(e),this._core.options[e]=t};for(const e in this._core.options){const s={get:t.bind(this,e),set:i.bind(this,e)};Object.defineProperty(this._publicOptions,e,s)}}_checkReadonlyOptions(e){if(f.includes(e))throw new Error(`Option "${e}" can only be set in the constructor`)}_checkProposedApi(){if(!this._core.optionsService.rawOptions.allowProposedApi)throw new Error("You must set the allowProposedApi option to true to use proposed API")}get onBell(){return this._core.onBell}get onBinary(){return this._core.onBinary}get onCursorMove(){return this._core.onCursorMove}get onData(){return this._core.onData}get onKey(){return this._core.onKey}get onLineFeed(){return this._core.onLineFeed}get onRender(){return this._core.onRender}get onResize(){return this._core.onResize}get onScroll(){return this._core.onScroll}get onSelectionChange(){return this._core.onSelectionChange}get onTitleChange(){return this._core.onTitleChange}get onWriteParsed(){return this._core.onWriteParsed}get onDimensionsChange(){return this._core.onDimensionsChange}get element(){return this._core.element}get screenElement(){return this._core.screenElement}get parser(){return this._parser??=new _.ParserApi(this._core)}get unicode(){return this._checkProposedApi(),new u.UnicodeApi(this._core)}get textarea(){return this._core.textarea}get rows(){return this._core.rows}get cols(){return this._core.cols}get buffer(){return this._buffer??=this._register(new d.BufferNamespaceApi(this._core))}get markers(){return this._core.markers}get modes(){const e=this._core.coreService.decPrivateModes;let t="none";switch(this._core.mouseStateService.activeProtocol){case"X10":t="x10";break;case"VT200":t="vt200";break;case"DRAG":t="drag";break;case"ANY":t="any"}return{applicationCursorKeysMode:e.applicationCursorKeys,applicationKeypadMode:e.applicationKeypad,bracketedPasteMode:e.bracketedPasteMode,insertMode:this._core.coreService.modes.insertMode,mouseTrackingMode:t,originMode:e.origin,reverseWraparoundMode:e.reverseWraparound,sendFocusMode:e.sendFocus,showCursor:!this._core.coreService.isCursorHidden,synchronizedOutputMode:e.synchronizedOutput,win32InputMode:e.win32InputMode,wraparoundMode:e.wraparound}}get dimensions(){return this._core.dimensions}get options(){return this._publicOptions}set options(e){for(const t in e)this._publicOptions[t]=e[t]}blur(){this._core.blur()}focus(){this._core.focus()}input(e,t=!0){this._core.input(e,t)}resize(e,t){this._verifyIntegers(e,t),this._core.resize(e,t)}open(e){this._core.open(e)}attachCustomKeyEventHandler(e){this._core.attachCustomKeyEventHandler(e)}attachCustomWheelEventHandler(e){this._core.attachCustomWheelEventHandler(e)}registerLinkProvider(e){return this._core.registerLinkProvider(e)}registerCharacterJoiner(e){return this._core.registerCharacterJoiner(e)}deregisterCharacterJoiner(e){this._core.deregisterCharacterJoiner(e)}registerMarker(e=0){return this._verifyIntegers(e),this._core.registerMarker(e)}registerDecoration(e){return this._verifyPositiveIntegers(e.x??0,e.width??0,e.height??0),this._core.registerDecoration(e)}hasSelection(){return this._core.hasSelection()}select(e,t,i){this._verifyIntegers(e,t,i),this._core.select(e,t,i)}getSelection(){return this._core.getSelection()}getSelectionPosition(){return this._core.getSelectionPosition()}clearSelection(){this._core.clearSelection()}selectAll(){this._core.selectAll()}selectLines(e,t){this._verifyIntegers(e,t),this._core.selectLines(e,t)}dispose(){super.dispose()}scrollLines(e){this._verifyIntegers(e),this._core.scrollLines(e)}scrollPages(e){this._verifyIntegers(e),this._core.scrollPages(e)}scrollToTop(){this._core.scrollToTop()}scrollToBottom(){this._core.scrollToBottom()}scrollToLine(e){this._verifyIntegers(e),this._core.scrollToLine(e)}clear(){this._core.clear()}write(e,t){this._core.write(e,t)}writeln(e,t){this._core.write(e),this._core.write("\r\n",t)}paste(e){this._core.paste(e)}refresh(e,t){this._verifyIntegers(e,t),this._core.refresh(e,t)}reset(){this._core.reset()}clearTextureAtlas(){this._core.clearTextureAtlas()}loadAddon(e){this._addonManager.loadAddon(this,e)}static get strings(){return{get promptLabel(){return a.promptLabel.get()},set promptLabel(e){a.promptLabel.set(e)},get tooMuchOutput(){return a.tooMuchOutput.get()},set tooMuchOutput(e){a.tooMuchOutput.set(e)}}}_verifyIntegers(...e){for(p of e)if(p===1/0||isNaN(p)||p%1!=0)throw new Error("This API only accepts integers")}_verifyPositiveIntegers(...e){for(p of e)if(p&&(p===1/0||isNaN(p)||p%1!=0||p<0))throw new Error("This API only accepts positive integers")}}t.Terminal=v},3955(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRenderer=void 0;const o=i(1433),n=i(2744),a=i(9176),h=i(6181),l=i(2274),c=i(654),d=i(7098),_=i(4103),u=i(4812),f=i(6501),p=i(8636),v=i(4159);let g=1,m=class extends u.Disposable{constructor(e,t,i,s,r,a,d,_,f,m,b,w,y,C){super(),this._terminal=e,this._document=t,this._element=i,this._screenElement=s,this._viewportElement=r,this._helperContainer=a,this._linkifier2=d,this._charSizeService=f,this._optionsService=m,this._bufferService=b,this._coreService=w,this._coreBrowserService=y,this._themeService=C,this._terminalClass=g++,this._rowElements=[],this._selectionRenderModel=(0,l.createSelectionRenderModel)(),this._lastSelectionColumnMode=!1,this._rowHasBlinkingCells=[],this._rowHasBlinkingCellsCount=0,this._onRequestRedraw=this._register(new p.Emitter),this.onRequestRedraw=this._onRequestRedraw.event,this._rowContainer=this._document.createElement("div"),this._rowContainer.classList.add("xterm-rows"),this._rowContainer.style.lineHeight="normal",this._rowContainer.setAttribute("aria-hidden","true"),this._refreshRowElements(this._bufferService.cols,this._bufferService.rows),this._selectionContainer=this._document.createElement("div"),this._selectionContainer.classList.add("xterm-selection"),this._selectionContainer.setAttribute("aria-hidden","true"),this.dimensions=(0,h.createRenderDimensions)(),this._updateDimensions(),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._themeService.onChangeColors(e=>this._injectCss(e))),this._injectCss(this._themeService.colors),this._rowFactory=_.createInstance(o.DomRendererRowFactory,document),this._element.classList.add("xterm-dom-renderer-owner-"+this._terminalClass),this._screenElement.appendChild(this._rowContainer),this._screenElement.appendChild(this._selectionContainer),this._register(this._linkifier2.onShowLinkUnderline(e=>this._handleLinkHover(e))),this._register(this._linkifier2.onHideLinkUnderline(e=>this._handleLinkLeave(e))),this._cursorBlinkStateManager=new S(this._rowContainer,this._coreBrowserService),this._register((0,v.addDisposableListener)(this._document,"mousedown",()=>this._cursorBlinkStateManager.restartBlinkAnimation())),this._register((0,u.toDisposable)(()=>this._cursorBlinkStateManager.dispose())),this._textBlinkStateManager=this._register(new c.TextBlinkStateManager(()=>this._onRequestRedraw.fire({start:0,end:this._bufferService.rows-1}),this._coreBrowserService,this._optionsService)),this._register((0,u.toDisposable)(()=>{this._element.classList.remove("xterm-dom-renderer-owner-"+this._terminalClass),this._rowContainer.remove(),this._selectionContainer.remove(),this._widthCache.dispose(),this._themeStyleElement.remove(),this._dimensionsStyleElement.remove()})),this._widthCache=new n.WidthCache,this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}_updateDimensions(){const e=this._coreBrowserService.dpr;this.dimensions.device.char.width=this._charSizeService.width*e,this.dimensions.device.char.height=Math.ceil(this._charSizeService.height*e),this.dimensions.device.cell.width=this.dimensions.device.char.width+Math.round(this._optionsService.rawOptions.letterSpacing),this.dimensions.device.cell.height=Math.floor(this.dimensions.device.char.height*this._optionsService.rawOptions.lineHeight),this.dimensions.device.char.left=0,this.dimensions.device.char.top=0,this.dimensions.device.canvas.width=this.dimensions.device.cell.width*this._bufferService.cols,this.dimensions.device.canvas.height=this.dimensions.device.cell.height*this._bufferService.rows,this.dimensions.css.canvas.width=Math.round(this.dimensions.device.canvas.width/e),this.dimensions.css.canvas.height=Math.round(this.dimensions.device.canvas.height/e),this.dimensions.css.cell.width=this.dimensions.css.canvas.width/this._bufferService.cols,this.dimensions.css.cell.height=this.dimensions.css.canvas.height/this._bufferService.rows;for(const e of this._rowElements)e.style.width=`${this.dimensions.css.canvas.width}px`,e.style.height=`${this.dimensions.css.cell.height}px`,e.style.lineHeight=`${this.dimensions.css.cell.height}px`,e.style.overflow="hidden";this._dimensionsStyleElement||(this._dimensionsStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._dimensionsStyleElement));const t=`${this._terminalSelector} .xterm-rows span { display: inline-block; height: 100%; vertical-align: top;}`;this._dimensionsStyleElement.textContent=t,this._selectionContainer.style.height=this._viewportElement.style.height,this._screenElement.style.width=`${this.dimensions.css.canvas.width}px`,this._screenElement.style.height=`${this.dimensions.css.canvas.height}px`}_injectCss(e){this._themeStyleElement||(this._themeStyleElement=this._document.createElement("style"),this._screenElement.appendChild(this._themeStyleElement));let t=`${this._terminalSelector} .xterm-rows { pointer-events: none; color: ${e.foreground.css};}`;t+=`${this._terminalSelector} .xterm-rows, ${this._terminalSelector} .xterm-rows span { font-family: ${this._optionsService.rawOptions.fontFamily}; font-size: ${this._optionsService.rawOptions.fontSize}px; font-kerning: none; white-space: pre}`,t+=`${this._terminalSelector} .xterm-rows .xterm-dim { color: ${_.color.multiplyOpacity(e.foreground,.5).css};}`,t+=`${this._terminalSelector} span:not(.xterm-bold) { font-weight: ${this._optionsService.rawOptions.fontWeight};}${this._terminalSelector} span.xterm-bold { font-weight: ${this._optionsService.rawOptions.fontWeightBold};}${this._terminalSelector} span.xterm-italic { font-style: italic;}${this._terminalSelector} span.xterm-blink-hidden { visibility: hidden;}`;const i=`blink_underline_${this._terminalClass}`,s=`blink_bar_${this._terminalClass}`,r=`blink_block_${this._terminalClass}`;t+=`@keyframes ${i} { 50% { border-bottom-style: hidden; }}`,t+=`@keyframes ${s} { 50% { box-shadow: none; }}`,t+=`@keyframes ${r} { 0% { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css}; } 50% { background-color: inherit; color: ${e.cursor.css}; }}`,t+=`${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-underline { animation: ${i} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-bar { animation: ${s} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-focus .xterm-cursor.xterm-cursor-blink.xterm-cursor-block { animation: ${r} 1s step-end infinite;}${this._terminalSelector} .xterm-rows.xterm-cursor-blink-idle .xterm-cursor.xterm-cursor-blink { animation: none !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block { background-color: ${e.cursor.css}; color: ${e.cursorAccent.css};}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-block:not(.xterm-cursor-blink) { background-color: ${e.cursor.css} !important; color: ${e.cursorAccent.css} !important;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-outline { outline: 1px solid ${e.cursor.css}; outline-offset: -1px;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-bar { box-shadow: ${this._optionsService.rawOptions.cursorWidth}px 0 0 ${e.cursor.css} inset;}${this._terminalSelector} .xterm-rows .xterm-cursor.xterm-cursor-underline { border-bottom: 1px ${e.cursor.css}; border-bottom-style: solid; height: calc(100% - 1px);}`,t+=`${this._terminalSelector} .xterm-selection { position: absolute; top: 0; left: 0; z-index: 1; pointer-events: none;}${this._terminalSelector}.focus .xterm-selection div { position: absolute; background-color: ${e.selectionBackgroundOpaque.css};}${this._terminalSelector} .xterm-selection div { position: absolute; background-color: ${e.selectionInactiveBackgroundOpaque.css};}`;for(const[i,s]of e.ansi.entries())t+=`${this._terminalSelector} .xterm-fg-${i} { color: ${s.css}; }${this._terminalSelector} .xterm-fg-${i}.xterm-dim { color: ${_.color.multiplyOpacity(s,.5).css}; }${this._terminalSelector} .xterm-bg-${i} { background-color: ${s.css}; }`;t+=`${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR} { color: ${_.color.opaque(e.background).css}; }${this._terminalSelector} .xterm-fg-${a.INVERTED_DEFAULT_COLOR}.xterm-dim { color: ${_.color.multiplyOpacity(_.color.opaque(e.background),.5).css}; }${this._terminalSelector} .xterm-bg-${a.INVERTED_DEFAULT_COLOR} { background-color: ${e.foreground.css}; }`,this._themeStyleElement.textContent=t}_setDefaultSpacing(){const e=this.dimensions.css.cell.width-this._widthCache.get("W",!1,!1);this._rowContainer.style.letterSpacing=`${e}px`,this._rowFactory.defaultSpacing=e}handleDevicePixelRatioChange(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}_refreshRowElements(e,t){for(let e=this._rowElements.length;e<=t;e++){const e=this._document.createElement("div");this._rowContainer.appendChild(e),this._rowElements.push(e),this._rowHasBlinkingCells.push(!1)}for(;this._rowElements.length>t;)this._rowContainer.removeChild(this._rowElements.pop()),this._rowHasBlinkingCells.pop()&&this._rowHasBlinkingCellsCount--}handleResize(e,t){this._refreshRowElements(e,t),this._updateDimensions(),this.handleSelectionChanged(this._selectionRenderModel.selectionStart,this._selectionRenderModel.selectionEnd,this._selectionRenderModel.columnSelectMode)}handleCharSizeChanged(){this._updateDimensions(),this._widthCache.clear(),this._setDefaultSpacing()}handleBlur(){this._rowContainer.classList.remove("xterm-focus"),this._cursorBlinkStateManager.pause(),this.renderRows(0,this._bufferService.rows-1)}handleFocus(){this._rowContainer.classList.add("xterm-focus"),this._cursorBlinkStateManager.resume(),this.renderRows(this._bufferService.buffer.y,this._bufferService.buffer.y)}handleViewportVisibilityChange(e){this._textBlinkStateManager.setViewportVisible(e)}handleSelectionChanged(e,t,i){const s=this._bufferService.rows;this._selectionContainer.replaceChildren(),this._rowFactory.handleSelectionChanged(e,t,i);let r=0,o=-1;this._lastSelectionStart&&this._lastSelectionEnd&&(this._selectionRenderModel.update(this._terminal,this._lastSelectionStart,this._lastSelectionEnd,this._lastSelectionColumnMode),this._selectionRenderModel.hasSelection&&(r=this._selectionRenderModel.viewportCappedStartRow,o=this._selectionRenderModel.viewportCappedEndRow));let n=0,a=-1;if(!e||!t)return;if(this._selectionRenderModel.update(this._terminal,e,t,i),this._selectionRenderModel.hasSelection){const s=this._selectionRenderModel.viewportStartRow,r=this._selectionRenderModel.viewportEndRow,o=this._selectionRenderModel.viewportCappedStartRow,h=this._selectionRenderModel.viewportCappedEndRow;n=o,a=h;const l=this._document.createDocumentFragment();if(i){const i=e[0]>t[0];l.appendChild(this._createSelectionElement(o,i?t[0]:e[0],i?e[0]:t[0],h-o+1))}else{const i=s===o?e[0]:0,n=o===r?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(o,i,n));const a=h-o-1;if(l.appendChild(this._createSelectionElement(o+1,0,this._bufferService.cols,a)),o!==h){const e=r===h?t[0]:this._bufferService.cols;l.appendChild(this._createSelectionElement(h,0,e))}}this._selectionContainer.appendChild(l)}let h=Math.min(r,n),l=Math.max(o,a);if(l>=0){h=Math.max(h,0),l=Math.min(l,s-1);const e=this._bufferService.buffer.y;this._selectionRenderModel.hasSelection&&e>=0&ðis.dimensions.css.canvas.width&&(n=this.dimensions.css.canvas.width-o),r.style.height=s*this.dimensions.css.cell.height+"px",r.style.top=e*this.dimensions.css.cell.height+"px",r.style.left=`${o}px`,r.style.width=`${n}px`,r}handleCursorMove(){this._cursorBlinkStateManager.restartBlinkAnimation()}_handleOptionsChanged(){this._updateDimensions(),this._injectCss(this._themeService.colors),this._widthCache.setFont(this._optionsService.rawOptions.fontFamily,this._optionsService.rawOptions.fontSize,this._optionsService.rawOptions.fontWeight,this._optionsService.rawOptions.fontWeightBold),this._setDefaultSpacing()}clear(){for(const e of this._rowElements)e.replaceChildren();this._rowHasBlinkingCellsCount>0&&(this._rowHasBlinkingCells.fill(!1),this._rowHasBlinkingCellsCount=0,this._textBlinkStateManager.setNeedsBlinkInViewport(!1))}renderRows(e,t){const i=this._bufferService.buffer,s=i.ybase+i.y,r=Math.min(i.x,this._bufferService.cols-1),o=this._coreService.decPrivateModes.cursorBlink??this._optionsService.rawOptions.cursorBlink,n=this._coreService.decPrivateModes.cursorStyle??this._optionsService.rawOptions.cursorStyle,a=this._optionsService.rawOptions.cursorInactiveStyle,h={hasBlinkingCells:!1};for(let l=e;l<=t;l++){const e=l+i.ydisp,t=this._rowElements[l];if(!t)continue;const c=i.lines.get(e);c?(t.replaceChildren(...this._rowFactory.createRow(c,e,e===s,n,a,r,o,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,-1,-1,h)),this._setRowBlinkState(l,h.hasBlinkingCells)):(t.replaceChildren(),this._setRowBlinkState(l,!1))}this._updateTextBlinkState()}get _terminalSelector(){return`.xterm-dom-renderer-owner-${this._terminalClass}`}_handleLinkHover(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!0)}_handleLinkLeave(e){this._setCellUnderline(e.x1,e.x2,e.y1,e.y2,e.cols,!1)}_setCellUnderline(e,t,i,s,r,o){i<0&&(e=0),s<0&&(t=0);const n=this._bufferService.rows-1;i=Math.max(Math.min(i,n),0),s=Math.max(Math.min(s,n),0),r=Math.min(r,this._bufferService.cols);const a=this._bufferService.buffer,h=a.ybase+a.y,l=Math.min(a.x,r-1),c=this._optionsService.rawOptions.cursorBlink,d=this._optionsService.rawOptions.cursorStyle,_=this._optionsService.rawOptions.cursorInactiveStyle,u={hasBlinkingCells:!1};for(let n=i;n<=s;++n){const f=n+a.ydisp,p=this._rowElements[n];if(!p)continue;const v=a.lines.get(f);v?(p.replaceChildren(...this._rowFactory.createRow(v,f,f===h,d,_,l,c,this._textBlinkStateManager.isBlinkOn,this.dimensions.css.cell.width,this._widthCache,o?n===i?e:0:-1,o?(n===s?t:r)-1:-1,u)),this._setRowBlinkState(n,u.hasBlinkingCells)):(p.replaceChildren(),this._setRowBlinkState(n,!1))}this._updateTextBlinkState()}_setRowBlinkState(e,t){this._rowHasBlinkingCells[e]!==t&&(this._rowHasBlinkingCells[e]=t,this._rowHasBlinkingCellsCount+=t?1:-1)}_updateTextBlinkState(){this._textBlinkStateManager.setNeedsBlinkInViewport(this._rowHasBlinkingCellsCount>0)}};t.DomRenderer=m,t.DomRenderer=m=s([r(7,f.IInstantiationService),r(8,d.ICharSizeService),r(9,f.IOptionsService),r(10,f.IBufferService),r(11,f.ICoreService),r(12,d.ICoreBrowserService),r(13,d.IThemeService)],m);class S{constructor(e,t){this._rowContainer=e,this._coreBrowserService=t,this._isIdlePaused=!1,this._coreBrowserService.isFocused&&this._resetIdleTimer()}dispose(){this._clearIdleTimer()}restartBlinkAnimation(){this._isIdlePaused&&this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}pause(){this._isIdlePaused=!1,this._clearIdleTimer()}resume(){this._isIdlePaused=!1,this._rowContainer.classList.remove("xterm-cursor-blink-idle"),this._resetIdleTimer()}_resetIdleTimer(){this._isIdlePaused=!1,this._clearIdleTimer(),this._idleTimeout=this._coreBrowserService.window.setTimeout(()=>{this._stopBlinkingDueToIdle()},3e5)}_clearIdleTimer(){void 0!==this._idleTimeout&&(this._coreBrowserService.window.clearTimeout(this._idleTimeout),this._idleTimeout=void 0)}_stopBlinkingDueToIdle(){this._rowContainer.classList.add("xterm-cursor-blink-idle"),this._isIdlePaused=!0,this._idleTimeout=void 0}}},1433(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DomRendererRowFactory=void 0;const o=i(9176),n=i(8938),a=i(3055),h=i(6501),l=i(4103),c=i(7098),d=i(945),_=i(6181),u=i(5451);let f=class{constructor(e,t,i,s,r,o,n){this._document=e,this._characterJoinerService=t,this._optionsService=i,this._coreBrowserService=s,this._coreService=r,this._decorationService=o,this._themeService=n,this._workCell=new a.CellData,this._columnSelectMode=!1,this.defaultSpacing=0}handleSelectionChanged(e,t,i){this._selectionStart=e,this._selectionEnd=t,this._columnSelectMode=i}createRow(e,t,i,s,r,a,h,c,_,f,p,v,g){const m=[];g&&(g.hasBlinkingCells=!1);const S=this._characterJoinerService.getJoinedCharacters(t),b=this._themeService.colors;let w,y=e.getNoBgTrimmedLength();i&&y=P,F=I,W=this._workCell;if(S.length>0&&I===S[0][0]&&N){const s=S.shift(),r=this._isCellInSelection(s[0],t);for(C=s[0]+1;C=s[1],N?(H=!0,W=new d.JoinedCellData(this._workCell,e.translateToString(!0,s[0],s[1]),s[1]-s[0]),F=s[1]-1,y=W.getWidth()):P=s[1]}const z=this._isCellInSelection(I,t),K=i&&I===a,U=O&&I>=p&&I<=v;g&&W.isBlink()&&(g.hasBlinkingCells=!0),!c&&W.isBlink()&&T.push("xterm-blink-hidden");let j=!1;this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{j=!0});let $=W.getChars()||n.WHITESPACE_CELL_CHAR;if(" "===$&&(W.isUnderline()||W.isOverline())&&($=" "),k=y*_-f.get($,W.isBold(),W.isItalic()),w){if(D&&(z&&A||!z&&!A&&W.bg===L)&&(z&&A&&b.selectionForeground||W.fg===x)&&W.extended.ext===R&&U===M&&k===B&&!K&&!H&&!j&&N){W.isInvisible()?E+=n.WHITESPACE_CELL_CHAR:E+=$,D++;continue}D&&(w.textContent=E),w=this._document.createElement("span"),D=0,E=""}else w=this._document.createElement("span");if(L=W.bg,x=W.fg,R=W.extended.ext,M=U,B=k,A=z,H&&a>=I&&a<=F&&(a=I),!this._coreService.isCursorHidden&&K&&this._coreService.isCursorInitialized)if(T.push("xterm-cursor"),this._coreBrowserService.isFocused)h&&T.push("xterm-cursor-blink"),T.push("bar"===s?"xterm-cursor-bar":"underline"===s?"xterm-cursor-underline":"xterm-cursor-block");else if(r)switch(r){case"outline":T.push("xterm-cursor-outline");break;case"block":T.push("xterm-cursor-block");break;case"bar":T.push("xterm-cursor-bar");break;case"underline":T.push("xterm-cursor-underline")}if(W.isBold()&&T.push("xterm-bold"),W.isItalic()&&T.push("xterm-italic"),W.isDim()&&T.push("xterm-dim"),E=W.isInvisible()?n.WHITESPACE_CELL_CHAR:W.getChars()||n.WHITESPACE_CELL_CHAR,W.isUnderline()&&(T.push(`xterm-underline-${W.extended.underlineStyle}`)," "===E&&(E=" "),!W.isUnderlineColorDefault()))if(W.isUnderlineColorRGB())w.style.textDecorationColor=`rgb(${u.AttributeData.toColorRGB(W.getUnderlineColor()).join(",")})`;else{let e=W.getUnderlineColor();this._optionsService.rawOptions.drawBoldTextInBrightColors&&W.isBold()&&e<8&&(e+=8),w.style.textDecorationColor=b.ansi[e].css}W.isOverline()&&(T.push("xterm-overline")," "===E&&(E=" ")),W.isStrikethrough()&&T.push("xterm-strikethrough"),U&&(w.style.textDecoration="underline");let V=W.getFgColor(),q=W.getFgColorMode(),X=W.getBgColor(),Y=W.getBgColorMode();const G=!!W.isInverse();if(G){const e=V;V=X,X=e;const t=q;q=Y,Y=t}let J,Z,Q,ee=!1;switch(this._decorationService.forEachDecorationAtCell(I,t,void 0,e=>{"top"!==e.options.layer&&ee||(e.backgroundColorRGB&&(Y=50331648,X=e.backgroundColorRGB.rgba>>8&16777215,J=e.backgroundColorRGB),e.foregroundColorRGB&&(q=50331648,V=e.foregroundColorRGB.rgba>>8&16777215,Z=e.foregroundColorRGB),ee="top"===e.options.layer)}),!ee&&z&&(J=this._coreBrowserService.isFocused?b.selectionBackgroundOpaque:b.selectionInactiveBackgroundOpaque,X=J.rgba>>8&16777215,Y=50331648,ee=!0,b.selectionForeground&&(q=50331648,V=b.selectionForeground.rgba>>8&16777215,Z=b.selectionForeground)),ee&&T.push("xterm-decoration-top"),Y){case 16777216:case 33554432:Q=b.ansi[X],T.push(`xterm-bg-${X}`);break;case 50331648:Q=l.channels.toColor(X>>16,X>>8&255,255&X),this._addStyle(w,`background-color:#${(X>>>0).toString(16).padStart(6,"0")}`);break;default:G?(Q=b.foreground,T.push(`xterm-bg-${o.INVERTED_DEFAULT_COLOR}`)):Q=b.background}switch(J||W.isDim()&&(J=l.color.multiplyOpacity(Q,.5)),q){case 16777216:case 33554432:W.isBold()&&V<8&&this._optionsService.rawOptions.drawBoldTextInBrightColors&&(V+=8),this._applyMinimumContrast(w,Q,b.ansi[V],W,J,void 0)||T.push(`xterm-fg-${V}`);break;case 50331648:const e=l.channels.toColor(V>>16&255,V>>8&255,255&V);this._applyMinimumContrast(w,Q,e,W,J,Z)||this._addStyle(w,`color:#${V.toString(16).padStart(6,"0")}`);break;default:this._applyMinimumContrast(w,Q,b.foreground,W,J,Z)||G&&T.push(`xterm-fg-${o.INVERTED_DEFAULT_COLOR}`)}T.length&&(w.className=T.join(" "),T.length=0),K||H||j||!N?w.textContent=E:D++,k!==this.defaultSpacing&&(w.style.letterSpacing=`${k}px`),m.push(w),I=F}return w&&D&&(w.textContent=E),m}_applyMinimumContrast(e,t,i,s,r,o){if(1===this._optionsService.rawOptions.minimumContrastRatio||(0,_.treatGlyphAsBackgroundColor)(s.getCode()))return!1;const n=this._getContrastCache(s);let a;if(r||o||(a=n.getColor(t.rgba,i.rgba)),void 0===a){const e=this._optionsService.rawOptions.minimumContrastRatio/(s.isDim()?2:1);a=l.color.ensureContrastRatio(r??t,o??i,e),n.setColor((r??t).rgba,(o??i).rgba,a??null)}return!!a&&(this._addStyle(e,`color:${a.css}`),!0)}_getContrastCache(e){return e.isDim()?this._themeService.colors.halfContrastCache:this._themeService.colors.contrastCache}_addStyle(e,t){e.setAttribute("style",`${e.getAttribute("style")||""}${t};`)}_isCellInSelection(e,t){const i=this._selectionStart,s=this._selectionEnd;return!(!i||!s)&&(this._columnSelectMode?i[0]<=s[0]?e>=i[0]&&t>=i[1]&&e=i[1]&&e>=s[0]&&t<=s[1]:t>i[1]&&t=i[0]&&e=i[0])}};t.DomRendererRowFactory=f,t.DomRendererRowFactory=f=s([r(1,c.ICharacterJoinerService),r(2,h.IOptionsService),r(3,c.ICoreBrowserService),r(4,h.ICoreService),r(5,h.IDecorationService),r(6,c.IThemeService)],f)},2744(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WidthCache=void 0;const s=i(6181);t.WidthCache=class{constructor(e=()=>new r){this._flat=new Float32Array(256),this._font="",this._fontSize=0,this._weight="normal",this._weightBold="bold",this._canvasElements=[],this._canvasElements=[e(),e(),e(),e()],this.clear()}dispose(){this._canvasElements.length=0,this._holey=void 0}clear(){this._flat.fill(-9999),this._holey=new Map}setFont(e,t,i,s){e===this._font&&t===this._fontSize&&i===this._weight&&s===this._weightBold||(this._font=e,this._fontSize=t,this._weight=i,this._weightBold=s,this._canvasElements[0].setFont(e,t,i,!1),this._canvasElements[1].setFont(e,t,s,!1),this._canvasElements[2].setFont(e,t,i,!0),this._canvasElements[3].setFont(e,t,s,!0),this.clear())}get(e,t,i){let s;if(!t&&!i&&1===e.length&&(s=e.charCodeAt(0))<256){if(-9999!==this._flat[s])return this._flat[s];const t=this._measure(e,0);return t>0&&(this._flat[s]=t),t}let r=e;t&&(r+="B"),i&&(r+="I");let o=this._holey.get(r);if(void 0===o){let s=0;t&&(s|=1),i&&(s|=2),o=this._measure(e,s),o>0&&this._holey.set(r,o)}return o}_measure(e,t){return this._canvasElements[t].measure(e)}};class r{constructor(){"undefined"!=typeof OffscreenCanvas?(this._canvas=new OffscreenCanvas(1,1),this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d"))):(this._canvas=document.createElement("canvas"),this._canvas.width=1,this._canvas.height=1,this._ctx=(0,s.throwIfFalsy)(this._canvas.getContext("2d")))}setFont(e,t,i,s){const r=s?"italic":"";this._ctx.font=`${r} ${i} ${t}px ${e}`.trim()}measure(e){return this._ctx.measureText(e).width}}},9176(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.INVERTED_DEFAULT_COLOR=void 0,t.INVERTED_DEFAULT_COLOR=257},6181(e,t){function i(e){return 57508<=e&&e<=57558}function s(e){return e>=128512&&e<=128591||e>=127744&&e<=128511||e>=128640&&e<=128767||e>=9728&&e<=9983||e>=9984&&e<=10175||e>=65024&&e<=65039||e>=129280&&e<=129535||e>=127462&&e<=127487}Object.defineProperty(t,"__esModule",{value:!0}),t.throwIfFalsy=function(e){if(!e)throw new Error("value must not be falsy");return e},t.isPowerlineGlyph=i,t.isRestrictedPowerlineGlyph=function(e){return 57520<=e&&e<=57527},t.isEmoji=s,t.allowRescaling=function(e,t,r,o){return 1===t&&r>Math.ceil(1.5*o)&&void 0!==e&&e>255&&!s(e)&&!i(e)&&!function(e){return 57344<=e&&e<=63743}(e)},t.treatGlyphAsBackgroundColor=function(e){return i(e)||function(e){return 9472<=e&&e<=9631}(e)},t.createRenderDimensions=function(){return{css:{canvas:{width:0,height:0},cell:{width:0,height:0}},device:{canvas:{width:0,height:0},cell:{width:0,height:0},char:{width:0,height:0,left:0,top:0}}}},t.computeNextVariantOffset=function(e,t,i=0){return(e-(2*Math.round(t)-i))%(2*Math.round(t))}},2274(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.createSelectionRenderModel=function(){return new i};class i{constructor(){this.clear()}clear(){this.hasSelection=!1,this.columnSelectMode=!1,this.viewportStartRow=0,this.viewportEndRow=0,this.viewportCappedStartRow=0,this.viewportCappedEndRow=0,this.startCol=0,this.endCol=0,this.selectionStart=void 0,this.selectionEnd=void 0}update(e,t,i,s=!1){if(this.selectionStart=t,this.selectionEnd=i,!t||!i||t[0]===i[0]&&t[1]===i[1])return void this.clear();const r=e.buffers.active.ydisp,o=t[1]-r,n=i[1]-r,a=Math.max(o,0),h=Math.min(n,e.rows-1);a>=e.rows||h<0?this.clear():(this.hasSelection=!0,this.columnSelectMode=s,this.viewportStartRow=o,this.viewportEndRow=n,this.viewportCappedStartRow=a,this.viewportCappedEndRow=h,this.startCol=t[0],this.endCol=i[0])}isCellSelected(e,t,i){return!!this.hasSelection&&(i-=e.buffer.active.viewportY,this.columnSelectMode?this.startCol<=this.endCol?t>=this.startCol&&i>=this.viewportCappedStartRow&&t=this.viewportCappedStartRow&&t>=this.endCol&&i<=this.viewportCappedEndRow:i>this.viewportStartRow&&i=this.startCol&&t=this.startCol)}}},654(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.TextBlinkStateManager=void 0;const s=i(4812);class r extends s.Disposable{constructor(e,t,i){super(),this._renderCallback=e,this._coreBrowserService=t,this._optionsService=i,this._intervalDuration=0,this._blinkOn=!0,this._needsBlinkInViewport=!1,this._isViewportVisible=!0,this._register(this._optionsService.onSpecificOptionChange("blinkIntervalDuration",e=>{this.setIntervalDuration(e)})),this.setIntervalDuration(this._optionsService.rawOptions.blinkIntervalDuration),this._register((0,s.toDisposable)(()=>this._clearInterval()))}get isBlinkOn(){return this._blinkOn}get isEnabled(){return this._intervalDuration>0}setNeedsBlinkInViewport(e){this._needsBlinkInViewport!==e&&(this._needsBlinkInViewport=e,this._updateIntervalState())}setViewportVisible(e){this._isViewportVisible!==e&&(this._isViewportVisible=e,this._updateIntervalState())}setIntervalDuration(e){e!==this._intervalDuration&&(this._intervalDuration=e,this._clearInterval(),this._updateIntervalState())}_updateIntervalState(){if(this._intervalDuration>0&&this._needsBlinkInViewport&&this._isViewportVisible){if(void 0!==this._interval)return;const e=this._blinkOn;return this._blinkOn=!0,this._interval=this._coreBrowserService.window.setInterval(()=>{this._blinkOn=!this._blinkOn,this._renderCallback()},this._intervalDuration),void(e||this._renderCallback())}this._clearInterval(),this._blinkOn||(this._blinkOn=!0,this._renderCallback())}_clearInterval(){void 0!==this._interval&&(this._coreBrowserService.window.clearInterval(this._interval),this._interval=void 0)}}t.TextBlinkStateManager=r},8501(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._domNodePointerDown(e)))}_createArrow(e){const t=this._register(new c.ScrollbarArrow(e));return this.domNode.domNode.appendChild(t.bgDomNode),this.domNode.domNode.appendChild(t.domNode),t}_createSlider(e,t,i,s){this.slider=new h.FastDomNode(document.createElement("div")),this.slider.setClassName("xterm-slider"),this.slider.setPosition("absolute"),this.slider.setTop(e),this.slider.setLeft(t),"number"==typeof i&&this.slider.setWidth(i),"number"==typeof s&&this.slider.setHeight(s),this.slider.setLayerHinting(!0),this.slider.setContain("strict"),this.domNode.domNode.appendChild(this.slider.domNode),this._register(a.addDisposableListener(this.slider.domNode,a.eventType.POINTER_DOWN,e=>{0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))})),this._onclick(this.slider.domNode,e=>{e.leftButton&&e.stopPropagation()})}_handleElementSize(e){return this._scrollbarState.setVisibleSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollSize(e){return this._scrollbarState.setScrollSize(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}_handleElementScrollPosition(e){return this._scrollbarState.setScrollPosition(e)&&(this._visibilityController.setIsNeeded(this._scrollbarState.isNeeded()),this._shouldRender=!0,this._lazyRender||this.render()),this._shouldRender}beginReveal(){this._visibilityController.setShouldBeVisible(!0)}beginHide(){this._visibilityController.setShouldBeVisible(!1)}render(){this._shouldRender&&(this._shouldRender=!1,this._renderDomNode(this._scrollbarState.getRectangleLargeSize(),this._scrollbarState.getRectangleSmallSize()),this._updateSlider(this._scrollbarState.getSliderSize(),this._scrollbarState.getArrowSize()+this._scrollbarState.getSliderPosition()))}_domNodePointerDown(e){e.target===this.domNode.domNode&&this._handlePointerDown(e)}delegatePointerDown(e){const t=this.domNode.domNode.getClientRects()[0].top,i=t+this._scrollbarState.getSliderPosition(),s=t+this._scrollbarState.getSliderPosition()+this._scrollbarState.getSliderSize(),r=this._sliderPointerPosition(e);i<=r&&r<=s?0===e.button&&(e.preventDefault(),this._sliderPointerDown(e)):this._handlePointerDown(e)}_handlePointerDown(e){let t,i;if(e.target===this.domNode.domNode&&"number"==typeof e.offsetX&&"number"==typeof e.offsetY)t=e.offsetX,i=e.offsetY;else{const s=a.getDomNodePagePosition(this.domNode.domNode);t=e.pageX-s.left,i=e.pageY-s.top}const s=this._pointerDownRelativePosition(t,i);this._setDesiredScrollPositionNow(this._scrollByPage?this._scrollbarState.getDesiredScrollPositionFromOffsetPaged(s):this._scrollbarState.getDesiredScrollPositionFromOffset(s)),0===e.button&&(e.preventDefault(),this._sliderPointerDown(e))}_sliderPointerDown(e){if(!(e.target&&e.target instanceof Element))return;const t=this._sliderPointerPosition(e),i=this._sliderOrthogonalPointerPosition(e),s=this._scrollbarState.clone();this.slider.toggleClassName("xterm-active",!0),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{const r=this._sliderOrthogonalPointerPosition(e),o=Math.abs(r-i);if(u.isWindows&&o>140)return void this._setDesiredScrollPositionNow(s.getScrollPosition());const n=this._sliderPointerPosition(e)-t;this._setDesiredScrollPositionNow(s.getDesiredScrollPositionFromDelta(n))},()=>{this.slider.toggleClassName("xterm-active",!1),this._host.handleDragEnd()}),this._host.handleDragStart()}_setDesiredScrollPositionNow(e){const t={};this.writeScrollPosition(t,e),this._scrollable.setScrollPositionNow(t)}updateScrollbarSize(e){this._updateScrollbarSize(e),this._scrollbarState.setScrollbarSize(e),this._shouldRender=!0,this._lazyRender||this.render()}isNeeded(){return this._scrollbarState.isNeeded()}}t.AbstractScrollbar=f},1203(e,t){function i(e){return"number"==typeof e?`${e}px`:e}Object.defineProperty(t,"__esModule",{value:!0}),t.FastDomNode=void 0,t.FastDomNode=class{constructor(e){this.domNode=e,this._width="",this._height="",this._top="",this._left="",this._bottom="",this._right="",this._className="",this._position="",this._layerHint=!1,this._contain="none"}setWidth(e){const t=i(e);this._width!==t&&(this._width=t,this.domNode.style.width=this._width)}setHeight(e){const t=i(e);this._height!==t&&(this._height=t,this.domNode.style.height=this._height)}setTop(e){const t=i(e);this._top!==t&&(this._top=t,this.domNode.style.top=this._top)}setLeft(e){const t=i(e);this._left!==t&&(this._left=t,this.domNode.style.left=this._left)}setBottom(e){const t=i(e);this._bottom!==t&&(this._bottom=t,this.domNode.style.bottom=this._bottom)}setRight(e){const t=i(e);this._right!==t&&(this._right=t,this.domNode.style.right=this._right)}setClassName(e){this._className!==e&&(this._className=e,this.domNode.className=this._className)}toggleClassName(e,t){this.domNode.classList.toggle(e,t),this._className=this.domNode.className}setPosition(e){this._position!==e&&(this._position=e,this.domNode.style.position=this._position)}setLayerHinting(e){this._layerHint!==e&&(this._layerHint=e,this.domNode.style.transform=e?"translate3d(0px, 0px, 0px)":"")}setContain(e){this._contain!==e&&(this._contain=e,this.domNode.style.contain=this._contain)}setAttribute(e,t){this.domNode.setAttribute(e,t)}}},928(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{try{e.releasePointerCapture(t)}catch{}}))}catch{o=a.getWindow(e)}this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_MOVE,e=>{e.buttons===i?(e.preventDefault(),this._pointerMoveCallback(e)):this.stopMonitoring(!0)})),this._hooks.add(a.addDisposableListener(o,a.eventType.POINTER_UP,e=>this.stopMonitoring(!0)))}}},9699(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.HorizontalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition();if(super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(t.horizontalHasArrows?t.horizontalScrollbarSize:0,2===t.horizontal?0:t.horizontalScrollbarSize,2===t.vertical?0:t.verticalScrollbarSize,s.width,s.scrollWidth,o.scrollLeft),visibility:t.horizontal,extraScrollbarClassName:"xterm-horizontal",scrollable:e,scrollByPage:t.scrollByPage}),t.horizontalHasArrows)throw new Error("horizontalHasArrows is not supported in xterm.js");this._createSlider(Math.floor((t.horizontalScrollbarSize-t.horizontalSliderSize)/2),0,void 0,t.horizontalSliderSize)}_updateSlider(e,t){this.slider.setWidth(e),this.slider.setLeft(t)}_renderDomNode(e,t){this.domNode.setWidth(e),this.domNode.setHeight(t),this.domNode.setLeft(0),this.domNode.setBottom(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollWidth)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollLeft)||this._shouldRender,this._shouldRender=this._handleElementSize(e.width)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return e}_sliderPointerPosition(e){return e.pageX}_sliderOrthogonalPointerPosition(e){return e.pageY}_updateScrollbarSize(e){this.slider.setHeight(e)}writeScrollPosition(e,t){e.scrollLeft=t}updateOptions(e){this.updateScrollbarSize(2===e.horizontal?0:e.horizontalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._visibilityController.setVisibility(e.horizontal),this._scrollByPage=e.scrollByPage}}t.HorizontalScrollbar=o},3988(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;ni&&(s=i-t),s<0&&(s=0),r<0&&(r=0),n+r>o&&(n=o-r),n<0&&(n=0),this.width=t,this.scrollWidth=i,this.scrollLeft=s,this.height=r,this.scrollHeight=o,this.scrollTop=n}equals(e){return this.rawScrollLeft===e.rawScrollLeft&&this.rawScrollTop===e.rawScrollTop&&this.width===e.width&&this.scrollWidth===e.scrollWidth&&this.scrollLeft===e.scrollLeft&&this.height===e.height&&this.scrollHeight===e.scrollHeight&&this.scrollTop===e.scrollTop}withScrollDimensions(e,t){return new o(this._forceIntegerValues,void 0!==e.width?e.width:this.width,void 0!==e.scrollWidth?e.scrollWidth:this.scrollWidth,t?this.rawScrollLeft:this.scrollLeft,void 0!==e.height?e.height:this.height,void 0!==e.scrollHeight?e.scrollHeight:this.scrollHeight,t?this.rawScrollTop:this.scrollTop)}withScrollPosition(e){return new o(this._forceIntegerValues,this.width,this.scrollWidth,void 0!==e.scrollLeft?e.scrollLeft:this.rawScrollLeft,this.height,this.scrollHeight,void 0!==e.scrollTop?e.scrollTop:this.rawScrollTop)}createScrollEvent(e,t){const i=this.width!==e.width,s=this.scrollWidth!==e.scrollWidth,r=this.scrollLeft!==e.scrollLeft,o=this.height!==e.height,n=this.scrollHeight!==e.scrollHeight,a=this.scrollTop!==e.scrollTop;return{inSmoothScrolling:t,oldWidth:e.width,oldScrollWidth:e.scrollWidth,oldScrollLeft:e.scrollLeft,width:this.width,scrollWidth:this.scrollWidth,scrollLeft:this.scrollLeft,oldHeight:e.height,oldScrollHeight:e.scrollHeight,oldScrollTop:e.scrollTop,height:this.height,scrollHeight:this.scrollHeight,scrollTop:this.scrollTop,widthChanged:i,scrollWidthChanged:s,scrollLeftChanged:r,heightChanged:o,scrollHeightChanged:n,scrollTopChanged:a}}}t.ScrollState=o;class n extends r.Disposable{constructor(e){super(),this._scrollableBrand=void 0,this._onScroll=this._register(new s.Emitter),this.onScroll=this._onScroll.event,this._smoothScrollDuration=e.smoothScrollDuration,this._scheduleAtNextAnimationFrame=e.scheduleAtNextAnimationFrame,this._state=new o(e.forceIntegerValues,0,0,0,0,0,0),this._smoothScrolling=null}dispose(){this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),super.dispose()}setSmoothScrollDuration(e){this._smoothScrollDuration=e}validateScrollPosition(e){return this._state.withScrollPosition(e)}getScrollDimensions(){return this._state}setScrollDimensions(e,t){const i=this._state.withScrollDimensions(e,t);this._setState(i,Boolean(this._smoothScrolling)),this._smoothScrolling?.acceptScrollDimensions(this._state)}getFutureScrollPosition(){return this._smoothScrolling?this._smoothScrolling.to:this._state}getCurrentScrollPosition(){return this._state}setScrollPositionNow(e){const t=this._state.withScrollPosition(e);this._smoothScrolling&&(this._smoothScrolling.dispose(),this._smoothScrolling=null),this._setState(t,!1)}setScrollPositionSmooth(e,t){if(0!==this._smoothScrollDuration){if(this._smoothScrolling){e={scrollLeft:void 0===e.scrollLeft?this._smoothScrolling.to.scrollLeft:e.scrollLeft,scrollTop:void 0===e.scrollTop?this._smoothScrolling.to.scrollTop:e.scrollTop};const i=this._state.withScrollPosition(e);if(this._smoothScrolling.to.scrollLeft===i.scrollLeft&&this._smoothScrolling.to.scrollTop===i.scrollTop)return;let s;s=t?new l(this._smoothScrolling.from,i,this._smoothScrolling.startTime,this._smoothScrolling.duration):l.start(this._state,i,this._smoothScrollDuration),this._smoothScrolling.dispose(),this._smoothScrolling=s}else{const t=this._state.withScrollPosition(e);this._smoothScrolling=l.start(this._state,t,this._smoothScrollDuration)}this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})}else this.setScrollPositionNow(e)}hasPendingScrollAnimation(){return Boolean(this._smoothScrolling)}_performSmoothScrolling(){if(!this._smoothScrolling)return;const e=this._smoothScrolling.tick(),t=this._state.withScrollPosition(e);return this._setState(t,!0),this._smoothScrolling?e.isDone?(this._smoothScrolling.dispose(),void(this._smoothScrolling=null)):void(this._smoothScrolling.animationFrameDisposable=this._scheduleAtNextAnimationFrame(()=>{this._smoothScrolling&&(this._smoothScrolling.animationFrameDisposable=null,this._performSmoothScrolling())})):void 0}_setState(e,t){const i=this._state;i.equals(e)||(this._state=e,this._onScroll.fire(this._state.createScrollEvent(i,t)))}}t.Scrollable=n;class a{constructor(e,t,i){this.scrollLeft=e,this.scrollTop=t,this.isDone=i}}function h(e,t){const i=t-e;return function(t){return e+i*(1-(s=1-t,Math.pow(s,3)));var s}}class l{constructor(e,t,i,s){this.from=e,this.to=t,this.duration=s,this.startTime=i,this.animationFrameDisposable=null,this._initAnimations()}_initAnimations(){this._scrollLeft=this._initAnimation(this.from.scrollLeft,this.to.scrollLeft,this.to.width),this._scrollTop=this._initAnimation(this.from.scrollTop,this.to.scrollTop,this.to.height)}_initAnimation(e,t,i){if(Math.abs(e-t)>2.5*i){let n,a;return e0&&Math.abs(e.deltaY)>0)return 1;let i=.5;if(this._isAlmostInt(e.deltaX)&&this._isAlmostInt(e.deltaY)||(i+=.25),t){const s=Math.abs(e.deltaX),r=Math.abs(e.deltaY),o=Math.abs(t.deltaX),n=Math.abs(t.deltaY),a=Math.max(Math.min(s,o),1),h=Math.max(Math.min(r,n),1),l=Math.max(s,o),c=Math.max(r,n);l%a===0&&c%h===0&&(i-=.5)}return Math.min(Math.max(i,0),1)}_isAlmostInt(e){return Math.abs(Math.round(e)-e)<.01}}S.INSTANCE=new S;class b extends _.Widget{get options(){return this._options}constructor(e,t,i){let s;super(),this._onScroll=this._register(new f.Emitter),this.onScroll=this._onScroll.event,t=t??{};const r=!i;i?s=i:(t.mouseWheelSmoothScroll=!1,s=new g.Scrollable({forceIntegerValues:!0,smoothScrollDuration:0,scheduleAtNextAnimationFrame:t=>a.scheduleAtNextAnimationFrame(a.getWindow(e),t)})),this._options=function(e){const t={lazyRender:void 0!==e.lazyRender&&e.lazyRender,className:void 0!==e.className?e.className:"",useShadows:void 0===e.useShadows||e.useShadows,handleMouseWheel:void 0===e.handleMouseWheel||e.handleMouseWheel,flipAxes:void 0!==e.flipAxes&&e.flipAxes,consumeMouseWheelIfScrollbarIsNeeded:void 0!==e.consumeMouseWheelIfScrollbarIsNeeded&&e.consumeMouseWheelIfScrollbarIsNeeded,alwaysConsumeMouseWheel:void 0!==e.alwaysConsumeMouseWheel&&e.alwaysConsumeMouseWheel,scrollYToX:void 0!==e.scrollYToX&&e.scrollYToX,mouseWheelScrollSensitivity:void 0!==e.mouseWheelScrollSensitivity?e.mouseWheelScrollSensitivity:1,fastScrollSensitivity:void 0!==e.fastScrollSensitivity?e.fastScrollSensitivity:5,scrollPredominantAxis:void 0===e.scrollPredominantAxis||e.scrollPredominantAxis,mouseWheelSmoothScroll:void 0===e.mouseWheelSmoothScroll||e.mouseWheelSmoothScroll,listenOnDomNode:void 0!==e.listenOnDomNode?e.listenOnDomNode:null,horizontal:void 0!==e.horizontal?e.horizontal:1,horizontalScrollbarSize:void 0!==e.horizontalScrollbarSize?e.horizontalScrollbarSize:10,horizontalSliderSize:void 0!==e.horizontalSliderSize?e.horizontalSliderSize:0,horizontalHasArrows:void 0!==e.horizontalHasArrows&&e.horizontalHasArrows,vertical:void 0!==e.vertical?e.vertical:1,verticalScrollbarSize:void 0!==e.verticalScrollbarSize?e.verticalScrollbarSize:10,verticalHasArrows:void 0!==e.verticalHasArrows&&e.verticalHasArrows,verticalSliderSize:void 0!==e.verticalSliderSize?e.verticalSliderSize:0,scrollByPage:void 0!==e.scrollByPage&&e.scrollByPage};return t.horizontalSliderSize=void 0!==e.horizontalSliderSize?e.horizontalSliderSize:t.horizontalScrollbarSize,t.verticalSliderSize=void 0!==e.verticalSliderSize?e.verticalSliderSize:t.verticalScrollbarSize,v.isMac&&(t.className+=" xterm-mac"),t}(t),this._scrollable=s,this._register(this._scrollable.onScroll(e=>{this._handleScroll(e),this._onScroll.fire(e)})),r&&this._register(this._scrollable);const o={handleMouseWheel:e=>this._handleMouseWheel(e),handleDragStart:()=>this._handleDragStart(),handleDragEnd:()=>this._handleDragEnd()};this._verticalScrollbar=this._register(new d.VerticalScrollbar(this._scrollable,this._options,o)),this._horizontalScrollbar=this._register(new c.HorizontalScrollbar(this._scrollable,this._options,o)),this._domNode=document.createElement("div"),this._domNode.className="xterm-scrollable-element "+this._options.className,this._domNode.setAttribute("role","presentation"),this._domNode.style.position="relative",this._domNode.appendChild(e),this._domNode.appendChild(this._horizontalScrollbar.domNode.domNode),this._domNode.appendChild(this._verticalScrollbar.domNode.domNode),this._options.useShadows?(this._leftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._leftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._leftShadowDomNode.domNode),this._topShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topShadowDomNode.domNode),this._topLeftShadowDomNode=new h.FastDomNode(document.createElement("div")),this._topLeftShadowDomNode.setClassName("xterm-shadow"),this._domNode.appendChild(this._topLeftShadowDomNode.domNode)):(this._leftShadowDomNode=null,this._topShadowDomNode=null,this._topLeftShadowDomNode=null),this._listenOnDomNode=this._options.listenOnDomNode??this._domNode,this._mouseWheelToDispose=[],this._setListeningToMouseWheel(this._options.handleMouseWheel),this._onmouseover(this._listenOnDomNode,e=>this._handleMouseOver(e)),this._onmouseleave(this._listenOnDomNode,e=>this._handleMouseLeave(e)),this._hideTimeout=this._register(new u.TimeoutTimer),this._isDragging=!1,this._mouseIsOver=!1,this._shouldRender=!0,this._revealOnScroll=!0}dispose(){this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),super.dispose()}getDomNode(){return this._domNode}getScrollDimensions(){return this._scrollable.getScrollDimensions()}setScrollDimensions(e){this._scrollable.setScrollDimensions(e,!1)}setScrollPosition(e){e.reuseAnimation?this._scrollable.setScrollPositionSmooth(e,e.reuseAnimation):this._scrollable.setScrollPositionNow(e)}getScrollPosition(){return this._scrollable.getCurrentScrollPosition()}updateClassName(e){this._options.className=e,v.isMac&&(this._options.className+=" xterm-mac"),this._domNode.className="xterm-scrollable-element "+this._options.className}updateOptions(e){void 0!==e.handleMouseWheel&&(this._options.handleMouseWheel=e.handleMouseWheel,this._setListeningToMouseWheel(this._options.handleMouseWheel)),void 0!==e.mouseWheelScrollSensitivity&&(this._options.mouseWheelScrollSensitivity=e.mouseWheelScrollSensitivity),void 0!==e.fastScrollSensitivity&&(this._options.fastScrollSensitivity=e.fastScrollSensitivity),void 0!==e.scrollPredominantAxis&&(this._options.scrollPredominantAxis=e.scrollPredominantAxis),void 0!==e.horizontal&&(this._options.horizontal=e.horizontal),void 0!==e.vertical&&(this._options.vertical=e.vertical),void 0!==e.horizontalHasArrows&&(this._options.horizontalHasArrows=e.horizontalHasArrows),void 0!==e.verticalHasArrows&&(this._options.verticalHasArrows=e.verticalHasArrows),void 0!==e.horizontalScrollbarSize&&(this._options.horizontalScrollbarSize=e.horizontalScrollbarSize),void 0!==e.verticalScrollbarSize&&(this._options.verticalScrollbarSize=e.verticalScrollbarSize),void 0!==e.scrollByPage&&(this._options.scrollByPage=e.scrollByPage),this._horizontalScrollbar.updateOptions(this._options),this._verticalScrollbar.updateOptions(this._options),this._options.lazyRender||this._render()}delegateScrollFromMouseWheelEvent(e){this._handleMouseWheel(new l.StandardWheelEvent(e))}_setListeningToMouseWheel(e){if(this._mouseWheelToDispose.length>0!==e&&(this._mouseWheelToDispose=(0,p.dispose)(this._mouseWheelToDispose),e)){const e=e=>{this._handleMouseWheel(new l.StandardWheelEvent(e))};this._mouseWheelToDispose.push(a.addDisposableListener(this._listenOnDomNode,a.eventType.MOUSE_WHEEL,e,{passive:!1}))}}_handleMouseWheel(e){if(e.browserEvent?.defaultPrevented)return;const t=S.INSTANCE;t.acceptStandardWheelEvent(e);let i=!1;if(e.deltaY||e.deltaX){let s=e.deltaY*this._options.mouseWheelScrollSensitivity,r=e.deltaX*this._options.mouseWheelScrollSensitivity;this._options.scrollPredominantAxis&&(this._options.scrollYToX&&r+s===0?r=s=0:Math.abs(s)>=Math.abs(r)?r=0:s=0),this._options.flipAxes&&([s,r]=[r,s]);const o=!v.isMac&&e.browserEvent&&e.browserEvent.shiftKey;!this._options.scrollYToX&&!o||r||(r=s,s=0),e.browserEvent&&e.browserEvent.altKey&&(r*=this._options.fastScrollSensitivity,s*=this._options.fastScrollSensitivity);const n=this._scrollable.getFutureScrollPosition();let a={};if(s){const e=50*s,t=n.scrollTop-(e<0?Math.floor(e):Math.ceil(e));this._verticalScrollbar.writeScrollPosition(a,t)}if(r){const e=50*r,t=n.scrollLeft-(e<0?Math.floor(e):Math.ceil(e));this._horizontalScrollbar.writeScrollPosition(a,t)}a=this._scrollable.validateScrollPosition(a),(n.scrollLeft!==a.scrollLeft||n.scrollTop!==a.scrollTop)&&(this._options.mouseWheelSmoothScroll&&t.isPhysicalMouseWheel()?this._scrollable.setScrollPositionSmooth(a):this._scrollable.setScrollPositionNow(a),i=!0)}let s=i;!s&&this._options.alwaysConsumeMouseWheel&&(s=!0),!s&&this._options.consumeMouseWheelIfScrollbarIsNeeded&&(this._verticalScrollbar.isNeeded()||this._horizontalScrollbar.isNeeded())&&(s=!0),s&&(e.preventDefault(),e.stopPropagation())}_handleScroll(e){this._shouldRender=this._horizontalScrollbar.handleScroll(e)||this._shouldRender,this._shouldRender=this._verticalScrollbar.handleScroll(e)||this._shouldRender,this._options.useShadows&&(this._shouldRender=!0),this._revealOnScroll&&this._reveal(),this._options.lazyRender||this._render()}renderNow(){if(!this._options.lazyRender)throw new Error("Please use `lazyRender` together with `renderNow`!");this._render()}_render(){if(this._shouldRender&&(this._shouldRender=!1,this._horizontalScrollbar.render(),this._verticalScrollbar.render(),this._options.useShadows)){const e=this._scrollable.getCurrentScrollPosition(),t=e.scrollTop>0,i=e.scrollLeft>0,s=i?" xterm-shadow-left":"",r=t?" xterm-shadow-top":"",o=i||t?" xterm-shadow-top-left-corner":"";this._leftShadowDomNode.setClassName(`xterm-shadow${s}`),this._topShadowDomNode.setClassName(`xterm-shadow${r}`),this._topLeftShadowDomNode.setClassName(`xterm-shadow${o}${r}${s}`)}}_handleDragStart(){this._isDragging=!0,this._reveal()}_handleDragEnd(){this._isDragging=!1,this._hide()}_handleMouseLeave(e){this._mouseIsOver=!1,this._hide()}_handleMouseOver(e){this._mouseIsOver=!0,this._reveal()}_reveal(){this._verticalScrollbar.beginReveal(),this._horizontalScrollbar.beginReveal(),this._scheduleHide()}_hide(){this._mouseIsOver||this._isDragging||(this._verticalScrollbar.beginHide(),this._horizontalScrollbar.beginHide())}_scheduleHide(){this._mouseIsOver||this._isDragging||this._hideTimeout.cancelAndSet(()=>this._hide(),500)}}t.SmoothScrollableElement=b},9594(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._arrowPointerDown(e))),this._register(c.addStandardDisposableListener(this.domNode,c.eventType.POINTER_DOWN,e=>this._arrowPointerDown(e))),this._pointerdownRepeatTimer=this._register(new c.WindowIntervalTimer),this._pointerdownScheduleRepeatTimer=this._register(new l.TimeoutTimer)}_arrowPointerDown(e){e.target&&e.target instanceof Element&&(this._handleActivate(),this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancelAndSet(()=>{this._pointerdownRepeatTimer.cancelAndSet(()=>this._handleActivate(),1e3/24,c.getWindow(e))},200),this._pointerMoveMonitor.startMonitoring(e.target,e.pointerId,e.buttons,e=>{},()=>{this._pointerdownRepeatTimer.cancel(),this._pointerdownScheduleRepeatTimer.cancel()}),e.preventDefault())}}t.ScrollbarArrow=d},1270(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ScrollbarState=void 0;class i{constructor(e,t,i,s,r,o){this._scrollbarSize=Math.round(t),this._oppositeScrollbarSize=Math.round(i),this._arrowSize=Math.round(e),this._visibleSize=s,this._scrollSize=r,this._scrollPosition=o,this._computedAvailableSize=0,this._computedIsNeeded=!1,this._computedSliderSize=0,this._computedSliderRatio=0,this._computedSliderPosition=0,this._refreshComputedValues()}clone(){return new i(this._arrowSize,this._scrollbarSize,this._oppositeScrollbarSize,this._visibleSize,this._scrollSize,this._scrollPosition)}setVisibleSize(e){const t=Math.round(e);return this._visibleSize!==t&&(this._visibleSize=t,this._refreshComputedValues(),!0)}setScrollSize(e){const t=Math.round(e);return this._scrollSize!==t&&(this._scrollSize=t,this._refreshComputedValues(),!0)}setScrollPosition(e){const t=Math.round(e);return this._scrollPosition!==t&&(this._scrollPosition=t,this._refreshComputedValues(),!0)}setScrollbarSize(e){this._scrollbarSize=Math.round(e)}setArrowSize(e){const t=Math.round(e);this._arrowSize!==t&&(this._arrowSize=t,this._refreshComputedValues())}setOppositeScrollbarSize(e){this._oppositeScrollbarSize=Math.round(e)}static _computeValues(e,t,i,s,r){const o=Math.max(0,i-e),n=Math.max(0,o-2*t),a=s>0&&s>i;if(!a)return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(n),computedSliderRatio:0,computedSliderPosition:0};const h=Math.round(Math.max(20,Math.floor(i*n/s))),l=(n-h)/(s-i),c=r*l;return{computedAvailableSize:Math.round(o),computedIsNeeded:a,computedSliderSize:Math.round(h),computedSliderRatio:l,computedSliderPosition:Math.round(c)}}_refreshComputedValues(){const e=i._computeValues(this._oppositeScrollbarSize,this._arrowSize,this._visibleSize,this._scrollSize,this._scrollPosition);this._computedAvailableSize=e.computedAvailableSize,this._computedIsNeeded=e.computedIsNeeded,this._computedSliderSize=e.computedSliderSize,this._computedSliderRatio=e.computedSliderRatio,this._computedSliderPosition=e.computedSliderPosition}getArrowSize(){return this._arrowSize}getScrollPosition(){return this._scrollPosition}getRectangleLargeSize(){return this._computedAvailableSize}getRectangleSmallSize(){return this._scrollbarSize}isNeeded(){return this._computedIsNeeded}getSliderSize(){return this._computedSliderSize}getSliderPosition(){return this._computedSliderPosition}getDesiredScrollPositionFromOffset(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize-this._computedSliderSize/2;return Math.round(t/this._computedSliderRatio)}getDesiredScrollPositionFromOffsetPaged(e){if(!this._computedIsNeeded)return 0;const t=e-this._arrowSize;let i=this._scrollPosition;return t{this._domNode?.setClassName(this._visibleClassName)},0))}_hide(e){this._revealTimer.cancel(),this._isVisible&&(this._isVisible=!1,this._domNode?.setClassName(this._invisibleClassName+(e?" xterm-fade":"")))}}t.ScrollbarVisibilityController=o},2650(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;n{s||(s=!0,this._remove(i))}}_remove(e){if(e.prev!==_.Undefined&&e.next!==_.Undefined){const t=e.prev;t.next=e.next,e.next.prev=t}else e.prev===_.Undefined&&e.next===_.Undefined?(this._first=_.Undefined,this._last=_.Undefined):e.next===_.Undefined?(this._last=this._last.prev,this._last.next=_.Undefined):e.prev===_.Undefined&&(this._first=this._first.next,this._first.prev=_.Undefined)}*[Symbol.iterator](){let e=this._first;for(;e!==_.Undefined;)yield e.element,e=e.next}}var f;!function(e){e.TAP="-xterm-gesturetap",e.CHANGE="-xterm-gesturechange",e.START="-xterm-gesturestart",e.END="-xterm-gesturesend",e.CONTEXT_MENU="-xterm-gesturecontextmenu"}(f||(t.EventType=f={}));class p extends l.Disposable{constructor(){super(),this._dispatched=!1,this._targets=new u,this._ignoreTargets=new u,this._activeTouches={},this._handle=null,this._lastSetTapCountTime=0;const e=c;this._register(h.addDisposableListener(e.document,"touchstart",e=>this._handleTouchStart(e),{passive:!1})),this._register(h.addDisposableListener(e.document,"touchend",t=>this._handleTouchEnd(e,t))),this._register(h.addDisposableListener(e.document,"touchmove",e=>this._handleTouchMove(e),{passive:!1}))}static addTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._targets.push(e);return(0,l.toDisposable)(t)}static ignoreTarget(e){if(!p.isTouchDevice())return l.Disposable.None;p._instance||(p._instance=new p);const t=p._instance._ignoreTargets.push(e);return(0,l.toDisposable)(t)}static isTouchDevice(){return"ontouchstart"in c||navigator.maxTouchPoints>0}dispose(){this._handle&&(this._handle.dispose(),this._handle=null),super.dispose()}_handleTouchStart(e){const t=Date.now();this._handle&&(this._handle.dispose(),this._handle=null);for(let i=0,s=e.targetTouches.length;i=p._holdDelay&&Math.abs(n.initialPageX-d(n.rollingPageX))<30&&Math.abs(n.initialPageY-d(n.rollingPageY))<30){const e=this._newGestureEvent(f.CONTEXT_MENU,n.initialTarget);e.pageX=d(n.rollingPageX),e.pageY=d(n.rollingPageY),this._dispatchEvent(e)}else if(1===s){const t=d(n.rollingPageX),s=d(n.rollingPageY),r=d(n.rollingTimestamps)-n.rollingTimestamps[0],o=t-n.rollingPageX[0],a=s-n.rollingPageY[0],h=[...this._targets].filter(e=>n.initialTarget instanceof Node&&e.contains(n.initialTarget));this._inertia(e,h,i,Math.abs(o)/r,o>0?1:-1,t,Math.abs(a)/r,a>0?1:-1,s)}this._dispatchEvent(this._newGestureEvent(f.END,n.initialTarget)),delete this._activeTouches[o.identifier]}this._dispatched&&(t.preventDefault(),t.stopPropagation(),this._dispatched=!1)}_newGestureEvent(e,t){const i=document.createEvent("CustomEvent");return i.initEvent(e,!1,!0),i.initialTarget=t,i.tapCount=0,i}_dispatchEvent(e){if(e.type===f.TAP){const t=(new Date).getTime();let i;i=t-this._lastSetTapCountTime>p._clearTapCountTime?1:2,this._lastSetTapCountTime=t,e.tapCount=i}else e.type!==f.CHANGE&&e.type!==f.CONTEXT_MENU||(this._lastSetTapCountTime=0);if(e.initialTarget instanceof Node){for(const t of this._ignoreTargets)if(t.contains(e.initialTarget))return;const t=[];for(const i of this._targets)if(i.contains(e.initialTarget)){let s=0,r=e.initialTarget;for(;r&&r!==i;)s++,r=r.parentElement;t.push([s,i])}t.sort((e,t)=>e[0]-t[0]);for(const[,i]of t)i.dispatchEvent(e),this._dispatched=!0}}_inertia(e,t,i,s,r,o,n,a,l){this._handle=h.scheduleAtNextAnimationFrame(e,()=>{const h=Date.now(),c=h-i;let d=0,_=0,u=!0;s+=p._scrollFriction*c,n+=p._scrollFriction*c,s>0&&(u=!1,d=r*s*c),n>0&&(u=!1,_=a*n*c);const v=this._newGestureEvent(f.CHANGE);v.translationX=d,v.translationY=_,t.forEach(e=>e.dispatchEvent(v)),u||this._inertia(e,t,h,s,r,o+d,n,a,l+_)})}_handleTouchMove(e){const t=Date.now();for(let i=0,s=e.changedTouches.length;i3&&(r.rollingPageX.shift(),r.rollingPageY.shift(),r.rollingTimestamps.shift()),r.rollingPageX.push(s.pageX),r.rollingPageY.push(s.pageY),r.rollingTimestamps.push(t)}this._dispatched&&(e.preventDefault(),e.stopPropagation(),this._dispatched=!1)}}t.Gesture=p,p._scrollFriction=-.005,p._holdDelay=700,p._clearTapCountTime=400,n([function(e,t,i){let s=null,r=null;if("function"==typeof i.value?(s="value",r=i.value,0!==r.length&&console.warn("Memoize should only be used in functions with zero parameters")):"function"==typeof i.get&&(s="get",r=i.get),!r||!s)throw new Error("not supported");const o=`$memoize$${t}`;i[s]=function(...e){return this.hasOwnProperty(o)||Object.defineProperty(this,o,{configurable:!1,enumerable:!1,writable:!1,value:r.apply(this,e)}),this[o]}}],p,"isTouchDevice",null)},8997(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.VerticalScrollbar=void 0;const s=i(8501),r=i(1270);class o extends s.AbstractScrollbar{constructor(e,t,i){const s=e.getScrollDimensions(),o=e.getCurrentScrollPosition(),n=t.verticalHasArrows;super({lazyRender:t.lazyRender,host:i,scrollbarState:new r.ScrollbarState(n?t.verticalScrollbarSize:0,2===t.vertical?0:t.verticalScrollbarSize,0,s.height,s.scrollHeight,o.scrollTop),visibility:t.vertical,extraScrollbarClassName:"xterm-vertical",scrollable:e,scrollByPage:t.scrollByPage}),this._arrowScrollDelta=0,this._setArrows(n,t.verticalScrollbarSize),this._createSlider(0,Math.floor((t.verticalScrollbarSize-t.verticalSliderSize)/2),t.verticalSliderSize,void 0)}_updateSlider(e,t){this.slider.setHeight(e),this.slider.setTop(t)}_renderDomNode(e,t){this.domNode.setWidth(t),this.domNode.setHeight(e),this.domNode.setRight(0),this.domNode.setTop(0)}handleScroll(e){return this._shouldRender=this._handleElementScrollSize(e.scrollHeight)||this._shouldRender,this._shouldRender=this._handleElementScrollPosition(e.scrollTop)||this._shouldRender,this._shouldRender=this._handleElementSize(e.height)||this._shouldRender,this._shouldRender}_pointerDownRelativePosition(e,t){return t}_sliderPointerPosition(e){return e.pageY}_sliderOrthogonalPointerPosition(e){return e.pageX}_updateScrollbarSize(e){this.slider.setWidth(e)}writeScrollPosition(e,t){e.scrollTop=t}_arrowScroll(e){const t=this._scrollable.getCurrentScrollPosition();this._scrollable.setScrollPositionNow({scrollTop:t.scrollTop+e})}_setArrows(e,t){if(this._arrowScrollDelta=t,!this._arrowUp||!this._arrowDown){const e=0;this._arrowUp=this._createArrow({className:"xterm-scra xterm-arrow-up",top:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(-this._arrowScrollDelta)}),this._arrowDown=this._createArrow({className:"xterm-scra xterm-arrow-down",bottom:e,left:e,bgWidth:t,bgHeight:t,handleActivate:()=>this._arrowScroll(this._arrowScrollDelta)})}if(this._updateArrowSize(this._arrowUp,t),this._updateArrowSize(this._arrowDown,t),!this._arrowUp||!this._arrowDown)return;const i=e?"":"none";this._arrowUp.bgDomNode.style.display=i,this._arrowUp.domNode.style.display=i,this._arrowDown.bgDomNode.style.display=i,this._arrowDown.domNode.style.display=i}_updateArrowSize(e,t){e&&(e.bgDomNode.style.width=`${t}px`,e.bgDomNode.style.height=`${t}px`,e.domNode.style.width=`${t}px`,e.domNode.style.height=`${t}px`)}updateOptions(e){const t=e.verticalHasArrows?e.verticalScrollbarSize:0;this._scrollbarState.setArrowSize(t),this._setArrows(e.verticalHasArrows,e.verticalScrollbarSize),this.updateScrollbarSize(2===e.vertical?0:e.verticalScrollbarSize),this._scrollbarState.setOppositeScrollbarSize(0),this._visibilityController.setVisibility(e.vertical),this._scrollByPage=e.scrollByPage}}t.VerticalScrollbar=o},7741(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nt(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseover(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_OVER,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}_onmouseleave(e,t){this._register(a.addDisposableListener(e,a.eventType.MOUSE_LEAVE,i=>t(new h.StandardMouseEvent(a.getWindow(e),i))))}}t.Widget=c},5959(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.SelectionModel=void 0,t.SelectionModel=class{constructor(e){this._bufferService=e,this.isSelectAllActive=!1,this.selectionStartLength=0}clearSelection(){this.selectionStart=void 0,this.selectionEnd=void 0,this.isSelectAllActive=!1,this.selectionStartLength=0}get finalSelectionStart(){return this.isSelectAllActive?[0,0]:this.selectionEnd&&this.selectionStart&&this.areSelectionValuesReversed()?this.selectionEnd:this.selectionStart}get finalSelectionEnd(){if(this.isSelectAllActive)return[this._bufferService.cols,this._bufferService.buffer.ybase+this._bufferService.rows-1];if(this.selectionStart){if(!this.selectionEnd||this.areSelectionValuesReversed()){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?e%this._bufferService.cols===0?[this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)-1]:[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[e,this.selectionStart[1]]}if(this.selectionStartLength&&this.selectionEnd[1]===this.selectionStart[1]){const e=this.selectionStart[0]+this.selectionStartLength;return e>this._bufferService.cols?[e%this._bufferService.cols,this.selectionStart[1]+Math.floor(e/this._bufferService.cols)]:[Math.max(e,this.selectionEnd[0]),this.selectionEnd[1]]}return this.selectionEnd}}areSelectionValuesReversed(){const e=this.selectionStart,t=this.selectionEnd;return!(!e||!t)&&(e[1]>t[1]||e[1]===t[1]&&e[0]>t[0])}handleTrim(e){return this.selectionStart&&(this.selectionStart[1]-=e),this.selectionEnd&&(this.selectionEnd[1]-=e),this.selectionEnd&&this.selectionEnd[1]<0?(this.clearSelection(),!0):!!(this.selectionStart&&this.selectionStart[1]<0)&&(this.selectionStart=[0,0],!0)}}},4792(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharSizeService=void 0;const o=i(6501),n=i(4812),a=i(8636);let h=class extends n.Disposable{get hasValidSize(){return this.width>0&&this.height>0}constructor(e,t,i){super(),this._optionsService=i,this.width=0,this.height=0,this._onCharSizeChange=this._register(new a.Emitter),this.onCharSizeChange=this._onCharSizeChange.event;try{this._measureStrategy=this._register(new d(this._optionsService))}catch{this._measureStrategy=this._register(new c(e,t,this._optionsService))}this._register(this._optionsService.onMultipleOptionChange(["fontFamily","fontSize"],()=>this.measure()))}measure(){const e=this._measureStrategy.measure();e.width===this.width&&e.height===this.height||(this.width=e.width,this.height=e.height,this._onCharSizeChange.fire())}};t.CharSizeService=h,t.CharSizeService=h=s([r(2,o.IOptionsService)],h);class l extends n.Disposable{constructor(){super(...arguments),this._result={width:0,height:0}}_validateAndSet(e,t){void 0!==e&&e>0&&void 0!==t&&t>0&&(this._result.width=e,this._result.height=t)}}class c extends l{constructor(e,t,i){super(),this._document=e,this._parentElement=t,this._optionsService=i,this._measureElement=this._document.createElement("span"),this._measureElement.classList.add("xterm-char-measure-element"),this._measureElement.textContent="W".repeat(32),this._measureElement.setAttribute("aria-hidden","true"),this._measureElement.style.whiteSpace="pre",this._measureElement.style.fontKerning="none",this._parentElement.appendChild(this._measureElement)}measure(){return this._measureElement.style.fontFamily=this._optionsService.rawOptions.fontFamily,this._measureElement.style.fontSize=`${this._optionsService.rawOptions.fontSize}px`,this._validateAndSet(Number(this._measureElement.offsetWidth)/32,Number(this._measureElement.offsetHeight)),this._result}}class d extends l{constructor(e){super(),this._optionsService=e,this._canvas=new OffscreenCanvas(100,100),this._ctx=this._canvas.getContext("2d");const t=this._ctx.measureText("W");if(!("width"in t&&"fontBoundingBoxAscent"in t&&"fontBoundingBoxDescent"in t))throw new Error("Required font metrics not supported")}measure(){this._ctx.font=`${this._optionsService.rawOptions.fontSize}px ${this._optionsService.rawOptions.fontFamily}`;const e=this._ctx.measureText("W");return this._validateAndSet(e.width,e.fontBoundingBoxAscent+e.fontBoundingBoxDescent),this._result}}},945(e,t,i){var s,r=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},o=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CharacterJoinerService=t.JoinedCellData=void 0;const n=i(5451),a=i(8938),h=i(3055),l=i(6501);class c extends n.AttributeData{constructor(e,t,i){super(),this.content=0,this.combinedData="",this.fg=e.fg,this.bg=e.bg,this.combinedData=t,this._width=i}isCombined(){return 2097152}getWidth(){return this._width}getChars(){return this.combinedData}getCode(){return 2097151}setFromCharData(e){throw new Error("not implemented")}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}}t.JoinedCellData=c;let d=s=class{constructor(e){this._bufferService=e,this._characterJoiners=[],this._nextCharacterJoinerId=0,this._workCell=new h.CellData}register(e){const t={id:this._nextCharacterJoinerId++,handler:e};return this._characterJoiners.push(t),t.id}deregister(e){for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;t1){const e=this._getJoinedRanges(s,h,n,t,o);for(let t=0;tthis._screenDprMonitor.setWindow(e))),this._register(s.EventUtils.forward(this._screenDprMonitor.onDprChange,this._onDprChange)),this._register((0,r.addDisposableListener)(this._textarea,"focus",()=>this._isFocused=!0)),this._register((0,r.addDisposableListener)(this._textarea,"blur",()=>this._isFocused=!1))}get window(){return this._window}set window(e){this._window!==e&&(this._window=e,this._onWindowChange.fire(this._window))}get dpr(){return this.window.devicePixelRatio}get isFocused(){return void 0===this._cachedIsFocused&&(this._cachedIsFocused=this._isFocused&&this._textarea.ownerDocument.hasFocus(),queueMicrotask(()=>this._cachedIsFocused=void 0)),this._cachedIsFocused}}t.CoreBrowserService=n;class a extends o.Disposable{constructor(e){super(),this._parentWindow=e,this._windowResizeListener=this._register(new o.MutableDisposable),this._onDprChange=this._register(new s.Emitter),this.onDprChange=this._onDprChange.event,this._outerListener=()=>this._setDprAndFireIfDiffers(),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._updateDpr(),this._setWindowResizeListener(),this._register((0,o.toDisposable)(()=>this.clearListener()))}setWindow(e){this._parentWindow=e,this._setWindowResizeListener(),this._setDprAndFireIfDiffers()}_setWindowResizeListener(){this._windowResizeListener.value=(0,r.addDisposableListener)(this._parentWindow,"resize",()=>this._setDprAndFireIfDiffers())}_setDprAndFireIfDiffers(){this._parentWindow.devicePixelRatio!==this._currentDevicePixelRatio&&this._onDprChange.fire(this._parentWindow.devicePixelRatio),this._updateDpr()}_updateDpr(){this._outerListener&&(this._resolutionMediaMatchList?.removeListener(this._outerListener),this._currentDevicePixelRatio=this._parentWindow.devicePixelRatio,this._resolutionMediaMatchList=this._parentWindow.matchMedia(`screen and (resolution: ${this._parentWindow.devicePixelRatio}dppx)`),this._resolutionMediaMatchList.addListener(this._outerListener))}clearListener(){this._resolutionMediaMatchList&&this._outerListener&&(this._resolutionMediaMatchList.removeListener(this._outerListener),this._resolutionMediaMatchList=void 0,this._outerListener=void 0)}}},2136(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.KeyboardService=void 0;const o=i(706),n=i(7241),a=i(9249),h=i(701),l=i(6501);let c=class{constructor(e,t){this._coreService=e,this._optionsService=t}_getWin32InputMode(){return this._win32InputMode??=new a.Win32InputMode,this._win32InputMode}_getKittyKeyboard(){return this._kittyKeyboard??=new n.KittyKeyboard,this._kittyKeyboard}evaluateKeyDown(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!0);const t=this._coreService.kittyKeyboard.flags;return this.useKitty?this._getKittyKeyboard().evaluate(e,t,e.repeat?2:1,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):(0,o.evaluateKeyboardEvent)(e,this._coreService.decPrivateModes.applicationCursorKeys,h.isMac,this._optionsService.rawOptions.macOptionIsMeta)}evaluateKeyUp(e){if(this.useWin32InputMode)return this._getWin32InputMode().evaluateKeyboardEvent(e,!1);const t=this._coreService.kittyKeyboard.flags;return this.useKitty&&2&t?this._getKittyKeyboard().evaluate(e,t,3,h.isMac&&this._optionsService.rawOptions.macOptionIsMeta):void 0}get useKitty(){const e=this._coreService.kittyKeyboard.flags;return!(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard||!n.KittyKeyboard.shouldUseProtocol(e))}get useWin32InputMode(){return!(!this._optionsService.rawOptions.vtExtensions?.win32InputMode||!this._coreService.decPrivateModes.win32InputMode)}};t.KeyboardService=c,t.KeyboardService=c=s([r(0,l.ICoreService),r(1,l.IOptionsService)],c)},9820(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.LinkProviderService=void 0;const s=i(4812);class r extends s.Disposable{constructor(){super(),this.linkProviders=[],this._register((0,s.toDisposable)(()=>this.linkProviders.length=0))}registerLinkProvider(e){return this.linkProviders.push(e),{dispose:()=>{const t=this.linkProviders.indexOf(e);-1!==t&&this.linkProviders.splice(t,1)}}}}t.LinkProviderService=r},8294(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.MouseCoordsService=void 0;const o=i(4159),n=i(5251),a=i(7098);let h=class{constructor(e,t){this._charSizeService=e,this._renderService=t}getCoords(e,t,i,s,r){return(0,n.getCoords)((0,o.getWindow)(t),e,t,i,s,this._charSizeService.hasValidSize,this._renderService.dimensions.css.cell.width,this._renderService.dimensions.css.cell.height,r)}getMouseReportCoords(e,t){const i=(0,n.getCoordsRelativeToElement)((0,o.getWindow)(t),e,t);if(this._charSizeService.hasValidSize)return i[0]=Math.min(Math.max(i[0],0),this._renderService.dimensions.css.canvas.width-1),i[1]=Math.min(Math.max(i[1],0),this._renderService.dimensions.css.canvas.height-1),{col:Math.floor(i[0]/this._renderService.dimensions.css.cell.width),row:Math.floor(i[1]/this._renderService.dimensions.css.cell.height),x:Math.floor(i[0]),y:Math.floor(i[1])}}};t.MouseCoordsService=h,t.MouseCoordsService=h=s([r(0,a.ICharSizeService),r(1,a.IRenderService)],h)},9784(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.AltMouseCursorController=t.MouseService=void 0;const o=i(4159),n=i(6501),a=i(4812),h=i(7098),l=i(2650);let c=class{constructor(e,t,i,s,r,o,n,a,h){this._renderService=e,this._mouseCoordsService=t,this._mouseStateService=i,this._coreService=s,this._bufferService=r,this._optionsService=o,this._selectionService=n,this._logService=a,this._coreBrowserService=h,this._lastEvent=null,this._wheelPartialScroll=0,this._touchScrollAccumulator=0}bindMouse(e,t,i){const{element:s,document:r}=e,n=new a.MutableDisposable,h=new a.MutableDisposable;t(n),t(h);const c={target:e,focus:i,requestedEvents:{mouseup:null,wheel:null,mousedrag:null,mousemove:null},mouseupListener:n,mousedragListener:h},_={mouseup:e=>this._handleMouseUp(c,e),wheel:e=>this._handleWheel(c,e),mousedrag:e=>this._handleMouseDrag(c,e),mousemove:e=>this._handleMouseMove(c,e)};this._altMouseCursor=new d(s,r,()=>this._mouseStateService.areMouseEventsActive&&!!this._optionsService.rawOptions.mouseEventsRequireAlt),t(this._altMouseCursor),t(this._mouseStateService.onProtocolChange(e=>{this._handleProtocolChange(c,_,e)})),t(this._optionsService.onSpecificOptionChange("mouseEventsRequireAlt",()=>{this._syncMouseModeState(s),this._altMouseCursor?.sync()})),this._mouseStateService.activeProtocol=this._mouseStateService.activeProtocol,t((0,o.addDisposableListener)(s,"mousedown",e=>this._handleMouseDown(c,e))),t((0,o.addDisposableListener)(s,"wheel",e=>this._handlePassiveWheel(c,e),{passive:!1})),t(l.Gesture.addTarget(e.screenElement)),t((0,o.addDisposableListener)(e.screenElement,l.EventType.START,()=>this._handleTouchStart())),t((0,o.addDisposableListener)(e.screenElement,l.EventType.CHANGE,e=>this._handleTouchChange(c,e)))}_sendEvent(e,t){const i=this._mouseCoordsService.getMouseReportCoords(t,e.target.screenElement);if(!i)return!1;let s,r;switch(t.overrideType||t.type){case"mousemove":r=32,void 0===t.buttons?(s=3,void 0!==t.button&&(s=t.button<3?t.button:3)):s=1&t.buttons?0:4&t.buttons?1:2&t.buttons?2:3;break;case"mouseup":r=0,s=t.button<3?t.button:3;break;case"mousedown":r=1,s=t.button<3?t.button:3;break;case"wheel":if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;const e=t.deltaY;if(0===e)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return!1;r=e<0?0:1,s=4;break;default:return!1}if(void 0===r||void 0===s||s>4)return!1;if(4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&!t.altKey)return!1;const o=4!==s&&this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive;return this._triggerMouseEvent({col:i.col,row:i.row,x:i.x,y:i.y,button:s,action:r,ctrl:t.ctrlKey,alt:!o&&t.altKey,shift:t.shiftKey})}_handleMouseUp(e,t){this._sendEvent(e,t),t.buttons||(e.mouseupListener.clear(),e.mousedragListener.clear())}_handleWheel(e,t){return this._sendEvent(e,t),t.preventDefault(),t.stopPropagation(),!1}_handleMouseDrag(e,t){t.buttons&&this._sendEvent(e,t)}_handleMouseMove(e,t){t.buttons||this._sendEvent(e,t)}_handleMouseDown(e,t){if(t.preventDefault(),e.focus(),!this._mouseStateService.areMouseEventsActive||this._selectionService.shouldForceSelection(t))return;this._sendEvent(e,t);const{element:i,document:s}=e.target,r=i.ownerDocument??s;e.requestedEvents.mouseup&&(e.mouseupListener.value=(0,o.addDisposableListener)(r,"mouseup",e.requestedEvents.mouseup)),e.requestedEvents.mousedrag&&(e.mousedragListener.value=(0,o.addDisposableListener)(r,"mousemove",e.requestedEvents.mousedrag))}_handlePassiveWheel(e,t){if(!e.requestedEvents.wheel){if(!this._mouseStateService.allowCustomWheelEvent(t))return!1;if(!this._bufferService.buffer.hasScrollback){if(0===t.deltaY)return!1;if(0===this._consumeWheelEvent(t,this._renderService?.dimensions?.device?.cell?.height,this._coreBrowserService?.dpr))return t.preventDefault(),t.stopPropagation(),!1;const e=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(t.deltaY<0?"A":"B");return this._coreService.triggerDataEvent(e,!0),t.preventDefault(),t.stopPropagation(),!1}}}_handleTouchStart(){this._touchScrollAccumulator=0}_handleTouchChange(e,t){t.preventDefault(),t.stopPropagation(),e.requestedEvents.wheel?this._handleTouchScrollAsWheel(e,t):this._bufferService.buffer.hasScrollback?e.target.handleTouchScroll?.(t.translationY):this._handleTouchScrollAsKeys(t)}_handleTouchScrollAsKeys(e){const t=this._renderService?.dimensions.css.cell.height;if(!t)return;this._touchScrollAccumulator-=e.translationY;const i=Math.trunc(this._touchScrollAccumulator/t);if(0===i)return;this._touchScrollAccumulator-=i*t;const s=""+(this._coreService.decPrivateModes.applicationCursorKeys?"O":"[")+(i<0?"A":"B");for(let e=0;e0?1:-1),this._wheelPartialScroll%=1):e.deltaMode===WheelEvent.DOM_DELTA_PAGE&&(r*=this._bufferService.rows),r}_triggerMouseEvent(e){if(e.col<0||e.col>=this._bufferService.cols||e.row<0||e.row>=this._bufferService.rows)return!1;if(4===e.button&&32===e.action)return!1;if(3===e.button&&32!==e.action)return!1;if(4!==e.button&&(2===e.action||3===e.action))return!1;if(e.col++,e.row++,32===e.action&&this._lastEvent&&this._equalEvents(this._lastEvent,e,this._mouseStateService.isPixelEncoding))return!1;if(!this._mouseStateService.restrictMouseEvent(e))return!1;const t=this._mouseStateService.encodeMouseEvent(e);return t&&(this._mouseStateService.isDefaultEncoding?this._coreService.triggerBinaryEvent(t):this._coreService.triggerDataEvent(t,!0)),this._lastEvent=e,!0}_explainEvents(e){return{down:!!(1&e),up:!!(2&e),drag:!!(4&e),move:!!(8&e),wheel:!!(16&e)}}_equalEvents(e,t,i){if(i){if(e.x!==t.x)return!1;if(e.y!==t.y)return!1}else{if(e.col!==t.col)return!1;if(e.row!==t.row)return!1}return e.button===t.button&&e.action===t.action&&e.ctrl===t.ctrl&&e.alt===t.alt&&e.shift===t.shift}};t.MouseService=c,t.MouseService=c=s([r(0,h.IRenderService),r(1,h.IMouseCoordsService),r(2,n.IMouseStateService),r(3,n.ICoreService),r(4,n.IBufferService),r(5,n.IOptionsService),r(6,h.ISelectionService),r(7,n.ILogService),r(8,h.ICoreBrowserService)],c);class d{constructor(e,t,i){this._element=e,this._document=t,this._isActive=i,this._listeners=new a.MutableDisposable}dispose(){this._listeners.dispose()}sync(){if(this._listeners.clear(),!this._isActive())return;const e=new a.DisposableStore,t=e=>this.syncFromModifier(e);e.add((0,o.addDisposableListener)(this._document,"keydown",t)),e.add((0,o.addDisposableListener)(this._document,"keyup",t)),e.add((0,o.addDisposableListener)(this._element,"mousemove",t));const i=this._element.ownerDocument?.defaultView;i&&e.add((0,o.addDisposableListener)(i,"blur",()=>{this._isActive()&&this.resetClass()})),this._listeners.value=e}resetClass(){this._updateClass(!1)}syncFromModifier(e){this._isActive()&&this._updateClass(e.getModifierState("Alt"))}_updateClass(e){e?this._element.classList.add("enable-mouse-events"):this._element.classList.remove("enable-mouse-events")}}t.AltMouseCursorController=d},5783(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.RenderService=void 0;const o=i(4852),n=i(7098),a=i(4812),h=i(6168),l=i(6501),c=i(8636);let d=class extends a.Disposable{get dimensions(){return this._renderer.value.dimensions}constructor(e,t,i,s,r,n,l,d,u,f){super(),this._rowCount=e,this._optionsService=i,this._logService=s,this._charSizeService=r,this._coreService=n,this._coreBrowserService=u,this._renderer=this._register(new a.MutableDisposable),this._observerDisposable=this._register(new a.MutableDisposable),this._isPaused=!1,this._needsFullRefresh=!1,this._isNextRenderRedrawOnly=!0,this._needsSelectionRefresh=!1,this._canvasWidth=0,this._canvasHeight=0,this._selectionState={start:void 0,end:void 0,columnSelectMode:!1},this._onDimensionsChange=this._register(new c.Emitter),this.onDimensionsChange=this._onDimensionsChange.event,this._onRenderedViewportChange=this._register(new c.Emitter),this.onRenderedViewportChange=this._onRenderedViewportChange.event,this._onRender=this._register(new c.Emitter),this.onRender=this._onRender.event,this._onRefreshRequest=this._register(new c.Emitter),this.onRefreshRequest=this._onRefreshRequest.event,this._pausedResizeTask=this._register(new h.DebouncedIdleTask(this._logService)),this._renderDebouncer=new o.RenderDebouncer((e,t)=>this._renderRows(e,t),this._coreBrowserService),this._register(this._renderDebouncer),this._syncOutputHandler=new _(this._coreBrowserService,this._coreService,()=>this._fullRefresh()),this._register((0,a.toDisposable)(()=>this._syncOutputHandler.dispose())),this._register(this._coreBrowserService.onDprChange(()=>this.handleDevicePixelRatioChange())),this._register(d.onResize(()=>this._fullRefresh())),this._register(d.buffers.onBufferActivate(()=>this._renderer.value?.clear())),this._register(this._optionsService.onOptionChange(()=>this._handleOptionsChanged())),this._register(this._charSizeService.onCharSizeChange(()=>this.handleCharSizeChanged())),this._register(l.onDecorationRegistered(()=>this._fullRefresh())),this._register(l.onDecorationRemoved(()=>this._fullRefresh())),this._register(this._optionsService.onMultipleOptionChange(["drawBoldTextInBrightColors","letterSpacing","lineHeight","fontFamily","fontSize","fontWeight","fontWeightBold","minimumContrastRatio","rescaleOverlappingGlyphs"],()=>{this.clear(),this.handleResize(d.cols,d.rows),this._fullRefresh()})),this._register(this._optionsService.onMultipleOptionChange(["cursorBlink","cursorStyle"],()=>this.refreshRows(d.buffer.y,d.buffer.y,void 0,!0))),this._register(f.onChangeColors(()=>this._fullRefresh())),this._registerIntersectionObserver(this._coreBrowserService.window,t),this._register(this._coreBrowserService.onWindowChange(e=>this._registerIntersectionObserver(e,t)))}_registerIntersectionObserver(e,t){if("IntersectionObserver"in e){const i=new e.IntersectionObserver(e=>this._handleIntersectionChange(e[e.length-1]),{threshold:0});this._observerDisposable.value=(0,a.toDisposable)(()=>{this._intersectionObserver?.disconnect(),this._intersectionObserver=void 0}),this._intersectionObserver=i,i.observe(t)}}_handleIntersectionChange(e){this._isPaused=void 0===e.isIntersecting?0===e.intersectionRatio:!e.isIntersecting,this._renderer.value?.handleViewportVisibilityChange?.(!this._isPaused),this._isPaused||this._charSizeService.hasValidSize||this._charSizeService.measure(),!this._isPaused&&this._needsFullRefresh&&(this._pausedResizeTask.flush(),this.refreshRows(0,this._rowCount-1),this._needsFullRefresh=!1)}refreshRows(e,t,i=!1,s=!1){if(this._isPaused)return void(this._needsFullRefresh=!0);if(this._coreService.decPrivateModes.synchronizedOutput)return void this._syncOutputHandler.bufferRows(e,t);const r=this._syncOutputHandler.flush();r&&(e=Math.min(e,r.start),t=Math.max(t,r.end)),s||(this._isNextRenderRedrawOnly=!1),i?this._renderRows(e,t):this._renderDebouncer.refresh(e,t,this._rowCount)}_renderRows(e,t){this._renderer.value&&(this._coreService.decPrivateModes.synchronizedOutput?this._syncOutputHandler.bufferRows(e,t):(e=Math.min(e,this._rowCount-1),t=Math.min(t,this._rowCount-1),this._renderer.value.renderRows(e,t),this._needsSelectionRefresh&&(this._renderer.value.handleSelectionChanged(this._selectionState.start,this._selectionState.end,this._selectionState.columnSelectMode),this._needsSelectionRefresh=!1),this._isNextRenderRedrawOnly||this._onRenderedViewportChange.fire({start:e,end:t}),this._onRender.fire({start:e,end:t}),this._isNextRenderRedrawOnly=!0))}resize(e,t){this._rowCount=t,this._fireOnCanvasResize()}_handleOptionsChanged(){this._renderer.value&&(this.refreshRows(0,this._rowCount-1),this._fireOnCanvasResize())}_fireOnCanvasResize(){this._renderer.value&&(this._renderer.value.dimensions.css.canvas.width===this._canvasWidth&&this._renderer.value.dimensions.css.canvas.height===this._canvasHeight||this._onDimensionsChange.fire(this._renderer.value.dimensions))}hasRenderer(){return!!this._renderer.value}setRenderer(e){this._renderer.value=e,this._renderer.value&&(this._renderer.value.onRequestRedraw(e=>this.refreshRows(e.start,e.end,e.sync,!0)),this._needsSelectionRefresh=!0,this._fullRefresh())}addRefreshCallback(e){return this._renderDebouncer.addRefreshCallback(e)}_fullRefresh(){this._isPaused?this._needsFullRefresh=!0:this.refreshRows(0,this._rowCount-1)}clearTextureAtlas(){this._renderer.value&&(this._renderer.value.clearTextureAtlas?.(),this._fullRefresh())}handleDevicePixelRatioChange(){this._charSizeService.measure(),this._renderer.value&&(this._renderer.value.handleDevicePixelRatioChange(),this.refreshRows(0,this._rowCount-1))}handleResize(e,t){this._renderer.value&&(this._isPaused?this._pausedResizeTask.set(()=>this._renderer.value?.handleResize(e,t)):this._renderer.value.handleResize(e,t),this._fullRefresh())}handleCharSizeChanged(){this._renderer.value?.handleCharSizeChanged()}handleBlur(){this._renderer.value?.handleBlur()}handleFocus(){this._renderer.value?.handleFocus()}handleSelectionChanged(e,t,i){this._selectionState.start=e,this._selectionState.end=t,this._selectionState.columnSelectMode=i,this._renderer.value?.handleSelectionChanged(e,t,i)}handleCursorMove(){this._renderer.value?.handleCursorMove()}clear(){this._renderer.value?.clear()}};t.RenderService=d,t.RenderService=d=s([r(2,l.IOptionsService),r(3,l.ILogService),r(4,n.ICharSizeService),r(5,l.ICoreService),r(6,l.IDecorationService),r(7,l.IBufferService),r(8,n.ICoreBrowserService),r(9,n.IThemeService)],d);class _{constructor(e,t,i){this._coreBrowserService=e,this._coreService=t,this._onTimeout=i,this._start=0,this._end=0,this._isBuffering=!1}bufferRows(e,t){this._isBuffering?(this._start=Math.min(this._start,e),this._end=Math.max(this._end,t)):(this._start=e,this._end=t,this._isBuffering=!0),this._timeout??=this._coreBrowserService.window.setTimeout(()=>{this._timeout=void 0,this._coreService.decPrivateModes.synchronizedOutput=!1,this._onTimeout()},1e3)}flush(){if(void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0),!this._isBuffering)return;const e={start:this._start,end:this._end};return this._isBuffering=!1,e}dispose(){void 0!==this._timeout&&(this._coreBrowserService.window.clearTimeout(this._timeout),this._timeout=void 0)}}},2079(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleMouseMove(e),this._mouseUpListener=e=>this._handleMouseUp(e),this._coreService.onUserInput(()=>{this.hasSelection&&this.clearSelection()}),this._trimListener.value=this._bufferService.buffer.lines.onTrim(e=>this._handleTrim(e)),this._register(this._bufferService.buffers.onBufferActivate(e=>this._handleBufferActivate(e))),this.enable(),this._model=new d.SelectionModel(this._bufferService),this._activeSelectionMode=0,this._register((0,u.toDisposable)(()=>{this._removeMouseDownListeners()})),this._register(this._bufferService.onResize(e=>{e.rowsChanged&&this.clearSelection()}))}reset(){this.clearSelection()}disable(){this.clearSelection(),this._enabled=!1}enable(){this._enabled=!0}get selectionStart(){return this._model.finalSelectionStart}get selectionEnd(){return this._model.finalSelectionEnd}get hasSelection(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;return!(!e||!t||e[0]===t[0]&&e[1]===t[1])}get selectionText(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd;if(!e||!t)return"";const i=this._bufferService.buffer,s=[];if(3===this._activeSelectionMode){if(e[0]===t[0])return"";const r=e[0]e.replace(b," ")).join(f.isWindows?"\r\n":"\n")}clearSelection(){this._model.clearSelection(),this._removeMouseDownListeners(),this.refresh(),this._onSelectionChange.fire()}refresh(e){this._refreshAnimationFrame||(this._refreshAnimationFrame=this._coreBrowserService.window.requestAnimationFrame(()=>this._refresh())),f.isLinux&&e&&this.selectionText.length&&this._onLinuxMouseSelection.fire(this.selectionText)}_refresh(){this._refreshAnimationFrame=void 0,this._onRedrawRequest.fire({start:this._model.finalSelectionStart,end:this._model.finalSelectionEnd,columnSelectMode:3===this._activeSelectionMode})}_isClickInSelection(e){const t=this._getMouseBufferCoords(e),i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!!(i&&s&&t)&&this._areCoordsInSelection(t,i,s)}isCellInSelection(e,t){const i=this._model.finalSelectionStart,s=this._model.finalSelectionEnd;return!(!i||!s)&&this._areCoordsInSelection([e,t],i,s)}_areCoordsInSelection(e,t,i){return e[1]>t[1]&&e[1]=t[0]&&e[0]=t[0]}_selectWordAtCursor(e,t){const i=this._linkifier.currentLink?.link?.range;if(i)return this._model.selectionStart=[i.start.x-1,i.start.y-1],this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols),this._model.selectionEnd=void 0,!0;const s=this._getMouseBufferCoords(e);return!!s&&(this._selectWordAt(s,t),this._model.selectionEnd=void 0,!0)}selectAll(){this._model.isSelectAllActive=!0,this.refresh(),this._onSelectionChange.fire()}selectLines(e,t){this._model.clearSelection(),e=Math.max(e,0),t=Math.min(t,this._bufferService.buffer.lines.length-1),this._model.selectionStart=[0,e],this._model.selectionEnd=[this._bufferService.cols,t],this.refresh(),this._onSelectionChange.fire()}_handleTrim(e){this._model.handleTrim(e)&&this.refresh()}_getMouseBufferCoords(e){const t=this._mouseCoordsService.getCoords(e,this._screenElement,this._bufferService.cols,this._bufferService.rows,!0);if(t)return t[0]--,t[1]--,t[1]+=this._bufferService.buffer.ydisp,t}_getMouseEventScrollAmount(e){let t=(0,l.getCoordsRelativeToElement)(this._coreBrowserService.window,e,this._screenElement)[1];const i=this._renderService.dimensions.css.canvas.height;return t>=0&&t<=i?0:(t>i&&(t-=i),t=Math.min(Math.max(t,-50),50),t/=50,t/Math.abs(t)+Math.round(14*t))}shouldForceSelection(e){return this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive?!e.altKey:f.isMac?e.altKey&&this._optionsService.rawOptions.macOptionClickForcesSelection:e.shiftKey}handleMouseDown(e){if(this._mouseDownTimeStamp=e.timeStamp,!(2===e.button&&this.hasSelection||0!==e.button||this._optionsService.rawOptions.mouseEventsRequireAlt&&this._mouseStateService.areMouseEventsActive&&e.altKey)){if(!this._enabled){if(!this.shouldForceSelection(e))return;e.stopPropagation()}e.preventDefault(),this._dragScrollAmount=0,this._enabled&&e.shiftKey?this._handleIncrementalClick(e):1===e.detail?this._handleSingleClick(e):2===e.detail?this._handleDoubleClick(e):3===e.detail&&this._handleTripleClick(e),this._addMouseDownListeners(),this.refresh(!0)}}_addMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.addEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.addEventListener("mouseup",this._mouseUpListener)),this._dragScrollIntervalTimer=this._coreBrowserService.window.setInterval(()=>this._dragScroll(),50)}_removeMouseDownListeners(){this._screenElement.ownerDocument&&(this._screenElement.ownerDocument.removeEventListener("mousemove",this._mouseMoveListener),this._screenElement.ownerDocument.removeEventListener("mouseup",this._mouseUpListener)),this._coreBrowserService.window.clearInterval(this._dragScrollIntervalTimer),this._dragScrollIntervalTimer=void 0}_handleIncrementalClick(e){this._model.selectionStart&&(this._model.selectionEnd=this._getMouseBufferCoords(e))}_handleSingleClick(e){const t=this.hasSelection;if(this._model.selectionStartLength=0,this._model.isSelectAllActive=!1,this._activeSelectionMode=this.shouldColumnSelect(e)?3:0,this._model.selectionStart=this._getMouseBufferCoords(e),!this._model.selectionStart)return;this._model.selectionEnd=void 0,t&&this._fireOnSelectionChange(this._model.finalSelectionStart,this._model.finalSelectionEnd,!1);const i=this._bufferService.buffer.lines.get(this._model.selectionStart[1]);i&&i.length!==this._model.selectionStart[0]&&0===i.hasWidth(this._model.selectionStart[0])&&this._model.selectionStart[0]++}_handleDoubleClick(e){this._selectWordAtCursor(e,!0)&&(this._activeSelectionMode=1)}_handleTripleClick(e){const t=this._getMouseBufferCoords(e);t&&(this._activeSelectionMode=2,this._selectLineAt(t[1]))}shouldColumnSelect(e){return(!this._optionsService.rawOptions.mouseEventsRequireAlt||!this._mouseStateService.areMouseEventsActive)&&e.altKey&&!(f.isMac&&this._optionsService.rawOptions.macOptionClickForcesSelection)}_handleMouseMove(e){if(e.stopImmediatePropagation(),!this._model.selectionStart)return;const t=this._model.selectionEnd?[this._model.selectionEnd[0],this._model.selectionEnd[1]]:null;if(this._model.selectionEnd=this._getMouseBufferCoords(e),!this._model.selectionEnd)return void this.refresh(!0);2===this._activeSelectionMode?this._model.selectionEnd[1]0?this._model.selectionEnd[0]=this._bufferService.cols:this._dragScrollAmount<0&&(this._model.selectionEnd[0]=0));const i=this._bufferService.buffer;if(this._model.selectionEnd[1]0?(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=this._bufferService.cols),this._model.selectionEnd[1]=Math.min(e.ydisp+this._bufferService.rows-1,e.lines.length-1)):(3!==this._activeSelectionMode&&(this._model.selectionEnd[0]=0),this._model.selectionEnd[1]=e.ydisp),this.refresh()}}_handleMouseUp(e){const t=e.timeStamp-this._mouseDownTimeStamp;if(this._removeMouseDownListeners(),this.selectionText.length<=1&&t<500&&e.altKey&&this._optionsService.rawOptions.altClickMovesCursor){if(this._bufferService.buffer.ybase===this._bufferService.buffer.ydisp){const t=this._mouseCoordsService.getCoords(e,this._element,this._bufferService.cols,this._bufferService.rows,!1);if(t&&void 0!==t[0]&&void 0!==t[1]){const e=(0,c.moveToCellSequence)(t[0]-1,t[1]-1,this._bufferService,this._coreService.decPrivateModes.applicationCursorKeys);this._coreService.triggerDataEvent(e,!0)}}}else this._fireEventIfSelectionChanged()}_fireEventIfSelectionChanged(){const e=this._model.finalSelectionStart,t=this._model.finalSelectionEnd,i=!(!e||!t||e[0]===t[0]&&e[1]===t[1]);i?e&&t&&(this._oldSelectionStart&&this._oldSelectionEnd&&e[0]===this._oldSelectionStart[0]&&e[1]===this._oldSelectionStart[1]&&t[0]===this._oldSelectionEnd[0]&&t[1]===this._oldSelectionEnd[1]||this._fireOnSelectionChange(e,t,i)):this._oldHasSelection&&this._fireOnSelectionChange(e,t,i)}_fireOnSelectionChange(e,t,i){this._oldSelectionStart=e,this._oldSelectionEnd=t,this._oldHasSelection=i,this._onSelectionChange.fire()}_handleBufferActivate(e){this.clearSelection(),this._trimListener.value=e.activeBuffer.lines.onTrim(e=>this._handleTrim(e))}_convertViewportColToCharacterIndex(e,t){let i=t;for(let s=0;t>=s;s++){const r=e.loadCell(s,this._workCell).getChars().length;0===this._workCell.getWidth()?i--:r>1&&t!==s&&(i+=r-1)}return i}setSelection(e,t,i){this._model.clearSelection(),this._removeMouseDownListeners(),this._model.selectionStart=[e,t],this._model.selectionStartLength=i,this.refresh(),this._fireEventIfSelectionChanged()}rightClickSelect(e){this._isClickInSelection(e)||(this._selectWordAtCursor(e,!1)&&this.refresh(!0),this._fireEventIfSelectionChanged())}_getWordAt(e,t,i=!0,s=!0){if(e[0]>=this._bufferService.cols)return;const r=this._bufferService.buffer,o=r.lines.get(e[1]);if(!o)return;const n=r.translateBufferLineToString(e[1],!1);let a=this._convertViewportColToCharacterIndex(o,e[0]),h=a;const l=e[0]-a;let c=0,d=0,_=0,u=0;if(" "===n.charAt(a)){for(;a>0&&" "===n.charAt(a-1);)a--;for(;h1&&(u+=s-1,h+=s-1);t>0&&a>0&&!this._isCharWordSeparator(o.loadCell(t-1,this._workCell));){o.loadCell(t-1,this._workCell);const e=this._workCell.getChars().length;0===this._workCell.getWidth()?(c++,t--):e>1&&(_+=e-1,a-=e-1),a--,t--}for(;i1&&(u+=e-1,h+=e-1),h++,i++}}h++;let f=a+l-c+_,p=Math.min(this._bufferService.cols,h-a+c+d-_-u);if(t||""!==n.slice(a,h).trim()){if(i&&0===f&&32!==o.getCodePoint(0)){const t=r.lines.get(e[1]-1);if(t&&o.isWrapped&&32!==t.getCodePoint(this._bufferService.cols-1)){const t=this._getWordAt([this._bufferService.cols-1,e[1]-1],!1,!0,!1);if(t){const e=this._bufferService.cols-t.start;f-=e,p+=e}}}if(s&&f+p===this._bufferService.cols&&32!==o.getCodePoint(this._bufferService.cols-1)){const t=r.lines.get(e[1]+1);if(t?.isWrapped&&32!==t.getCodePoint(0)){const t=this._getWordAt([0,e[1]+1],!1,!1,!0);t&&(p+=t.length)}}return{start:f,length:p}}}_selectWordAt(e,t){const i=this._getWordAt(e,t);if(i){for(;i.start<0;)i.start+=this._bufferService.cols,e[1]--;this._model.selectionStart=[i.start,e[1]],this._model.selectionStartLength=i.length}}_selectToWordAt(e){const t=this._getWordAt(e,!0);if(t){let i=e[1];for(;t.start<0;)t.start+=this._bufferService.cols,i--;if(!this._model.areSelectionValuesReversed())for(;t.start+t.length>this._bufferService.cols;)t.length-=this._bufferService.cols,i++;this._model.selectionEnd=[this._model.areSelectionValuesReversed()?t.start:t.start+t.length,i]}}_isCharWordSeparator(e){return 0!==e.getWidth()&&this._optionsService.rawOptions.wordSeparator.indexOf(e.getChars())>=0}_selectLineAt(e){const t=this._bufferService.buffer.getWrappedRangeForLine(e),i={start:{x:0,y:t.first},end:{x:this._bufferService.cols-1,y:t.last}};this._model.selectionStart=[0,t.first],this._model.selectionEnd=void 0,this._model.selectionStartLength=(0,p.getRangeLength)(i,this._bufferService.cols)}};t.SelectionService=w,t.SelectionService=w=n([h(3,g.IBufferService),h(4,g.ICoreService),h(5,_.IMouseCoordsService),h(6,g.IOptionsService),h(7,g.IMouseStateService),h(8,_.IRenderService),h(9,_.ICoreBrowserService)],w)},7098(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IKeyboardService=t.ILinkProviderService=t.IThemeService=t.ICharacterJoinerService=t.ISelectionService=t.IRenderService=t.IMouseService=t.IMouseCoordsService=t.ICoreBrowserService=t.ICharSizeService=void 0;const s=i(6201);t.ICharSizeService=(0,s.createDecorator)("CharSizeService"),t.ICoreBrowserService=(0,s.createDecorator)("CoreBrowserService"),t.IMouseCoordsService=(0,s.createDecorator)("MouseCoordsService"),t.IMouseService=(0,s.createDecorator)("MouseService"),t.IRenderService=(0,s.createDecorator)("RenderService"),t.ISelectionService=(0,s.createDecorator)("SelectionService"),t.ICharacterJoinerService=(0,s.createDecorator)("CharacterJoinerService"),t.IThemeService=(0,s.createDecorator)("ThemeService"),t.ILinkProviderService=(0,s.createDecorator)("LinkProviderService"),t.IKeyboardService=(0,s.createDecorator)("KeyboardService")},9078(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.ThemeService=void 0;const o=i(7174),n=i(9302),a=i(4103),h=i(4812),l=i(6501),c=i(8636),d=a.css.toColor("#ffffff"),_=a.css.toColor("#000000"),u=a.css.toColor("#ffffff"),f=_,p={css:"rgba(255, 255, 255, 0.3)",rgba:4294967117},v=d;let g=class extends h.Disposable{get colors(){return this._colors}constructor(e){super(),this._optionsService=e,this._contrastCache=new o.ColorContrastCache,this._halfContrastCache=new o.ColorContrastCache,this._onChangeColors=this._register(new c.Emitter),this.onChangeColors=this._onChangeColors.event,this._colors={foreground:d,background:_,cursor:u,cursorAccent:f,selectionForeground:void 0,selectionBackgroundTransparent:p,selectionBackgroundOpaque:a.color.blend(_,p),selectionInactiveBackgroundTransparent:p,selectionInactiveBackgroundOpaque:a.color.blend(_,p),scrollbarSliderBackground:a.color.opacity(d,.2),scrollbarSliderHoverBackground:a.color.opacity(d,.4),scrollbarSliderActiveBackground:a.color.opacity(d,.5),overviewRulerBorder:d,ansi:n.DEFAULT_ANSI_COLORS.slice(),contrastCache:this._contrastCache,halfContrastCache:this._halfContrastCache},this._updateRestoreColors(),this._setTheme(this._optionsService.rawOptions.theme),this._register(this._optionsService.onSpecificOptionChange("minimumContrastRatio",()=>this._contrastCache.clear())),this._register(this._optionsService.onSpecificOptionChange("theme",()=>this._setTheme(this._optionsService.rawOptions.theme)))}_setTheme(e={}){const t=this._colors;if(t.foreground=m(e.foreground,d),t.background=m(e.background,_),t.cursor=a.color.blend(t.background,m(e.cursor,u)),t.cursorAccent=a.color.blend(t.background,m(e.cursorAccent,f)),t.selectionBackgroundTransparent=m(e.selectionBackground,p),t.selectionBackgroundOpaque=a.color.blend(t.background,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundTransparent=m(e.selectionInactiveBackground,t.selectionBackgroundTransparent),t.selectionInactiveBackgroundOpaque=a.color.blend(t.background,t.selectionInactiveBackgroundTransparent),t.selectionForeground=e.selectionForeground?m(e.selectionForeground,a.NULL_COLOR):void 0,t.selectionForeground===a.NULL_COLOR&&(t.selectionForeground=void 0),a.color.isOpaque(t.selectionBackgroundTransparent)){const e=.3;t.selectionBackgroundTransparent=a.color.opacity(t.selectionBackgroundTransparent,e)}if(a.color.isOpaque(t.selectionInactiveBackgroundTransparent)){const e=.3;t.selectionInactiveBackgroundTransparent=a.color.opacity(t.selectionInactiveBackgroundTransparent,e)}if(t.scrollbarSliderBackground=m(e.scrollbarSliderBackground,a.color.opacity(t.foreground,.2)),t.scrollbarSliderHoverBackground=m(e.scrollbarSliderHoverBackground,a.color.opacity(t.foreground,.4)),t.scrollbarSliderActiveBackground=m(e.scrollbarSliderActiveBackground,a.color.opacity(t.foreground,.5)),t.overviewRulerBorder=m(e.overviewRulerBorder,v),t.ansi=n.DEFAULT_ANSI_COLORS.slice(),t.ansi[0]=m(e.black,n.DEFAULT_ANSI_COLORS[0]),t.ansi[1]=m(e.red,n.DEFAULT_ANSI_COLORS[1]),t.ansi[2]=m(e.green,n.DEFAULT_ANSI_COLORS[2]),t.ansi[3]=m(e.yellow,n.DEFAULT_ANSI_COLORS[3]),t.ansi[4]=m(e.blue,n.DEFAULT_ANSI_COLORS[4]),t.ansi[5]=m(e.magenta,n.DEFAULT_ANSI_COLORS[5]),t.ansi[6]=m(e.cyan,n.DEFAULT_ANSI_COLORS[6]),t.ansi[7]=m(e.white,n.DEFAULT_ANSI_COLORS[7]),t.ansi[8]=m(e.brightBlack,n.DEFAULT_ANSI_COLORS[8]),t.ansi[9]=m(e.brightRed,n.DEFAULT_ANSI_COLORS[9]),t.ansi[10]=m(e.brightGreen,n.DEFAULT_ANSI_COLORS[10]),t.ansi[11]=m(e.brightYellow,n.DEFAULT_ANSI_COLORS[11]),t.ansi[12]=m(e.brightBlue,n.DEFAULT_ANSI_COLORS[12]),t.ansi[13]=m(e.brightMagenta,n.DEFAULT_ANSI_COLORS[13]),t.ansi[14]=m(e.brightCyan,n.DEFAULT_ANSI_COLORS[14]),t.ansi[15]=m(e.brightWhite,n.DEFAULT_ANSI_COLORS[15]),e.extendedAnsi){const i=Math.min(t.ansi.length-16,e.extendedAnsi.length);for(let s=0;ssetTimeout(t,e))},t.disposableTimeout=function(e,t=0,i){const r=setTimeout(()=>{e(),i&&o.dispose()},t),o=(0,s.toDisposable)(()=>{clearTimeout(r)});return i?.add(o),o};const s=i(4812);t.TimeoutTimer=class{constructor(){this._token=-1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){-1!==this._token&&(clearTimeout(this._token),this._token=-1)}cancelAndSet(e,t){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed TimeoutTimer");this.cancel(),this._token=setTimeout(()=>{this._token=-1,e()},t)}setIfNotSet(e,t){if(this._isDisposed)throw new Error("Calling setIfNotSet on a disposed TimeoutTimer");-1===this._token&&(this._token=setTimeout(()=>{this._token=-1,e()},t))}},t.MicrotaskTimer=class{constructor(){this._isScheduled=!1,this._isDisposed=!1}dispose(){this.cancel(),this._isDisposed=!0}cancel(){this._isScheduled=!1}set(e){if(this._isDisposed)throw new Error("Calling set on a disposed MicrotaskTimer");this._isScheduled||(this._isScheduled=!0,queueMicrotask(()=>{this._isScheduled&&(this._isScheduled=!1,e())}))}},t.IntervalTimer=class{constructor(){this._isDisposed=!1}cancel(){this._disposable?.dispose(),this._disposable=void 0}cancelAndSet(e,t,i=globalThis){if(this._isDisposed)throw new Error("Calling cancelAndSet on a disposed IntervalTimer");this.cancel();const s=i.setInterval(()=>{e()},t);this._disposable={dispose:()=>{i.clearInterval(s),this._disposable=void 0}}}dispose(){this.cancel(),this._isDisposed=!0}}},5639(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CircularList=void 0;const s=i(4812),r=i(8636);class o extends s.Disposable{constructor(e){super(),this._maxLength=e,this.onDeleteEmitter=this._register(new r.Emitter),this.onDelete=this.onDeleteEmitter.event,this.onInsertEmitter=this._register(new r.Emitter),this.onInsert=this.onInsertEmitter.event,this.onTrimEmitter=this._register(new r.Emitter),this.onTrim=this.onTrimEmitter.event,this._array=new Array(this._maxLength),this._startIndex=0,this._length=0}get maxLength(){return this._maxLength}set maxLength(e){if(this._maxLength===e)return;const t=new Array(e);for(let i=0;ithis._length)for(let t=this._length;t=e;t--)this._array[this._getCyclicIndex(t+i.length)]=this._array[this._getCyclicIndex(t)];for(let t=0;tthis._maxLength){const e=this._length+i.length-this._maxLength;this._startIndex+=e,this._length=this._maxLength,this.onTrimEmitter.fire(e)}else this._length+=i.length}trimStart(e){e>this._length&&(e=this._length),this._startIndex+=e,this._length-=e,this.onTrimEmitter.fire(e)}shiftElements(e,t,i){if(!(t<=0)){if(e<0||e>=this._length)throw new Error("start argument out of range");if(e+i<0)throw new Error("Cannot shift elements in list beyond index 0");if(i>0){for(let s=t-1;s>=0;s--)this.set(e+s+i,this.get(e+s));const s=e+t+i-this._length;if(s>0)for(this._length+=s;this._length>this._maxLength;)this._length--,this._startIndex++,this.onTrimEmitter.fire(1)}else for(let s=0;s>>0},e.toColor=function(t,i,s,r){return{css:e.toCss(t,i,s,r),rgba:e.toRgba(t,i,s,r)}}}(n||(t.channels=n={})),function(e){function t(e,t){return o=Math.round(255*t),[i,s,r]=c.toChannels(e.rgba),{css:n.toCss(i,s,r,o),rgba:n.toRgba(i,s,r,o)}}e.blend=function(e,t){if(o=(255&t.rgba)/255,1===o)return{css:t.css,rgba:t.rgba};const a=t.rgba>>24&255,h=t.rgba>>16&255,l=t.rgba>>8&255,c=e.rgba>>24&255,d=e.rgba>>16&255,_=e.rgba>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),{css:n.toCss(i,s,r),rgba:n.toRgba(i,s,r)}},e.isOpaque=function(e){return!(255&~e.rgba)},e.ensureContrastRatio=function(e,t,i){const s=c.ensureContrastRatio(e.rgba,t.rgba,i);if(s)return n.toColor(s>>24&255,s>>16&255,s>>8&255)},e.opaque=function(e){const t=(255|e.rgba)>>>0;return[i,s,r]=c.toChannels(t),{css:n.toCss(i,s,r),rgba:t}},e.opacity=t,e.multiplyOpacity=function(e,i){return o=255&e.rgba,t(e,o*i/255)},e.toColorRGB=function(e){return[e.rgba>>24&255,e.rgba>>16&255,e.rgba>>8&255]}}(a||(t.color=a={})),function(e){let t,a;try{const e=document.createElement("canvas");e.width=1,e.height=1;const i=e.getContext("2d",{willReadFrequently:!0});i&&(t=i,t.globalCompositeOperation="copy",a=t.createLinearGradient(0,0,1,1))}catch{}e.toColor=function(e){if(e.match(/#[\da-f]{3,8}/i))switch(e.length){case 4:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),n.toColor(i,s,r);case 5:return i=parseInt(e.slice(1,2).repeat(2),16),s=parseInt(e.slice(2,3).repeat(2),16),r=parseInt(e.slice(3,4).repeat(2),16),o=parseInt(e.slice(4,5).repeat(2),16),n.toColor(i,s,r,o);case 7:return{css:e,rgba:(parseInt(e.slice(1),16)<<8|255)>>>0};case 9:return{css:e,rgba:parseInt(e.slice(1),16)>>>0}}const h=e.match(/rgba?\(\s*(\d{1,3})\s*,\s*(\d{1,3})\s*,\s*(\d{1,3})\s*(,\s*(0|1|\d?\.(\d+))\s*)?\)/);if(h)return i=parseInt(h[1],10),s=parseInt(h[2],10),r=parseInt(h[3],10),o=Math.round(255*(void 0===h[5]?1:parseFloat(h[5]))),n.toColor(i,s,r,o);if("transparent"===e)return{css:"transparent",rgba:0};if(!t||!a)throw new Error("css.toColor: Unsupported css format");if(t.fillStyle=a,t.fillStyle=e,"string"!=typeof t.fillStyle)throw new Error("css.toColor: Unsupported css format");if(t.fillRect(0,0,1,1),[i,s,r,o]=t.getImageData(0,0,1,1).data,255!==o)throw new Error("css.toColor: Unsupported css format");return{rgba:n.toRgba(i,s,r,o),css:e}}}(h||(t.css=h={})),function(e){function t(e,t,i){const s=e/255,r=t/255,o=i/255;return.2126*(s<=.03928?s/12.92:Math.pow((s+.055)/1.055,2.4))+.7152*(r<=.03928?r/12.92:Math.pow((r+.055)/1.055,2.4))+.0722*(o<=.03928?o/12.92:Math.pow((o+.055)/1.055,2.4))}e.relativeLuminance=function(e){return t(e>>16&255,e>>8&255,255&e)},e.relativeLuminance2=t}(l||(t.rgb=l={})),function(e){function t(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c0||a>0||h>0);)n-=Math.max(0,Math.ceil(.1*n)),a-=Math.max(0,Math.ceil(.1*a)),h-=Math.max(0,Math.ceil(.1*h)),c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));return(n<<24|a<<16|h<<8|255)>>>0}function a(e,t,i){const s=e>>24&255,r=e>>16&255,o=e>>8&255;let n=t>>24&255,a=t>>16&255,h=t>>8&255,c=_(l.relativeLuminance2(n,a,h),l.relativeLuminance2(s,r,o));for(;c>>0}e.blend=function(e,t){if(o=(255&t)/255,1===o)return t;const a=t>>24&255,h=t>>16&255,l=t>>8&255,c=e>>24&255,d=e>>16&255,_=e>>8&255;return i=c+Math.round((a-c)*o),s=d+Math.round((h-d)*o),r=_+Math.round((l-_)*o),n.toRgba(i,s,r)},e.ensureContrastRatio=function(e,i,s){const r=l.relativeLuminance(e>>8),o=l.relativeLuminance(i>>8);if(_(r,o)>8));if(n_(r,l.relativeLuminance(t>>8))?o:t}return o}const n=a(e,i,s),h=_(r,l.relativeLuminance(n>>8));if(h_(r,l.relativeLuminance(o>>8))?n:o}return n}},e.reduceLuminance=t,e.increaseLuminance=a,e.toChannels=function(e){return[e>>24&255,e>>16&255,e>>8&255,255&e]}}(c||(t.rgba=c={}))},5777(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CoreTerminal=void 0;const s=i(6501),r=i(6025),o=i(7276),n=i(9640),a=i(56),h=i(4071),l=i(6478),c=i(7428),d=i(6415),_=i(5746),u=i(5882),f=i(2486),p=i(3562),v=i(8811),g=i(8636),m=i(4812);let S=!1;class b extends m.Disposable{get onScroll(){return this._onScrollApi||(this._onScrollApi=this._register(new g.Emitter),this._onScroll.event(e=>{this._onScrollApi?.fire(e.position)})),this._onScrollApi.event}get cols(){return this._bufferService.cols}get rows(){return this._bufferService.rows}get buffers(){return this._bufferService.buffers}get options(){return this.optionsService.options}set options(e){for(const t in e)this.optionsService.options[t]=e[t]}constructor(e){super(),this._windowsWrappingHeuristics=this._register(new m.MutableDisposable),this._onBinary=this._register(new g.Emitter),this.onBinary=this._onBinary.event,this._onData=this._register(new g.Emitter),this.onData=this._onData.event,this._onLineFeed=this._register(new g.Emitter),this.onLineFeed=this._onLineFeed.event,this._onRender=this._register(new g.Emitter),this.onRender=this._onRender.event,this._onResize=this._register(new g.Emitter),this.onResize=this._onResize.event,this._onWriteParsed=this._register(new g.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._onScroll=this._register(new g.Emitter),this._instantiationService=new r.InstantiationService,this.optionsService=this._register(new a.OptionsService(e)),this._instantiationService.setService(s.IOptionsService,this.optionsService),this._logService=this._register(this._instantiationService.createInstance(o.LogService)),this._instantiationService.setService(s.ILogService,this._logService),this._bufferService=this._register(this._instantiationService.createInstance(n.BufferService)),this._instantiationService.setService(s.IBufferService,this._bufferService),this.coreService=this._register(this._instantiationService.createInstance(h.CoreService)),this._instantiationService.setService(s.ICoreService,this.coreService),this.mouseStateService=this._register(this._instantiationService.createInstance(l.MouseStateService)),this._instantiationService.setService(s.IMouseStateService,this.mouseStateService),this.unicodeService=this._register(this._instantiationService.createInstance(d.UnicodeService)),this.unicodeService.register(new c.UnicodeV6),this._instantiationService.setService(s.IUnicodeService,this.unicodeService),this._charsetService=this._instantiationService.createInstance(_.CharsetService),this._instantiationService.setService(s.ICharsetService,this._charsetService),this._oscLinkService=this._instantiationService.createInstance(v.OscLinkService),this._instantiationService.setService(s.IOscLinkService,this._oscLinkService),this._inputHandler=this._register(new f.InputHandler(this._bufferService,this._charsetService,this.coreService,this._logService,this.optionsService,this._oscLinkService,this.mouseStateService,this.unicodeService)),this._register(g.EventUtils.forward(this._inputHandler.onLineFeed,this._onLineFeed)),this._register(g.EventUtils.forward(this._bufferService.onResize,this._onResize)),this._register(g.EventUtils.forward(this.coreService.onData,this._onData)),this._register(g.EventUtils.forward(this.coreService.onBinary,this._onBinary)),this._register(this.coreService.onRequestScrollToBottom(()=>this.scrollToBottom(!0))),this._register(this.coreService.onUserInput(()=>this._writeBuffer.handleUserInput())),this._register(this.optionsService.onMultipleOptionChange(["windowsPty"],()=>this._handleWindowsPtyOptionChange())),this._register(this._bufferService.onScroll(()=>{this._onScroll.fire({position:this._bufferService.buffer.ydisp}),this._inputHandler.markRangeDirty(this._bufferService.buffer.scrollTop,this._bufferService.buffer.scrollBottom)})),this._writeBuffer=this._register(new p.WriteBuffer((e,t)=>this._inputHandler.parse(e,t))),this._register(g.EventUtils.forward(this._writeBuffer.onWriteParsed,this._onWriteParsed))}write(e,t){this._writeBuffer.write(e,t)}writeSync(e,t){this._logService.logLevel<=s.LogLevelEnum.WARN&&!S&&(this._logService.warn("writeSync is unreliable and will be removed soon."),S=!0),this._writeBuffer.writeSync(e,t)}input(e,t=!0){this.coreService.triggerDataEvent(e,t)}resize(e,t){isNaN(e)||isNaN(t)||(e=Math.max(e,2),t=Math.max(t,1),this._writeBuffer.flushSync(),this._bufferService.resize(e,t))}scroll(e,t=!1){this._bufferService.scroll(e,t)}scrollLines(e,t){this._bufferService.scrollLines(e,t)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}registerEscHandler(e,t){return this._inputHandler.registerEscHandler(e,t)}registerDcsHandler(e,t){return this._inputHandler.registerDcsHandler(e,t)}registerCsiHandler(e,t){return this._inputHandler.registerCsiHandler(e,t)}registerOscHandler(e,t){return this._inputHandler.registerOscHandler(e,t)}registerApcHandler(e,t){return this._inputHandler.registerApcHandler(e,t)}_setup(){this._handleWindowsPtyOptionChange()}reset(){this._inputHandler.reset(),this._bufferService.reset(),this._charsetService.reset(),this.coreService.reset(),this.mouseStateService.reset()}_handleWindowsPtyOptionChange(){let e=!1;const t=this.optionsService.rawOptions.windowsPty;t&&void 0!==t.backend&&void 0!==t.buildNumber&&(e=!!("conpty"===t.backend&&t.buildNumber<21376)),e?this._enableWindowsWrappingHeuristics():this._windowsWrappingHeuristics.clear()}_enableWindowsWrappingHeuristics(){if(!this._windowsWrappingHeuristics.value){const e=[];e.push(this.onLineFeed(u.updateWindowsModeWrappedState.bind(null,this._bufferService))),e.push(this.registerCsiHandler({final:"H"},()=>((0,u.updateWindowsModeWrappedState)(this._bufferService),!1))),this._windowsWrappingHeuristics.value=(0,m.toDisposable)(()=>{for(const t of e)t.dispose()})}}}t.CoreTerminal=b},8636(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EventUtils=t.Emitter=void 0;const s=i(4812);var r;t.Emitter=class{constructor(){this._listeners=[],this._disposed=!1}get event(){return this._event||(this._event=(e,t,i)=>{if(this._disposed)return(0,s.toDisposable)(()=>{});const r={fn:e,thisArgs:t};this._listeners=this._listeners.slice(),this._listeners.push(r);const o=(0,s.toDisposable)(()=>{const e=this._listeners.indexOf(r);-1!==e&&(this._listeners=this._listeners.slice(),this._listeners.splice(e,1))});return i&&(Array.isArray(i)?i.push(o):i.add(o)),o}),this._event}fire(e){if(this._disposed||!this._listeners.length)return;if(1===this._listeners.length)return void this._listeners[0].fn.call(this._listeners[0].thisArgs,e);const t=this._listeners;for(let i=0,s=t.length;it.fire(e))},e.map=function(e,t){return(i,s,r)=>e(e=>i.call(s,t(e)),void 0,r)},e.any=function(...e){return(t,i,r)=>{const o=new s.DisposableStore;for(const s of e)o.add(s(e=>t.call(i,e)));return r&&(Array.isArray(r)?r.push(o):r.add(o)),o}},e.runAndSubscribe=function(e,t,i){return t(i),e(e=>t(e))}}(r||(t.EventUtils=r={}))},2486(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.InputHandler=t.WindowsOptionsReportType=void 0,t.isValidColorIndex=L;const o=i(6760),n=i(6717),a=i(4812),h=i(726),l=i(6107),c=i(8938),d=i(3055),_=i(5451),u=i(6501),f=i(6415),p=i(1346),v=i(9823),g=i(2607),m=i(8693),S=i(8636),b=i(7804),w={"(":0,")":1,"*":2,"+":3,"-":1,".":2};function y(e,t){if(e>24)return t.setWinLines||!1;switch(e){case 1:return!!t.restoreWin;case 2:return!!t.minimizeWin;case 3:return!!t.setWinPosition;case 4:return!!t.setWinSizePixels;case 5:return!!t.raiseWin;case 6:return!!t.lowerWin;case 7:return!!t.refreshWin;case 8:return!!t.setWinSizeChars;case 9:return!!t.maximizeWin;case 10:return!!t.fullscreenWin;case 11:return!!t.getWinState;case 13:return!!t.getWinPosition;case 14:return!!t.getWinSizePixels;case 15:return!!t.getScreenSizePixels;case 16:return!!t.getCellSizePixels;case 18:return!!t.getWinSizeChars;case 19:return!!t.getScreenSizeChars;case 20:return!!t.getIconTitle;case 21:return!!t.getWinTitle;case 22:return!!t.pushTitle;case 23:return!!t.popTitle;case 24:return!!t.setWinLines}return!1}var C;!function(e){e[e.GET_WIN_SIZE_PIXELS=0]="GET_WIN_SIZE_PIXELS",e[e.GET_CELL_SIZE_PIXELS=1]="GET_CELL_SIZE_PIXELS"}(C||(t.WindowsOptionsReportType=C={}));let k=0;class D extends a.Disposable{getAttrData(){return this._curAttrData}constructor(e,t,i,s,r,a,c,d,_=new n.EscapeSequenceParser){super(),this._bufferService=e,this._charsetService=t,this._coreService=i,this._logService=s,this._optionsService=r,this._oscLinkService=a,this._mouseStateService=c,this._unicodeService=d,this._parser=_,this._parseBuffer=new Uint32Array(4096),this._stringDecoder=new h.StringToUtf32,this._utf8Decoder=new h.Utf8ToUtf32,this._windowTitle="",this._iconName="",this._windowTitleStack=[],this._iconNameStack=[],this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone(),this._onRequestBell=this._register(new S.Emitter),this.onRequestBell=this._onRequestBell.event,this._onRequestRefreshRows=this._register(new S.Emitter),this.onRequestRefreshRows=this._onRequestRefreshRows.event,this._onRequestReset=this._register(new S.Emitter),this.onRequestReset=this._onRequestReset.event,this._onRequestSendFocus=this._register(new S.Emitter),this.onRequestSendFocus=this._onRequestSendFocus.event,this._onRequestSyncScrollBar=this._register(new S.Emitter),this.onRequestSyncScrollBar=this._onRequestSyncScrollBar.event,this._onRequestWindowsOptionsReport=this._register(new S.Emitter),this.onRequestWindowsOptionsReport=this._onRequestWindowsOptionsReport.event,this._onA11yChar=this._register(new S.Emitter),this.onA11yChar=this._onA11yChar.event,this._onA11yTab=this._register(new S.Emitter),this.onA11yTab=this._onA11yTab.event,this._onCursorMove=this._register(new S.Emitter),this.onCursorMove=this._onCursorMove.event,this._onLineFeed=this._register(new S.Emitter),this.onLineFeed=this._onLineFeed.event,this._onScroll=this._register(new S.Emitter),this.onScroll=this._onScroll.event,this._onTitleChange=this._register(new S.Emitter),this.onTitleChange=this._onTitleChange.event,this._onColor=this._register(new S.Emitter),this.onColor=this._onColor.event,this._onRequestColorSchemeQuery=this._register(new S.Emitter),this.onRequestColorSchemeQuery=this._onRequestColorSchemeQuery.event,this._parseStack={paused:!1,cursorStartX:0,cursorStartY:0,decodedLength:0,position:0},this._specialColors=[256,257,258],this._register(this._parser),this._dirtyRowTracker=new E(this._bufferService),this._activeBuffer=this._bufferService.buffer,this._register(this._bufferService.buffers.onBufferActivate(e=>this._activeBuffer=e.activeBuffer)),this._parser.setCsiHandlerFallback((e,t)=>{this._logService.debug("Unknown CSI code: ",{identifier:this._parser.identToString(e),params:t.toArray()})}),this._parser.setEscHandlerFallback(e=>{this._logService.debug("Unknown ESC code: ",{identifier:this._parser.identToString(e)})}),this._parser.setExecuteHandlerFallback(e=>{this._logService.debug("Unknown EXECUTE code: ",{code:e})}),this._parser.setOscHandlerFallback((e,t,i)=>{this._logService.debug("Unknown OSC code: ",{identifier:e,action:t,data:i})}),this._parser.setDcsHandlerFallback((e,t,i)=>{"HOOK"===t&&(i=i.toArray()),this._logService.debug("Unknown DCS code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setApcHandlerFallback((e,t,i)=>{this._logService.debug("Unknown APC code: ",{identifier:this._parser.identToString(e),action:t,payload:i})}),this._parser.setPrintHandler((e,t,i)=>this.print(e,t,i)),this._parser.registerCsiHandler({final:"@"},e=>this.insertChars(e)),this._parser.registerCsiHandler({intermediates:" ",final:"@"},e=>this.scrollLeft(e)),this._parser.registerCsiHandler({final:"A"},e=>this.cursorUp(e)),this._parser.registerCsiHandler({intermediates:" ",final:"A"},e=>this.scrollRight(e)),this._parser.registerCsiHandler({final:"B"},e=>this.cursorDown(e)),this._parser.registerCsiHandler({final:"C"},e=>this.cursorForward(e)),this._parser.registerCsiHandler({final:"D"},e=>this.cursorBackward(e)),this._parser.registerCsiHandler({final:"E"},e=>this.cursorNextLine(e)),this._parser.registerCsiHandler({final:"F"},e=>this.cursorPrecedingLine(e)),this._parser.registerCsiHandler({final:"G"},e=>this.cursorCharAbsolute(e)),this._parser.registerCsiHandler({final:"H"},e=>this.cursorPosition(e)),this._parser.registerCsiHandler({final:"I"},e=>this.cursorForwardTab(e)),this._parser.registerCsiHandler({final:"J"},e=>this.eraseInDisplay(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"J"},e=>this.eraseInDisplay(e,!0)),this._parser.registerCsiHandler({final:"K"},e=>this.eraseInLine(e,!1)),this._parser.registerCsiHandler({prefix:"?",final:"K"},e=>this.eraseInLine(e,!0)),this._parser.registerCsiHandler({final:"L"},e=>this.insertLines(e)),this._parser.registerCsiHandler({final:"M"},e=>this.deleteLines(e)),this._parser.registerCsiHandler({final:"P"},e=>this.deleteChars(e)),this._parser.registerCsiHandler({final:"S"},e=>this.scrollUp(e)),this._parser.registerCsiHandler({final:"T"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"X"},e=>this.eraseChars(e)),this._parser.registerCsiHandler({final:"Z"},e=>this.cursorBackwardTab(e)),this._parser.registerCsiHandler({final:"^"},e=>this.scrollDown(e)),this._parser.registerCsiHandler({final:"`"},e=>this.charPosAbsolute(e)),this._parser.registerCsiHandler({final:"a"},e=>this.hPositionRelative(e)),this._parser.registerCsiHandler({final:"b"},e=>this.repeatPrecedingCharacter(e)),this._parser.registerCsiHandler({final:"c"},e=>this.sendDeviceAttributesPrimary(e)),this._parser.registerCsiHandler({prefix:">",final:"c"},e=>this.sendDeviceAttributesSecondary(e)),this._parser.registerCsiHandler({final:"d"},e=>this.linePosAbsolute(e)),this._parser.registerCsiHandler({final:"e"},e=>this.vPositionRelative(e)),this._parser.registerCsiHandler({final:"f"},e=>this.hVPosition(e)),this._parser.registerCsiHandler({final:"g"},e=>this.tabClear(e)),this._parser.registerCsiHandler({final:"h"},e=>this.setMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"h"},e=>this.setModePrivate(e)),this._parser.registerCsiHandler({final:"l"},e=>this.resetMode(e)),this._parser.registerCsiHandler({prefix:"?",final:"l"},e=>this.resetModePrivate(e)),this._parser.registerCsiHandler({final:"m"},e=>this.charAttributes(e)),this._parser.registerCsiHandler({final:"n"},e=>this.deviceStatus(e)),this._parser.registerCsiHandler({prefix:"?",final:"n"},e=>this.deviceStatusPrivate(e)),this._parser.registerCsiHandler({intermediates:"!",final:"p"},e=>this.softReset(e)),this._parser.registerCsiHandler({prefix:">",final:"q"},e=>this.sendXtVersion(e)),this._parser.registerCsiHandler({intermediates:" ",final:"q"},e=>this.setCursorStyle(e)),this._parser.registerCsiHandler({final:"r"},e=>this.setScrollRegion(e)),this._parser.registerCsiHandler({final:"s"},e=>this.saveCursor(e)),this._parser.registerCsiHandler({final:"t"},e=>this.windowOptions(e)),this._parser.registerCsiHandler({final:"u"},e=>this.restoreCursor(e)),this._parser.registerCsiHandler({intermediates:"'",final:"}"},e=>this.insertColumns(e)),this._parser.registerCsiHandler({intermediates:"'",final:"~"},e=>this.deleteColumns(e)),this._parser.registerCsiHandler({intermediates:'"',final:"q"},e=>this.selectProtected(e)),this._parser.registerCsiHandler({intermediates:"$",final:"p"},e=>this.requestMode(e,!0)),this._parser.registerCsiHandler({prefix:"?",intermediates:"$",final:"p"},e=>this.requestMode(e,!1)),this._parser.registerCsiHandler({prefix:"=",final:"u"},e=>this.kittyKeyboardSet(e)),this._parser.registerCsiHandler({prefix:"?",final:"u"},e=>this.kittyKeyboardQuery(e)),this._parser.registerCsiHandler({prefix:">",final:"u"},e=>this.kittyKeyboardPush(e)),this._parser.registerCsiHandler({prefix:"<",final:"u"},e=>this.kittyKeyboardPop(e)),this._parser.setExecuteHandler("",()=>this.bell()),this._parser.setExecuteHandler("\n",()=>this.lineFeed()),this._parser.setExecuteHandler("\v",()=>this.lineFeed()),this._parser.setExecuteHandler("\f",()=>this.lineFeed()),this._parser.setExecuteHandler("\r",()=>this.carriageReturn()),this._parser.setExecuteHandler("\b",()=>this.backspace()),this._parser.setExecuteHandler("\t",()=>this.tab()),this._parser.setExecuteHandler("",()=>this.shiftOut()),this._parser.setExecuteHandler("",()=>this.shiftIn()),this._parser.setExecuteHandler("",()=>this.index()),this._parser.setExecuteHandler("
",()=>this.nextLine()),this._parser.setExecuteHandler("",()=>this.tabSet()),this._parser.registerOscHandler(0,new p.OscHandler(e=>(this.setTitle(e),this.setIconName(e),!0))),this._parser.registerOscHandler(1,new p.OscHandler(e=>this.setIconName(e))),this._parser.registerOscHandler(2,new p.OscHandler(e=>this.setTitle(e))),this._parser.registerOscHandler(4,new p.OscHandler(e=>this.setOrReportIndexedColor(e))),this._parser.registerOscHandler(8,new p.OscHandler(e=>this.setHyperlink(e))),this._parser.registerOscHandler(10,new p.OscHandler(e=>this.setOrReportFgColor(e))),this._parser.registerOscHandler(11,new p.OscHandler(e=>this.setOrReportBgColor(e))),this._parser.registerOscHandler(12,new p.OscHandler(e=>this.setOrReportCursorColor(e))),this._parser.registerOscHandler(104,new p.OscHandler(e=>this.restoreIndexedColor(e))),this._parser.registerOscHandler(110,new p.OscHandler(e=>this.restoreFgColor(e))),this._parser.registerOscHandler(111,new p.OscHandler(e=>this.restoreBgColor(e))),this._parser.registerOscHandler(112,new p.OscHandler(e=>this.restoreCursorColor(e))),this._parser.registerEscHandler({final:"7"},()=>this.saveCursor()),this._parser.registerEscHandler({final:"8"},()=>this.restoreCursor()),this._parser.registerEscHandler({final:"D"},()=>this.index()),this._parser.registerEscHandler({final:"E"},()=>this.nextLine()),this._parser.registerEscHandler({final:"H"},()=>this.tabSet()),this._parser.registerEscHandler({final:"M"},()=>this.reverseIndex()),this._parser.registerEscHandler({final:"="},()=>this.keypadApplicationMode()),this._parser.registerEscHandler({final:">"},()=>this.keypadNumericMode()),this._parser.registerEscHandler({final:"c"},()=>this.fullReset()),this._parser.registerEscHandler({final:"n"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"o"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"|"},()=>this.setgLevel(3)),this._parser.registerEscHandler({final:"}"},()=>this.setgLevel(2)),this._parser.registerEscHandler({final:"~"},()=>this.setgLevel(1)),this._parser.registerEscHandler({intermediates:"%",final:"@"},()=>this.selectDefaultCharset()),this._parser.registerEscHandler({intermediates:"%",final:"G"},()=>this.selectDefaultCharset());for(const e in o.CHARSETS)this._parser.registerEscHandler({intermediates:"(",final:e},()=>this.selectCharset("("+e)),this._parser.registerEscHandler({intermediates:")",final:e},()=>this.selectCharset(")"+e)),this._parser.registerEscHandler({intermediates:"*",final:e},()=>this.selectCharset("*"+e)),this._parser.registerEscHandler({intermediates:"+",final:e},()=>this.selectCharset("+"+e)),this._parser.registerEscHandler({intermediates:"-",final:e},()=>this.selectCharset("-"+e)),this._parser.registerEscHandler({intermediates:".",final:e},()=>this.selectCharset("."+e)),this._parser.registerEscHandler({intermediates:"/",final:e},()=>this.selectCharset("/"+e));this._parser.registerEscHandler({intermediates:"#",final:"8"},()=>this.screenAlignmentPattern()),this._parser.setErrorHandler(e=>(this._logService.error("Parsing error: ",e),e)),this._parser.registerDcsHandler({intermediates:"$",final:"q"},new v.DcsHandler((e,t)=>this.requestStatusString(e,t)))}_preserveStack(e,t,i,s){this._parseStack.paused=!0,this._parseStack.cursorStartX=e,this._parseStack.cursorStartY=t,this._parseStack.decodedLength=i,this._parseStack.position=s}_logSlowResolvingAsync(e){if(this._logService.logLevel<=u.LogLevelEnum.WARN){let t;const i=new Promise((e,i)=>{t=setTimeout(()=>i("#SLOW_TIMEOUT"),5e3)});Promise.race([e,i]).then(()=>{void 0!==t&&clearTimeout(t)},e=>{if(void 0!==t&&clearTimeout(t),"#SLOW_TIMEOUT"!==e)throw e;console.warn("async parser handler taking longer than 5000 ms")})}}_getCurrentLinkId(){return this._curAttrData.extended.urlId}parse(e,t){let i,s=this._activeBuffer.x,r=this._activeBuffer.y,o=0;const n=this._parseStack.paused;if(n){if(i=this._parser.parse(this._parseBuffer,this._parseStack.decodedLength,t))return this._logSlowResolvingAsync(i),i;s=this._parseStack.cursorStartX,r=this._parseStack.cursorStartY,this._parseStack.paused=!1,e.length>131072&&(o=this._parseStack.position+131072)}if(this._logService.logLevel<=u.LogLevelEnum.DEBUG&&this._logService.debug("parsing data "+("string"==typeof e?` "${e}"`:` "${Array.prototype.map.call(e,e=>String.fromCharCode(e)).join("")}"`)),this._logService.logLevel===u.LogLevelEnum.TRACE&&this._logService.trace("parsing data (codes)","string"==typeof e?e.split("").map(e=>e.charCodeAt(0)):e),this._parseBuffer.length131072)for(let t=o;t0&&2===p.getWidth(this._activeBuffer.x-1)&&p.setCellFromCodepoint(this._activeBuffer.x-1,0,1,u);let v=this._parser.precedingJoinState;for(let g=t;ga)if(d){const e=p;let t=this._activeBuffer.x-m;if(this._activeBuffer.x=m,this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData(),!0)):(this._activeBuffer.y>=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!0),p=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y),!p)return;for(m>0&&p instanceof l.BufferLine&&p.copyCellsFrom(e,t,0,m,!1);t=0;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u);continue}if(_&&(p.insertCells(this._activeBuffer.x,r-m,this._activeBuffer.getNullCell(u)),2===p.getWidth(a-1)&&p.setCellFromCodepoint(a-1,c.NULL_CELL_CODE,c.NULL_CELL_WIDTH,u)),p.setCellFromCodepoint(this._activeBuffer.x++,s,r,u),r>0)for(;--r;)p.setCellFromCodepoint(this._activeBuffer.x++,0,0,u)}this._parser.precedingJoinState=v,this._activeBuffer.x0&&0===p.getWidth(this._activeBuffer.x)&&!p.hasContent(this._activeBuffer.x)&&p.setCellFromCodepoint(this._activeBuffer.x,0,1,u),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}registerCsiHandler(e,t){return"t"!==e.final||e.prefix||e.intermediates?this._parser.registerCsiHandler(e,t):this._parser.registerCsiHandler(e,e=>!y(e.params[0],this._optionsService.rawOptions.windowOptions)||t(e))}registerDcsHandler(e,t){return this._parser.registerDcsHandler(e,new v.DcsHandler(t))}registerEscHandler(e,t){return this._parser.registerEscHandler(e,t)}registerOscHandler(e,t){return this._parser.registerOscHandler(e,new p.OscHandler(t))}registerApcHandler(e,t){return this._parser.registerApcHandler(e,new g.ApcHandler(t))}bell(){return this._onRequestBell.fire(),!0}lineFeed(){return this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._optionsService.rawOptions.convertEol&&(this._activeBuffer.x=0),this._activeBuffer.y++,this._activeBuffer.y===this._activeBuffer.scrollBottom+1?(this._activeBuffer.y--,this._bufferService.scroll(this._eraseAttrData())):this._activeBuffer.y>=this._bufferService.rows?this._activeBuffer.y=this._bufferService.rows-1:this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.x>=this._bufferService.cols&&this._activeBuffer.x--,this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._onLineFeed.fire(),!0}carriageReturn(){return this._activeBuffer.x=0,!0}backspace(){if(!this._coreService.decPrivateModes.reverseWraparound)return this._restrictCursor(),this._activeBuffer.x>0&&this._activeBuffer.x--,!0;if(this._restrictCursor(this._bufferService.cols),this._activeBuffer.x>0)this._activeBuffer.x--;else if(0===this._activeBuffer.x&&this._activeBuffer.y>this._activeBuffer.scrollTop&&this._activeBuffer.y<=this._activeBuffer.scrollBottom&&this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y)?.isWrapped){this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y).isWrapped=!1,this._activeBuffer.y--,this._activeBuffer.x=this._bufferService.cols-1;const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+this._activeBuffer.y);e.hasWidth(this._activeBuffer.x)&&!e.hasContent(this._activeBuffer.x)&&this._activeBuffer.x--}return this._restrictCursor(),!0}tab(){if(this._activeBuffer.x>=this._bufferService.cols)return!0;const e=this._activeBuffer.x;return this._activeBuffer.x=this._activeBuffer.nextStop(),this._optionsService.rawOptions.screenReaderMode&&this._onA11yTab.fire(this._activeBuffer.x-e),!0}shiftOut(){return this._charsetService.setgLevel(1),!0}shiftIn(){return this._charsetService.setgLevel(0),!0}_restrictCursor(e=this._bufferService.cols-1){this._activeBuffer.x=Math.min(e,Math.max(0,this._activeBuffer.x)),this._activeBuffer.y=this._coreService.decPrivateModes.origin?Math.min(this._activeBuffer.scrollBottom,Math.max(this._activeBuffer.scrollTop,this._activeBuffer.y)):Math.min(this._bufferService.rows-1,Math.max(0,this._activeBuffer.y)),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_setCursor(e,t){this._dirtyRowTracker.markDirty(this._activeBuffer.y),this._coreService.decPrivateModes.origin?(this._activeBuffer.x=e,this._activeBuffer.y=this._activeBuffer.scrollTop+t):(this._activeBuffer.x=e,this._activeBuffer.y=t),this._restrictCursor(),this._dirtyRowTracker.markDirty(this._activeBuffer.y)}_moveCursor(e,t){this._restrictCursor(),this._setCursor(this._activeBuffer.x+e,this._activeBuffer.y+t)}cursorUp(e){const t=this._activeBuffer.y-this._activeBuffer.scrollTop;return t>=0?this._moveCursor(0,-Math.min(t,e.params[0]||1)):this._moveCursor(0,-(e.params[0]||1)),!0}cursorDown(e){const t=this._activeBuffer.scrollBottom-this._activeBuffer.y;return t>=0?this._moveCursor(0,Math.min(t,e.params[0]||1)):this._moveCursor(0,e.params[0]||1),!0}cursorForward(e){return this._moveCursor(e.params[0]||1,0),!0}cursorBackward(e){return this._moveCursor(-(e.params[0]||1),0),!0}cursorNextLine(e){return this.cursorDown(e),this._activeBuffer.x=0,!0}cursorPrecedingLine(e){return this.cursorUp(e),this._activeBuffer.x=0,!0}cursorCharAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}cursorPosition(e){return this._setCursor(e.length>=2?(e.params[1]||1)-1:0,(e.params[0]||1)-1),!0}charPosAbsolute(e){return this._setCursor((e.params[0]||1)-1,this._activeBuffer.y),!0}hPositionRelative(e){return this._moveCursor(e.params[0]||1,0),!0}linePosAbsolute(e){return this._setCursor(this._activeBuffer.x,(e.params[0]||1)-1),!0}vPositionRelative(e){return this._moveCursor(0,e.params[0]||1),!0}hVPosition(e){return this.cursorPosition(e),!0}tabClear(e){const t=e.params[0];return 0===t?delete this._activeBuffer.tabs[this._activeBuffer.x]:3===t&&(this._activeBuffer.tabs={}),!0}cursorForwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.nextStop();return!0}cursorBackwardTab(e){if(this._activeBuffer.x>=this._bufferService.cols)return!0;let t=e.params[0]||1;for(;t--;)this._activeBuffer.x=this._activeBuffer.prevStop();return!0}selectProtected(e){const t=e.params[0];return 1===t&&(this._curAttrData.bg|=536870912),2!==t&&0!==t||(this._curAttrData.bg&=-536870913),!0}_eraseInBufferLine(e,t,i,s=!1,r=!1){const o=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);o&&(o.replaceCells(t,i,this._activeBuffer.getNullCell(this._eraseAttrData()),r),s&&(o.isWrapped=!1))}_resetBufferLine(e,t=!1){const i=this._activeBuffer.lines.get(this._activeBuffer.ybase+e);i&&(i.fill(this._activeBuffer.getNullCell(this._eraseAttrData()),t),this._bufferService.buffer.clearMarkers(this._activeBuffer.ybase+e),i.isWrapped=!1)}eraseInDisplay(e,t=!1){let i;switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:for(i=this._activeBuffer.y,this._dirtyRowTracker.markDirty(i),this._eraseInBufferLine(i++,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);i=this._bufferService.cols){const e=this._activeBuffer.lines.get(i+1);e&&(e.isWrapped=!1)}for(;i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0);break;case 2:if(this._optionsService.rawOptions.scrollOnEraseInDisplay){for(i=this._bufferService.rows,this._dirtyRowTracker.markRangeDirty(0,i-1);i--;){const e=this._activeBuffer.lines.get(this._activeBuffer.ybase+i);if(e?.getTrimmedLength())break}for(;i>=0;i--)this._bufferService.scroll(this._eraseAttrData())}else{for(i=this._bufferService.rows,this._dirtyRowTracker.markDirty(i-1);i--;)this._resetBufferLine(i,t);this._dirtyRowTracker.markDirty(0)}break;case 3:const e=this._activeBuffer.lines.length-this._bufferService.rows;e>0&&(this._activeBuffer.lines.trimStart(e),this._activeBuffer.ybase=Math.max(this._activeBuffer.ybase-e,0),this._activeBuffer.ydisp=Math.max(this._activeBuffer.ydisp-e,0),this._activeBuffer===this._bufferService.buffers.normal&&(this._bufferService.isUserScrolling=!1),this._onScroll.fire(0))}return!0}eraseInLine(e,t=!1){switch(this._restrictCursor(this._bufferService.cols),e.params[0]){case 0:this._eraseInBufferLine(this._activeBuffer.y,this._activeBuffer.x,this._bufferService.cols,0===this._activeBuffer.x,t);break;case 1:this._eraseInBufferLine(this._activeBuffer.y,0,this._activeBuffer.x+1,!1,t);break;case 2:this._eraseInBufferLine(this._activeBuffer.y,0,this._bufferService.cols,!0,t)}return this._dirtyRowTracker.markDirty(this._activeBuffer.y),!0}insertLines(e){this._restrictCursor();let t=e.params[0]||1;if(this._activeBuffer.y>this._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.ythis._activeBuffer.scrollBottom||this._activeBuffer.y65535?2:1}let h=a;for(let e=1;e0||(this._is("xterm")||this._is("rxvt-unicode")||this._is("screen")?this._coreService.triggerDataEvent("[?1;2c"):this._is("linux")&&this._coreService.triggerDataEvent("[?6c")),!0}sendDeviceAttributesSecondary(e){return e.params[0]>0||(this._is("xterm")?this._coreService.triggerDataEvent("[>0;276;0c"):this._is("rxvt-unicode")?this._coreService.triggerDataEvent("[>85;95;0c"):this._is("linux")?this._coreService.triggerDataEvent(e.params[0]+"c"):this._is("screen")&&this._coreService.triggerDataEvent("[>83;40003;0c")),!0}sendXtVersion(e){return e.params[0]>0||this._coreService.triggerDataEvent(`P>|xterm.js(${b.XTERM_VERSION})\\`),!0}_is(e){return(this._optionsService.rawOptions.termName+"").startsWith(e)}setMode(e){for(let t=0;t(o.triggerDataEvent(`[${t?"":"?"}${e};${i}$y`),!0),_=e=>e?1:2,u=e.params[0];return t?d(u,2===u?4:4===u?_(o.modes.insertMode):12===u?3:20===u?_(c.convertEol):0):1===u?d(u,_(i.applicationCursorKeys)):3===u?d(u,c.windowOptions.setWinLines?80===a?2:132===a?1:0:0):6===u?d(u,_(i.origin)):7===u?d(u,_(i.wraparound)):8===u?d(u,3):9===u?d(u,_("X10"===s)):12===u?d(u,_(c.cursorBlink)):25===u?d(u,_(!o.isCursorHidden)):45===u?d(u,_(i.reverseWraparound)):66===u?d(u,_(i.applicationKeypad)):67===u?d(u,4):1e3===u?d(u,_("VT200"===s)):1002===u?d(u,_("DRAG"===s)):1003===u?d(u,_("ANY"===s)):1004===u?d(u,_(i.sendFocus)):1005===u?d(u,4):1006===u?d(u,_("SGR"===r)):1015===u?d(u,4):1016===u?d(u,_("SGR_PIXELS"===r)):1048===u?d(u,1):47===u||1047===u||1049===u?d(u,_(h===l)):2004===u?d(u,_(i.bracketedPasteMode)):2026===u?d(u,_(i.synchronizedOutput)):9001===u&&this._optionsService.rawOptions.vtExtensions?.win32InputMode?d(u,_(i.win32InputMode)):d(u,0)}_updateAttrColor(e,t,i,s,r){return 2===t?(e|=50331648,e&=-16777216,e|=_.AttributeData.fromColorRGB([i,s,r])):5===t&&(e&=-67108864,e|=33554432|255&i),e}_extractColor(e,t,i){const s=[0,0,-1,0,0,0];let r=0,o=0;do{if(s[o+r]=e.params[t+o],e.hasSubParams(t+o)){const i=e.getSubParams(t+o);let n=0;do{5===s[1]&&(r=1),s[o+n+1+r]=i[n]}while(++n=2||2===s[1]&&o+r>=5)break;s[1]&&(r=1)}while(++o+t5)&&(e=1),t.extended.underlineStyle=e,t.fg|=268435456,0===e&&(t.fg&=-268435457),t.updateExtended()}_processSGR0(e){e.fg=l.DEFAULT_ATTR_DATA.fg,e.bg=l.DEFAULT_ATTR_DATA.bg,e.extended=e.extended.clone(),e.extended.underlineStyle=0,e.extended.underlineColor&=-67108864,e.updateExtended()}charAttributes(e){if(1===e.length&&0===e.params[0])return this._processSGR0(this._curAttrData),!0;const t=e.length;let i;const s=this._curAttrData;for(let r=0;r=30&&i<=37?(s.fg&=-67108864,s.fg|=16777216|i-30):i>=40&&i<=47?(s.bg&=-67108864,s.bg|=16777216|i-40):i>=90&&i<=97?(s.fg&=-67108864,s.fg|=16777224|i-90):i>=100&&i<=107?(s.bg&=-67108864,s.bg|=16777224|i-100):0===i?this._processSGR0(s):1===i?s.fg|=134217728:3===i?s.bg|=67108864:4===i?(s.fg|=268435456,this._processUnderline(e.hasSubParams(r)?e.getSubParams(r)[0]:1,s)):5===i?s.fg|=536870912:7===i?s.fg|=67108864:8===i?s.fg|=1073741824:9===i?s.fg|=2147483648:2===i?s.bg|=134217728:21===i?this._processUnderline(2,s):22===i?(s.fg&=-134217729,s.bg&=-134217729):23===i?s.bg&=-67108865:24===i?(s.fg&=-268435457,this._processUnderline(0,s)):25===i?s.fg&=-536870913:27===i?s.fg&=-67108865:28===i?s.fg&=-1073741825:29===i?s.fg&=2147483647:39===i?(s.fg&=-67108864,s.fg|=16777215&l.DEFAULT_ATTR_DATA.fg):49===i?(s.bg&=-67108864,s.bg|=16777215&l.DEFAULT_ATTR_DATA.bg):38===i||48===i||58===i?r+=this._extractColor(e,r,s):53===i?s.bg|=1073741824:55===i?s.bg&=-1073741825:221===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.fg&=-134217729:222===i&&(this._optionsService.rawOptions.vtExtensions?.kittySgrBoldFaintControl??1)?s.bg&=-134217729:59===i?(s.extended=s.extended.clone(),s.extended.underlineColor=-1,s.updateExtended()):this._logService.debug("Unknown SGR attribute: %d.",i);return!0}deviceStatus(e){switch(e.params[0]){case 5:this._coreService.triggerDataEvent("[0n");break;case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[${e};${t}R`)}return!0}deviceStatusPrivate(e){switch(e.params[0]){case 6:const e=this._activeBuffer.y+1,t=this._activeBuffer.x+1;this._coreService.triggerDataEvent(`[?${e};${t}R`);break;case 15:case 25:case 26:case 53:break;case 996:(this._optionsService.rawOptions.vtExtensions?.colorSchemeQuery??1)&&this._onRequestColorSchemeQuery.fire()}return!0}softReset(e){return this._coreService.isCursorHidden=!1,this._onRequestSyncScrollBar.fire(),this._activeBuffer.scrollTop=0,this._activeBuffer.scrollBottom=this._bufferService.rows-1,this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._coreService.reset(),this._charsetService.reset(),this._activeBuffer.savedX=0,this._activeBuffer.savedY=this._activeBuffer.ybase,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._coreService.decPrivateModes.origin=!1,!0}setCursorStyle(e){const t=0===e.length?1:e.params[0];if(0===t)this._coreService.decPrivateModes.cursorStyle=void 0,this._coreService.decPrivateModes.cursorBlink=void 0;else{switch(t){case 1:case 2:this._coreService.decPrivateModes.cursorStyle="block";break;case 3:case 4:this._coreService.decPrivateModes.cursorStyle="underline";break;case 5:case 6:this._coreService.decPrivateModes.cursorStyle="bar"}const e=t%2==1;this._coreService.decPrivateModes.cursorBlink=e}return!0}setScrollRegion(e){const t=e.params[0]||1;let i;return(e.length<2||(i=e.params[1])>this._bufferService.rows||0===i)&&(i=this._bufferService.rows),i>t&&(this._activeBuffer.scrollTop=t-1,this._activeBuffer.scrollBottom=i-1,this._setCursor(0,0)),!0}windowOptions(e){if(!y(e.params[0],this._optionsService.rawOptions.windowOptions))return!0;const t=e.length>1?e.params[1]:0;switch(e.params[0]){case 14:2!==t&&this._onRequestWindowsOptionsReport.fire(C.GET_WIN_SIZE_PIXELS);break;case 16:this._onRequestWindowsOptionsReport.fire(C.GET_CELL_SIZE_PIXELS);break;case 18:this._bufferService&&this._coreService.triggerDataEvent(`[8;${this._bufferService.rows};${this._bufferService.cols}t`);break;case 22:0!==t&&2!==t||(this._windowTitleStack.push(this._windowTitle),this._windowTitleStack.length>10&&this._windowTitleStack.shift()),0!==t&&1!==t||(this._iconNameStack.push(this._iconName),this._iconNameStack.length>10&&this._iconNameStack.shift());break;case 23:0!==t&&2!==t||this._windowTitleStack.length&&this.setTitle(this._windowTitleStack.pop()),0!==t&&1!==t||this._iconNameStack.length&&this.setIconName(this._iconNameStack.pop())}return!0}saveCursor(e){return this._activeBuffer.savedX=this._activeBuffer.x,this._activeBuffer.savedY=this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.savedCurAttrData.fg=this._curAttrData.fg,this._activeBuffer.savedCurAttrData.bg=this._curAttrData.bg,this._activeBuffer.savedCharset=this._charsetService.charset,this._activeBuffer.savedCharsets=this._charsetService.charsets.slice(),this._activeBuffer.savedGlevel=this._charsetService.glevel,this._activeBuffer.savedOriginMode=this._coreService.decPrivateModes.origin,this._activeBuffer.savedWraparoundMode=this._coreService.decPrivateModes.wraparound,!0}restoreCursor(e){this._activeBuffer.x=this._activeBuffer.savedX||0,this._activeBuffer.y=Math.max(this._activeBuffer.savedY-this._activeBuffer.ybase,0),this._curAttrData.fg=this._activeBuffer.savedCurAttrData.fg,this._curAttrData.bg=this._activeBuffer.savedCurAttrData.bg;for(let e=0;e1;){const e=i.shift(),s=i.shift();if(/^\d+$/.exec(e)){const i=parseInt(e,10);if(L(i))if("?"===s)t.push({type:0,index:i});else{const e=(0,m.parseColor)(s);e&&t.push({type:1,index:i,color:e})}}}return t.length&&this._onColor.fire(t),!0}setHyperlink(e){const t=e.indexOf(";");if(-1===t)return!0;const i=e.slice(0,t).trim(),s=e.slice(t+1);return s?this._createHyperlink(i,s):!i.trim()&&this._finishHyperlink()}_createHyperlink(e,t){this._getCurrentLinkId()&&this._finishHyperlink();const i=e.split(":");let s;const r=i.findIndex(e=>e.startsWith("id="));return-1!==r&&(s=i[r].slice(3)||void 0),this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=this._oscLinkService.registerLink({id:s,uri:t}),this._curAttrData.updateExtended(),!0}_finishHyperlink(){return this._curAttrData.extended=this._curAttrData.extended.clone(),this._curAttrData.extended.urlId=0,this._curAttrData.updateExtended(),!0}_setOrReportSpecialColor(e,t){const i=e.split(";");for(let e=0;e=this._specialColors.length);++e,++t)if("?"===i[e])this._onColor.fire([{type:0,index:this._specialColors[t]}]);else{const s=(0,m.parseColor)(i[e]);s&&this._onColor.fire([{type:1,index:this._specialColors[t],color:s}])}return!0}setOrReportFgColor(e){return this._setOrReportSpecialColor(e,0)}setOrReportBgColor(e){return this._setOrReportSpecialColor(e,1)}setOrReportCursorColor(e){return this._setOrReportSpecialColor(e,2)}restoreIndexedColor(e){if(!e)return this._onColor.fire([{type:2}]),!0;const t=[],i=e.split(";");for(let e=0;e=this._bufferService.rows&&(this._activeBuffer.y=this._bufferService.rows-1),this._restrictCursor(),!0}tabSet(){return this._activeBuffer.tabs[this._activeBuffer.x]=!0,!0}reverseIndex(){if(this._restrictCursor(),this._activeBuffer.y===this._activeBuffer.scrollTop){const e=this._activeBuffer.scrollBottom-this._activeBuffer.scrollTop;this._activeBuffer.lines.shiftElements(this._activeBuffer.ybase+this._activeBuffer.y,e,1),this._activeBuffer.lines.set(this._activeBuffer.ybase+this._activeBuffer.y,this._activeBuffer.getBlankLine(this._eraseAttrData())),this._dirtyRowTracker.markRangeDirty(this._activeBuffer.scrollTop,this._activeBuffer.scrollBottom)}else this._activeBuffer.y--,this._restrictCursor();return!0}fullReset(){return this._parser.reset(),this._onRequestReset.fire(),!0}reset(){this._curAttrData=l.DEFAULT_ATTR_DATA.clone(),this._eraseAttrDataInternal=l.DEFAULT_ATTR_DATA.clone()}_eraseAttrData(){return this._eraseAttrDataInternal.bg&=-67108864,this._eraseAttrDataInternal.bg|=67108863&this._curAttrData.bg,this._eraseAttrDataInternal}setgLevel(e){return this._charsetService.setgLevel(e),!0}screenAlignmentPattern(){const e=new d.CellData;e.content=1<<22|"E".charCodeAt(0),e.fg=this._curAttrData.fg,e.bg=this._curAttrData.bg,this._setCursor(0,0);for(let t=0;t(this._coreService.triggerDataEvent(`${e}\\`),!0))('"q'===e?`P1$r${this._curAttrData.isProtected()?1:0}"q`:'"p'===e?'P1$r61;1"p':"r"===e?`P1$r${i.scrollTop+1};${i.scrollBottom+1}r`:"m"===e?"P1$r0m":" q"===e?`P1$r${{block:2,underline:4,bar:6}[s.cursorStyle]-(s.cursorBlink?1:0)} q`:"P0$r")}markRangeDirty(e,t){this._dirtyRowTracker.markRangeDirty(e,t)}kittyKeyboardSet(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=e.length>1&&e.params[1]||1,s=this._coreService.kittyKeyboard;switch(i){case 1:s.flags=t;break;case 2:s.flags|=t;break;case 3:s.flags&=~t}return!0}kittyKeyboardQuery(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=this._coreService.kittyKeyboard.flags;return this._coreService.triggerDataEvent(`[?${t}u`),!0}kittyKeyboardPush(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=e.params[0]||0,i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;return s.length>=16&&s.shift(),s.push(i.flags),i.flags=t,!0}kittyKeyboardPop(e){if(!this._optionsService.rawOptions.vtExtensions?.kittyKeyboard)return!0;const t=Math.max(1,e.params[0]||1),i=this._coreService.kittyKeyboard,s=this._bufferService.buffer===this._bufferService.buffers.alt?i.altStack:i.mainStack;for(let e=0;e0;e++)i.flags=s.pop();return 0===s.length&&t>0&&(i.flags=0),!0}}t.InputHandler=D;let E=class{constructor(e){this._bufferService=e,this.clearRange()}clearRange(){this.start=this._bufferService.buffer.y,this.end=this._bufferService.buffer.y}markDirty(e){ethis.end&&(this.end=e)}markRangeDirty(e,t){e>t&&(k=e,e=t,t=k),ethis.end&&(this.end=t)}markAllDirty(){this.markRangeDirty(0,this._bufferService.rows-1)}};function L(e){return 0<=e&&e<256}E=s([r(0,u.IBufferService)],E)},4812(e,t){function i(e){return{dispose:e}}function s(e){if(!e)return e;if(Array.isArray(e)){for(const t of e)t.dispose();return[]}return e.dispose(),e}Object.defineProperty(t,"__esModule",{value:!0}),t.MutableDisposable=t.Disposable=t.DisposableStore=void 0,t.toDisposable=i,t.dispose=s,t.combinedDisposable=function(...e){return i(()=>s(e))};class r{constructor(){this._disposables=new Set,this._isDisposed=!1}get isDisposed(){return this._isDisposed}add(e){return this._isDisposed?e.dispose():this._disposables.add(e),e}dispose(){if(!this._isDisposed){this._isDisposed=!0;for(const e of this._disposables)e.dispose();this._disposables.clear()}}clear(){for(const e of this._disposables)e.dispose();this._disposables.clear()}}t.DisposableStore=r;class o{constructor(){this._store=new r}dispose(){this._store.dispose()}_register(e){return this._store.add(e)}}t.Disposable=o,o.None=Object.freeze({dispose(){}}),t.MutableDisposable=class{constructor(){this._isDisposed=!1}get value(){return this._isDisposed?void 0:this._value}set value(e){this._isDisposed||e===this._value||(this._value?.dispose(),this._value=e)}clear(){this.value=void 0}dispose(){this._isDisposed=!0,this._value?.dispose(),this._value=void 0}}},7710(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.FourKeyMap=t.TwoKeyMap=void 0;class i{constructor(){this._data={}}set(e,t,i){this._data[e]||(this._data[e]={}),this._data[e][t]=i}get(e,t){return this._data[e]?this._data[e][t]:void 0}clear(){this._data={}}}t.TwoKeyMap=i,t.FourKeyMap=class{constructor(){this._data=new i}set(e,t,s,r,o){this._data.get(e,t)||this._data.set(e,t,new i),this._data.get(e,t).set(s,r,o)}get(e,t,i,s){return this._data.get(e,t)?.get(i,s)}clear(){this._data.clear()}}},701(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.isChromeOS=t.isLinux=t.isWindows=t.isMac=t.isSafari=t.isLegacyEdge=t.isChrome=t.isFirefox=t.isNode=void 0,t.getZoomFactor=function(e){return 1},t.getSafariVersion=function(){if(!t.isSafari)return 0;const e=i.match(/Version\/(\d+)/);return null===e||e.length<2?0:parseInt(e[1],10)},t.isNode=!("undefined"==typeof process||!("title"in process)||"undefined"!=typeof navigator&&!navigator.userAgent.startsWith("Node.js/"));const i=t.isNode?"node":navigator.userAgent,s=t.isNode?"node":navigator.platform;t.isFirefox=i.includes("Firefox"),t.isChrome=i.includes("Chrome"),t.isLegacyEdge=i.includes("Edge"),t.isSafari=/^((?!chrome|android).)*safari/i.test(i),t.isMac=["Macintosh","MacIntel","MacPPC","Mac68K"].includes(s),t.isWindows=["Windows","Win16","Win32","WinCE"].includes(s),t.isLinux=s.indexOf("Linux")>=0,t.isChromeOS=/\bCrOS\b/.test(i)},3087(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.SortedList=void 0;const s=i(6168);let r=0;t.SortedList=class{constructor(e,t){this._getKey=e,this._array=[],this._insertedValues=[],this._isFlushingInserted=!1,this._deletedIndices=[],this._isFlushingDeleted=!1,this._flushInsertedTask=new s.IdleTaskQueue(t),this._flushDeletedTask=new s.IdleTaskQueue(t)}clear(){this._array.length=0,this._insertedValues.length=0,this._flushInsertedTask.clear(),this._isFlushingInserted=!1,this._deletedIndices.length=0,this._flushDeletedTask.clear(),this._isFlushingDeleted=!1}insert(e){this._flushCleanupDeleted(),0===this._insertedValues.length&&this._flushInsertedTask.enqueue(()=>this._flushInserted()),this._insertedValues.push(e)}_flushInserted(){const e=this._insertedValues.sort((e,t)=>this._getKey(e)-this._getKey(t));let t=0,i=0;const s=new Array(this._array.length+this._insertedValues.length);for(let r=0;r=this._array.length||this._getKey(e[t])<=this._getKey(this._array[i])?(s[r]=e[t],t++):s[r]=this._array[i++];this._array=s,this._insertedValues.length=0}_flushCleanupInserted(){!this._isFlushingInserted&&this._insertedValues.length>0&&this._flushInsertedTask.flush()}delete(e){if(this._flushCleanupInserted(),0===this._array.length)return!1;const t=this._getKey(e);if(void 0===t)return!1;if(r=this._search(t),-1===r)return!1;if(this._getKey(this._array[r])!==t)return!1;do{if(this._array[r]===e)return 0===this._deletedIndices.length&&this._flushDeletedTask.enqueue(()=>this._flushDeleted()),this._deletedIndices.push(r),!0}while(++re-t);let t=0;const i=new Array(this._array.length-e.length);let s=0;for(let r=0;r0&&this._flushDeletedTask.flush()}*getKeyIterator(e){if(this._flushCleanupInserted(),this._flushCleanupDeleted(),0!==this._array.length&&(r=this._search(e),!(r<0||r>=this._array.length)&&this._getKey(this._array[r])===e))do{yield this._array[r]}while(++r=this._array.length)&&this._getKey(this._array[r])===e))do{t(this._array[r])}while(++r=t;){let s=t+i>>1;const r=this._getKey(this._array[s]);if(r>e)i=s-1;else{if(!(r0&&this._getKey(this._array[s-1])===e;)s--;return s}t=s+1}}return t}}},4220(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.LimitedStringBuilder=t.StringBuilder=void 0;class i{constructor(){this._chunks=[],this._length=0}get length(){return this._length}reset(){this._chunks.length=0,this._length=0}append(e){this._chunks.push(e),this._length+=e.length}toString(){return this._chunks.join("")}}t.StringBuilder=i,t.LimitedStringBuilder=class{constructor(e){this._limit=e,this._builder=new i}get length(){return this._builder.length}get limit(){return this._limit}reset(){this._builder.reset()}append(e){return this._builder.append(e),this._builder.length>this._limit&&(this._builder.reset(),!0)}toString(){return this._builder.toString()}}},6168(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DebouncedIdleTask=t.IdleTaskQueue=t.PriorityTaskQueue=void 0;class i{constructor(e){this._tasks=[],this._i=0,this._logService=e}enqueue(e){this._tasks.push(e),this._start()}flush(){for(;this._ii)return r-t<-20&&this._logService.warn(`task queue exceeded allotted deadline by ${Math.abs(Math.round(r-t))}ms`),void this._start();r=i}this.clear()}}class s extends i{_requestCallback(e){return setTimeout(()=>e(this._createDeadline(16)))}_cancelCallback(e){clearTimeout(e)}_createDeadline(e){const t=performance.now()+e;return{timeRemaining:()=>Math.max(0,t-performance.now())}}}t.PriorityTaskQueue=s,t.IdleTaskQueue="requestIdleCallback"in globalThis?class extends i{_requestCallback(e){return requestIdleCallback(e)}_cancelCallback(e){cancelIdleCallback(e)}}:s,t.DebouncedIdleTask=class{constructor(e){this._queue=new t.IdleTaskQueue(e)}set(e){this._queue.clear(),this._queue.enqueue(e)}flush(){this._queue.flush()}dispose(){this._queue.clear()}}},7804(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.XTERM_VERSION=void 0,t.XTERM_VERSION="6.1.0-beta.303"},5882(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.updateWindowsModeWrappedState=function(e){const t=e.buffer.lines.get(e.buffer.ybase+e.buffer.y-1),i=t?.get(e.cols-1),r=e.buffer.lines.get(e.buffer.ybase+e.buffer.y);r&&i&&(r.isWrapped=i[s.CHAR_DATA_CODE_INDEX]!==s.NULL_CELL_CODE&&i[s.CHAR_DATA_CODE_INDEX]!==s.WHITESPACE_CELL_CODE)};const s=i(8938)},5451(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ExtendedAttrs=t.AttributeData=void 0;class i{constructor(){this.fg=0,this.bg=0,this.extended=new s}static toColorRGB(e){return[e>>>16&255,e>>>8&255,255&e]}static fromColorRGB(e){return(255&e[0])<<16|(255&e[1])<<8|255&e[2]}clone(){const e=new i;return e.fg=this.fg,e.bg=this.bg,e.extended=this.extended.clone(),e}isInverse(){return 67108864&this.fg}isBold(){return 134217728&this.fg}isUnderline(){return this.hasExtendedAttrs()&&0!==this.extended.underlineStyle?1:268435456&this.fg}isBlink(){return 536870912&this.fg}isInvisible(){return 1073741824&this.fg}isItalic(){return 67108864&this.bg}isDim(){return 134217728&this.bg}isStrikethrough(){return 2147483648&this.fg}isProtected(){return 536870912&this.bg}isOverline(){return 1073741824&this.bg}getFgColorMode(){return 50331648&this.fg}getBgColorMode(){return 50331648&this.bg}isFgRGB(){return!(50331648&~this.fg)}isBgRGB(){return!(50331648&~this.bg)}isFgPalette(){return 16777216==(50331648&this.fg)||33554432==(50331648&this.fg)}isBgPalette(){return 16777216==(50331648&this.bg)||33554432==(50331648&this.bg)}isFgDefault(){return!(50331648&this.fg)}isBgDefault(){return!(50331648&this.bg)}isAttributeDefault(){return 0===this.fg&&0===this.bg}getFgColor(){switch(50331648&this.fg){case 16777216:case 33554432:return 255&this.fg;case 50331648:return 16777215&this.fg;default:return-1}}getBgColor(){switch(50331648&this.bg){case 16777216:case 33554432:return 255&this.bg;case 50331648:return 16777215&this.bg;default:return-1}}hasExtendedAttrs(){return 268435456&this.bg}updateExtended(){this.extended.isEmpty()?this.bg&=-268435457:this.bg|=268435456}getUnderlineColor(){if(268435456&this.bg&&~this.extended.underlineColor)switch(50331648&this.extended.underlineColor){case 16777216:case 33554432:return 255&this.extended.underlineColor;case 50331648:return 16777215&this.extended.underlineColor;default:return this.getFgColor()}return this.getFgColor()}getUnderlineColorMode(){return 268435456&this.bg&&~this.extended.underlineColor?50331648&this.extended.underlineColor:this.getFgColorMode()}isUnderlineColorRGB(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&~this.extended.underlineColor):this.isFgRGB()}isUnderlineColorPalette(){return 268435456&this.bg&&~this.extended.underlineColor?16777216==(50331648&this.extended.underlineColor)||33554432==(50331648&this.extended.underlineColor):this.isFgPalette()}isUnderlineColorDefault(){return 268435456&this.bg&&~this.extended.underlineColor?!(50331648&this.extended.underlineColor):this.isFgDefault()}getUnderlineStyle(){return 268435456&this.fg?268435456&this.bg?this.extended.underlineStyle:1:0}getUnderlineVariantOffset(){return this.extended.underlineVariantOffset}}t.AttributeData=i;class s{get ext(){return this._urlId?-469762049&this._ext|this.underlineStyle<<26:this._ext}set ext(e){this._ext=e}get underlineStyle(){return this._urlId?5:(469762048&this._ext)>>26}set underlineStyle(e){this._ext&=-469762049,this._ext|=e<<26&469762048}get underlineColor(){return 67108863&this._ext}set underlineColor(e){this._ext&=-67108864,this._ext|=67108863&e}get urlId(){return this._urlId}set urlId(e){this._urlId=e}get underlineVariantOffset(){const e=(3758096384&this._ext)>>29;return e<0?4294967288^e:e}set underlineVariantOffset(e){this._ext&=536870911,this._ext|=e<<29&3758096384}constructor(e=0,t=0){this._ext=0,this._urlId=0,this._ext=e,this._urlId=t}clone(){return new s(this._ext,this._urlId)}isEmpty(){return 0===this.underlineStyle&&0===this._urlId}}t.ExtendedAttrs=s},1073(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Buffer=t.MAX_BUFFER_SIZE=void 0;const s=i(5639),r=i(4812),o=i(6168),n=i(5451),a=i(6107),h=i(732),l=i(3055),c=i(8938),d=i(8158),_=i(6760);t.MAX_BUFFER_SIZE=4294967295;class u extends r.Disposable{constructor(e,t,i,n){super(),this._hasScrollback=e,this._optionsService=t,this._bufferService=i,this._logService=n,this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.tabs={},this.savedY=0,this.savedX=0,this.savedCurAttrData=a.DEFAULT_ATTR_DATA.clone(),this.savedCharset=_.DEFAULT_CHARSET,this.savedCharsets=[],this.savedGlevel=0,this.savedOriginMode=!1,this.savedWraparoundMode=!0,this.markers=[],this._nullCell=l.CellData.fromCharData([0,c.NULL_CELL_CHAR,c.NULL_CELL_WIDTH,c.NULL_CELL_CODE]),this._whitespaceCell=l.CellData.fromCharData([0,c.WHITESPACE_CELL_CHAR,c.WHITESPACE_CELL_WIDTH,c.WHITESPACE_CELL_CODE]),this._isClearing=!1,this._memoryCleanupPosition=0,this._cols=this._bufferService.cols,this._rows=this._bufferService.rows,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops(),this._memoryCleanupQueue=new o.IdleTaskQueue(this._logService),this._register((0,r.toDisposable)(()=>this._memoryCleanupQueue.clear())),this._register((0,r.toDisposable)(()=>this.clearAllMarkers()))}getNullCell(e){return e?(this._nullCell.fg=e.fg,this._nullCell.bg=e.bg,this._nullCell.extended=e.extended):(this._nullCell.fg=0,this._nullCell.bg=0,this._nullCell.extended=new n.ExtendedAttrs),this._nullCell}getWhitespaceCell(e){return e?(this._whitespaceCell.fg=e.fg,this._whitespaceCell.bg=e.bg,this._whitespaceCell.extended=e.extended):(this._whitespaceCell.fg=0,this._whitespaceCell.bg=0,this._whitespaceCell.extended=new n.ExtendedAttrs),this._whitespaceCell}getBlankLine(e,t){return new a.BufferLine(this._bufferService.cols,this.getNullCell(e),t)}get hasScrollback(){return this._hasScrollback&&this.lines.maxLength>this._rows}get isCursorInViewport(){const e=this.ybase+this.y-this.ydisp;return e>=0&&et.MAX_BUFFER_SIZE?t.MAX_BUFFER_SIZE:i}fillViewportRows(e){if(0===this.lines.length){e??=a.DEFAULT_ATTR_DATA;let t=this._rows;for(;t--;)this.lines.push(this.getBlankLine(e))}}clear(){this.ydisp=0,this.ybase=0,this.y=0,this.x=0,this.lines=new s.CircularList(this._getCorrectBufferLength(this._rows)),this.scrollTop=0,this.scrollBottom=this._rows-1,this.setupTabStops()}resize(e,t){const i=this.getNullCell(a.DEFAULT_ATTR_DATA);let s=0;const r=this._getCorrectBufferLength(t);if(r>this.lines.maxLength&&(this.lines.maxLength=r),this.lines.length>0){if(this._cols0&&this.lines.length<=this.ybase+this.y+o+1?(this.ybase--,o++,this.ydisp>0&&this.ydisp--):this.lines.push(new a.BufferLine(e,i,!1)));else for(let e=this._rows;e>t;e--)this.lines.length>t+this.ybase&&(this.lines.length>this.ybase+this.y+1?this.lines.pop():(this.ybase++,this.ydisp++));if(r0&&(this.lines.trimStart(e),this.ybase=Math.max(this.ybase-e,0),this.ydisp=Math.max(this.ydisp-e,0),this.savedY=Math.max(this.savedY-e,0)),this.lines.maxLength=r}this.x=Math.min(this.x,e-1),this.y=Math.min(this.y,t-1),o&&(this.y+=o),this.savedX=Math.min(this.savedX,e-1),this.scrollTop=0}if(this.scrollBottom=t-1,this._isReflowEnabled&&(this._reflow(e,t),this._cols>e))for(let t=0;t0){const e=Math.max(0,this.lines.length-this.ybase-1);this.y=Math.min(this.y,e)}this._memoryCleanupQueue.clear(),s>.1*this.lines.length&&(this._memoryCleanupPosition=0,this._memoryCleanupQueue.enqueue(()=>this._batchedMemoryCleanup()))}_batchedMemoryCleanup(){let e=!0;this._memoryCleanupPosition>=this.lines.length&&(this._memoryCleanupPosition=0,e=!1);let t=0;for(;this._memoryCleanupPosition100)return!0;return e}get _isReflowEnabled(){const e=this._optionsService.rawOptions.windowsPty;return e&&e.buildNumber?this._hasScrollback&&"conpty"===e.backend&&e.buildNumber>=21376:this._hasScrollback}_reflow(e,t){this._cols!==e&&(e>this._cols?this._reflowLarger(e,t):this._reflowSmaller(e,t))}_reflowLarger(e,t){const i=this._optionsService.rawOptions.reflowCursorLine,s=(0,h.reflowLargerGetLinesToRemove)(this.lines,this._cols,e,this.ybase+this.y,this.getNullCell(a.DEFAULT_ATTR_DATA),i);if(s.length>0){const i=(0,h.reflowLargerCreateNewLayout)(this.lines,s);(0,h.reflowLargerApplyNewLayout)(this.lines,i.layout),this._reflowLargerAdjustViewport(e,t,i.countRemoved)}}_reflowLargerAdjustViewport(e,t,i){const s=this.getNullCell(a.DEFAULT_ATTR_DATA);let r=i;for(;r-- >0;)0===this.ybase?(this.y>0&&this.y--,this.lines.length=0;n--){let l=this.lines.get(n);if(!l||!l.isWrapped&&l.getTrimmedLength()<=e)continue;const c=[l];for(;l.isWrapped&&n>0;)l=this.lines.get(--n),c.unshift(l);if(!i){const e=this.ybase+this.y;if(e>=n&&e0&&(r.push({start:n+c.length+o,newLines:p}),o+=p.length),c.push(...p);let v=_.length-1,g=_[v];0===g&&(v--,g=_[v]);let m=c.length-u-1,S=d;for(;m>=0;){const e=Math.min(S,g);if(void 0===c[v])break;if(c[v].copyCellsFrom(c[m],S-e,g-e,e,!0),g-=e,0===g&&(v--,g=_[v]),S-=e,0===S){m--;const e=Math.max(m,0);S=(0,h.getWrappedLineTrimmedLength)(c,e,this._cols)}}for(let t=0;t0;)0===this.ybase?this.y0){const e=[],t=[];for(let e=0;e=0;l--)if(a&&a.start>s+h){for(let e=a.newLines.length-1;e>=0;e--)this.lines.set(l--,a.newLines[e]);l++,e.push({index:s+1,amount:a.newLines.length}),h+=a.newLines.length,a=r[++n]}else this.lines.set(l,t[s--]);let l=0;for(let t=e.length-1;t>=0;t--)e[t].index+=l,this.lines.onInsertEmitter.fire(e[t]),l+=e[t].amount;const c=Math.max(0,i+o-this.lines.maxLength);c>0&&this.lines.onTrimEmitter.fire(c)}}translateBufferLineToString(e,t,i=0,s){const r=this.lines.get(e);return r?r.translateToString(t,i,s):""}getWrappedRangeForLine(e){let t=e,i=e;for(;t>0&&this.lines.get(t).isWrapped;)t--;for(;i+10;);return e>=this._cols?this._cols-1:e<0?0:e}nextStop(e){for(e??=this.x;!this.tabs[++e]&&e=this._cols?this._cols-1:e<0?0:e}clearMarkers(e){this._isClearing=!0;for(let t=0;t{t.line-=e,t.line<0&&t.dispose()})),t.register(this.lines.onInsert(e=>{t.line>=e.index&&(t.line+=e.amount)})),t.register(this.lines.onDelete(e=>{t.line>=e.index&&t.linee.index&&(t.line-=e.amount)})),t.register(t.onDispose(()=>this._removeMarker(t))),t}_removeMarker(e){this._isClearing||this.markers.splice(this.markers.indexOf(e),1)}}t.Buffer=u},6107(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferLine=t.DEFAULT_ATTR_DATA=void 0;const s=i(5451),r=i(3055),o=i(8938),n=i(726);t.DEFAULT_ATTR_DATA=Object.freeze(new s.AttributeData);let a=0;const h=new r.CellData,l=t.DEFAULT_ATTR_DATA.extended.clone();class c{constructor(e,t,i=!1){this.isWrapped=i,this._combined={},this._extendedAttrs={},this._cacheValid=!1,this._cache="",this._cacheTrimmed=!1,this._data=new Uint32Array(3*e);const s=t??r.CellData.fromCharData([0,o.NULL_CELL_CHAR,o.NULL_CELL_WIDTH,o.NULL_CELL_CODE]);for(let t=0;t>22,2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):i]}set(e,t){this._cacheValid=!1,this._data[3*e+1]=t[o.CHAR_DATA_ATTR_INDEX],t[o.CHAR_DATA_CHAR_INDEX].length>1?(this._combined[e]=t[1],this._data[3*e+0]=2097152|e|t[o.CHAR_DATA_WIDTH_INDEX]<<22):this._data[3*e+0]=t[o.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|t[o.CHAR_DATA_WIDTH_INDEX]<<22}getWidth(e){return this._data[3*e+0]>>22}hasWidth(e){return 12582912&this._data[3*e+0]}getFg(e){return this._data[3*e+1]}getBg(e){return this._data[3*e+2]}hasContent(e){return 4194303&this._data[3*e+0]}getCodePoint(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e].charCodeAt(this._combined[e].length-1):2097151&t}isCombined(e){return 2097152&this._data[3*e+0]}getString(e){const t=this._data[3*e+0];return 2097152&t?this._combined[e]:2097151&t?(0,n.stringFromCodePoint)(2097151&t):""}isProtected(e){return 536870912&this._data[3*e+2]}loadCell(e,t){return a=3*e,t.content=this._data[a+0],t.fg=this._data[a+1],t.bg=this._data[a+2],2097152&t.content?t.combinedData=this._combined[e]:t.combinedData="",268435456&t.bg?t.extended=this._extendedAttrs[e]:(l._ext=0,l._urlId=0,t.extended=l),t}setCell(e,t){this._cacheValid=!1,2097152&t.content&&(this._combined[e]=t.combinedData),268435456&t.bg&&(this._extendedAttrs[e]=t.extended),this._data[3*e+0]=t.content,this._data[3*e+1]=t.fg,this._data[3*e+2]=t.bg}setCellFromCodepoint(e,t,i,s){this._cacheValid=!1,268435456&s.bg&&(this._extendedAttrs[e]=s.extended);const r=3*e;this._data[r+0]=t|i<<22,this._data[r+1]=s.fg,this._data[r+2]=s.bg}addCodepointToCell(e,t,i){this._cacheValid=!1;let s=this._data[3*e+0];2097152&s?this._combined[e]+=(0,n.stringFromCodePoint)(t):2097151&s?(this._combined[e]=(0,n.stringFromCodePoint)(2097151&s)+(0,n.stringFromCodePoint)(t),s&=-2097152,s|=2097152):s=t|1<<22,i&&(s&=-12582913,s|=i<<22),this._data[3*e+0]=s}insertCells(e,t,i){if(this._cacheValid=!1,(e%=this.length)&&2===this.getWidth(e-1)&&this.setCellFromCodepoint(e-1,0,1,i),t=0;--i)this.setCell(e+t+i,this.loadCell(e+i,h));for(let s=0;sthis.length){if(this._data.buffer.byteLength>=4*i)this._data=new Uint32Array(this._data.buffer,0,i);else{const e=new Uint32Array(i);e.set(this._data),this._data=e}for(let i=this.length;i=e&&delete this._combined[s]}const s=Object.keys(this._extendedAttrs);for(let t=0;t=e&&delete this._extendedAttrs[i]}}return this.length=e,4*i*2=0;--e)if(4194303&this._data[3*e+0])return e+(this._data[3*e+0]>>22);return 0}getNoBgTrimmedLength(){for(let e=this.length-1;e>=0;--e)if(4194303&this._data[3*e+0]||50331648&this._data[3*e+2])return e+(this._data[3*e+0]>>22);return 0}copyCellsFrom(e,t,i,s,r){this._cacheValid=!1;const o=e._data;if(r)for(let r=s-1;r>=0;r--){for(let e=0;e<3;e++)this._data[3*(i+r)+e]=o[3*(t+r)+e];this._copyCellMapsFrom(e,t+r,i+r)}else for(let r=0;r>22||1}s&&s.push(t);const h=a.join("");return r&&(this._cache=h,this._cacheValid=!0,this._cacheTrimmed=!!e),h}_copyCellMapsFrom(e,t,i){const s=3*t;2097152&e._data[s+0]&&(this._combined[i]=e._combined[t]),268435456&e._data[s+2]&&(this._extendedAttrs[i]=e._extendedAttrs[t])}_copySparseMapsFrom(e){this._combined={},this._extendedAttrs={};for(let t=0;te.end.y)throw new Error(`Buffer range end (${e.end.x}, ${e.end.y}) cannot be before start (${e.start.x}, ${e.start.y})`);return t*(e.end.y-e.start.y)+(e.end.x-e.start.x+1)}},732(e,t){function i(e,t,i){if(t===e.length-1)return e[t].getTrimmedLength();const s=!e[t].hasContent(i-1)&&1===e[t].getWidth(i-1),r=2===e[t+1].getWidth(0);return s&&r?i-1:i}Object.defineProperty(t,"__esModule",{value:!0}),t.reflowLargerGetLinesToRemove=function(e,t,s,r,o,n){const a=[];for(let h=0;h=h&&r0&&(e>_||0===d[e].getTrimmedLength());e--)v++;v>0&&(a.push(h+d.length-v),a.push(v)),h+=d.length-1}return a},t.reflowLargerCreateNewLayout=function(e,t){const i=[];let s=0,r=t[s],o=0;for(let n=0;nl&&(n-=l,a++);const c=2===e[a].getWidth(n-1);c&&n--;const d=c?s-1:s;r.push(d),h+=d}return r},t.getWrappedLineTrimmedLength=i},4097(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferSet=void 0;const s=i(4812),r=i(1073),o=i(8636);class n extends s.Disposable{constructor(e,t,i){super(),this._optionsService=e,this._bufferService=t,this._logService=i,this._normalBuffer=this._register(new s.MutableDisposable),this._altBuffer=this._register(new s.MutableDisposable),this._onBufferActivate=this._register(new o.Emitter),this.onBufferActivate=this._onBufferActivate.event,this.reset(),this._register(this._optionsService.onSpecificOptionChange("scrollback",()=>this.resize(this._bufferService.cols,this._bufferService.rows))),this._register(this._optionsService.onSpecificOptionChange("tabStopWidth",()=>this.setupTabStops()))}reset(){this._normal=new r.Buffer(!0,this._optionsService,this._bufferService,this._logService),this._normalBuffer.value=this._normal,this._normal.fillViewportRows(),this._alt=new r.Buffer(!1,this._optionsService,this._bufferService,this._logService),this._altBuffer.value=this._alt,this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}),this.setupTabStops()}get alt(){return this._alt}get active(){return this._activeBuffer}get normal(){return this._normal}activateNormalBuffer(){this._activeBuffer!==this._normal&&(this._normal.x=this._alt.x,this._normal.y=this._alt.y,this._alt.clearAllMarkers(),this._alt.clear(),this._activeBuffer=this._normal,this._onBufferActivate.fire({activeBuffer:this._normal,inactiveBuffer:this._alt}))}activateAltBuffer(e){this._activeBuffer!==this._alt&&(this._alt.fillViewportRows(e),this._alt.x=this._normal.x,this._alt.y=this._normal.y,this._activeBuffer=this._alt,this._onBufferActivate.fire({activeBuffer:this._alt,inactiveBuffer:this._normal}))}resize(e,t){this._normal.resize(e,t),this._alt.resize(e,t),this.setupTabStops(e)}setupTabStops(e){this._normal.setupTabStops(e),this._alt.setupTabStops(e)}}t.BufferSet=n},3055(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.CellData=void 0;const s=i(726),r=i(8938),o=i(5451);class n extends o.AttributeData{constructor(){super(...arguments),this.content=0,this.fg=0,this.bg=0,this.extended=new o.ExtendedAttrs,this.combinedData=""}static fromCharData(e){const t=new n;return t.setFromCharData(e),t}isCombined(){return 2097152&this.content}getWidth(){return this.content>>22}getChars(){return 2097152&this.content?this.combinedData:2097151&this.content?(0,s.stringFromCodePoint)(2097151&this.content):""}getCode(){return this.isCombined()?this.combinedData.charCodeAt(this.combinedData.length-1):2097151&this.content}setFromCharData(e){this.fg=e[r.CHAR_DATA_ATTR_INDEX],this.bg=0;let t=!1;if(e[r.CHAR_DATA_CHAR_INDEX].length>2)t=!0;else if(2===e[r.CHAR_DATA_CHAR_INDEX].length){const i=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0);if(55296<=i&&i<=56319){const s=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(1);56320<=s&&s<=57343?this.content=1024*(i-55296)+s-56320+65536|e[r.CHAR_DATA_WIDTH_INDEX]<<22:t=!0}else t=!0}else this.content=e[r.CHAR_DATA_CHAR_INDEX].charCodeAt(0)|e[r.CHAR_DATA_WIDTH_INDEX]<<22;t&&(this.combinedData=e[r.CHAR_DATA_CHAR_INDEX],this.content=2097152|e[r.CHAR_DATA_WIDTH_INDEX]<<22)}getAsCharData(){return[this.fg,this.getChars(),this.getWidth(),this.getCode()]}attributesEquals(e){if(this.getFgColorMode()!==e.getFgColorMode()||this.getFgColor()!==e.getFgColor())return!1;if(this.getBgColorMode()!==e.getBgColorMode()||this.getBgColor()!==e.getBgColor())return!1;if(this.isInverse()!==e.isInverse())return!1;if(this.isBold()!==e.isBold())return!1;if(this.isUnderline()!==e.isUnderline())return!1;if(this.isUnderline()){if(this.getUnderlineStyle()!==e.getUnderlineStyle())return!1;const t=this.isUnderlineColorDefault(),i=e.isUnderlineColorDefault();if(!t||!i){if(t!==i)return!1;if(this.getUnderlineColor()!==e.getUnderlineColor())return!1;if(this.getUnderlineColorMode()!==e.getUnderlineColorMode())return!1}}return this.isOverline()===e.isOverline()&&this.isBlink()===e.isBlink()&&this.isInvisible()===e.isInvisible()&&this.isItalic()===e.isItalic()&&this.isDim()===e.isDim()&&this.isStrikethrough()===e.isStrikethrough()}}t.CellData=n},8938(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.WHITESPACE_CELL_CODE=t.WHITESPACE_CELL_WIDTH=t.WHITESPACE_CELL_CHAR=t.NULL_CELL_CODE=t.NULL_CELL_WIDTH=t.NULL_CELL_CHAR=t.CHAR_DATA_CODE_INDEX=t.CHAR_DATA_WIDTH_INDEX=t.CHAR_DATA_CHAR_INDEX=t.CHAR_DATA_ATTR_INDEX=t.DEFAULT_EXT=t.DEFAULT_ATTR=t.DEFAULT_COLOR=void 0,t.DEFAULT_COLOR=0,t.DEFAULT_ATTR=t.DEFAULT_COLOR<<9|256,t.DEFAULT_EXT=0,t.CHAR_DATA_ATTR_INDEX=0,t.CHAR_DATA_CHAR_INDEX=1,t.CHAR_DATA_WIDTH_INDEX=2,t.CHAR_DATA_CODE_INDEX=3,t.NULL_CELL_CHAR="",t.NULL_CELL_WIDTH=1,t.NULL_CELL_CODE=0,t.WHITESPACE_CELL_CHAR=" ",t.WHITESPACE_CELL_WIDTH=1,t.WHITESPACE_CELL_CODE=32},8158(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.Marker=void 0;const s=i(4812),r=i(8636);class o{get id(){return this._id}constructor(e){this.line=e,this.isDisposed=!1,this._disposables=[],this._id=o._nextId++,this._onDispose=this.register(new r.Emitter),this.onDispose=this._onDispose.event}dispose(){this.isDisposed||(this.isDisposed=!0,this.line=-1,this._onDispose.fire(),(0,s.dispose)(this._disposables),this._disposables.length=0)}register(e){return this._disposables.push(e),e}}t.Marker=o,o._nextId=1},6760(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.DEFAULT_CHARSET=t.CHARSETS=void 0,t.CHARSETS={},t.DEFAULT_CHARSET=t.CHARSETS.B,t.CHARSETS[0]={"`":"◆",a:"▒",b:"␉",c:"␌",d:"␍",e:"␊",f:"°",g:"±",h:"",i:"␋",j:"┘",k:"┐",l:"┌",m:"└",n:"┼",o:"⎺",p:"⎻",q:"─",r:"⎼",s:"⎽",t:"├",u:"┤",v:"┴",w:"┬",x:"│",y:"≤",z:"≥","{":"π","|":"≠","}":"£","~":"·"},t.CHARSETS.A={"#":"£"},t.CHARSETS.B=void 0,t.CHARSETS[4]={"#":"£","@":"¾","[":"ij","\\":"½","]":"|","{":"¨","|":"f","}":"¼","~":"´"},t.CHARSETS.C=t.CHARSETS[5]={"[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS.R={"#":"£","@":"à","[":"°","\\":"ç","]":"§","{":"é","|":"ù","}":"è","~":"¨"},t.CHARSETS.Q={"@":"à","[":"â","\\":"ç","]":"ê","^":"î","`":"ô","{":"é","|":"ù","}":"è","~":"û"},t.CHARSETS.K={"@":"§","[":"Ä","\\":"Ö","]":"Ü","{":"ä","|":"ö","}":"ü","~":"ß"},t.CHARSETS.Y={"#":"£","@":"§","[":"°","\\":"ç","]":"é","`":"ù","{":"à","|":"ò","}":"è","~":"ì"},t.CHARSETS.E=t.CHARSETS[6]={"@":"Ä","[":"Æ","\\":"Ø","]":"Å","^":"Ü","`":"ä","{":"æ","|":"ø","}":"å","~":"ü"},t.CHARSETS.Z={"#":"£","@":"§","[":"¡","\\":"Ñ","]":"¿","{":"°","|":"ñ","}":"ç"},t.CHARSETS.H=t.CHARSETS[7]={"@":"É","[":"Ä","\\":"Ö","]":"Å","^":"Ü","`":"é","{":"ä","|":"ö","}":"å","~":"ü"},t.CHARSETS["="]={"#":"ù","@":"à","[":"é","\\":"ç","]":"ê","^":"î",_:"è","`":"ô","{":"ä","|":"ö","}":"ü","~":"û"}},706(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.evaluateKeyboardEvent=function(e,t,s,r){const o={type:0,cancel:!1,key:void 0},n=(e.shiftKey?1:0)|(e.altKey?2:0)|(e.ctrlKey?4:0)|(e.metaKey?8:0);switch(e.keyCode){case 0:"UIKeyInputUpArrow"===e.key?o.key=t?"OA":"[A":"UIKeyInputLeftArrow"===e.key?o.key=t?"OD":"[D":"UIKeyInputRightArrow"===e.key?o.key=t?"OC":"[C":"UIKeyInputDownArrow"===e.key&&(o.key=t?"OB":"[B");break;case 8:o.key=e.ctrlKey?"\b":"",e.altKey&&(o.key=""+o.key);break;case 9:if(e.shiftKey){o.key="[Z";break}o.key="\t",o.cancel=!0;break;case 13:"c"===e.key&&e.ctrlKey?o.key="":o.key=e.altKey?"\r":"\r",o.cancel=!0;break;case 27:o.key="",e.altKey&&(o.key=""),o.cancel=!0;break;case 37:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"D":t?"OD":"[D";break;case 39:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"C":t?"OC":"[C";break;case 38:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"A":t?"OA":"[A";break;case 40:if(e.metaKey)break;o.key=n?"[1;"+(n+1)+"B":t?"OB":"[B";break;case 45:e.shiftKey||e.ctrlKey||(o.key="[2~");break;case 46:o.key=n?"[3;"+(n+1)+"~":"[3~";break;case 36:o.key=n?"[1;"+(n+1)+"H":t?"OH":"[H";break;case 35:o.key=n?"[1;"+(n+1)+"F":t?"OF":"[F";break;case 33:e.shiftKey?o.type=2:e.ctrlKey?o.key="[5;"+(n+1)+"~":o.key="[5~";break;case 34:e.shiftKey?o.type=3:e.ctrlKey?o.key="[6;"+(n+1)+"~":o.key="[6~";break;case 112:o.key=n?"[1;"+(n+1)+"P":"OP";break;case 113:o.key=n?"[1;"+(n+1)+"Q":"OQ";break;case 114:o.key=n?"[1;"+(n+1)+"R":"OR";break;case 115:o.key=n?"[1;"+(n+1)+"S":"OS";break;case 116:o.key=n?"[15;"+(n+1)+"~":"[15~";break;case 117:o.key=n?"[17;"+(n+1)+"~":"[17~";break;case 118:o.key=n?"[18;"+(n+1)+"~":"[18~";break;case 119:o.key=n?"[19;"+(n+1)+"~":"[19~";break;case 120:o.key=n?"[20;"+(n+1)+"~":"[20~";break;case 121:o.key=n?"[21;"+(n+1)+"~":"[21~";break;case 122:o.key=n?"[23;"+(n+1)+"~":"[23~";break;case 123:o.key=n?"[24;"+(n+1)+"~":"[24~";break;default:if(!e.ctrlKey||e.shiftKey||e.altKey||e.metaKey)if(s&&!r||!e.altKey||e.metaKey)if(!s||e.altKey||e.ctrlKey||e.shiftKey||!e.metaKey){if(e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&e.keyCode>=48&&1===e.key.length)o.key=e.key;else if(e.key&&e.ctrlKey&&e.shiftKey)switch(e.code){case"Minus":o.key="";break;case"Digit2":o.key="\0";break;case"Digit6":o.key=""}}else 65===e.keyCode&&(o.type=1);else{const t=i[e.keyCode],s=t?.[e.shiftKey?1:0];if(s)o.key=""+s;else if(e.keyCode>=65&&e.keyCode<=90){const t=e.ctrlKey?e.keyCode-64:e.keyCode+32;let i=String.fromCharCode(t);e.shiftKey&&(i=i.toUpperCase()),o.key=""+i}else if(32===e.keyCode)o.key=""+(e.ctrlKey?"\0":" ");else if("Dead"===e.key&&e.code.startsWith("Key")){let t=e.code.slice(3,4);e.shiftKey||(t=t.toLowerCase()),o.key=""+t,o.cancel=!0}}else e.keyCode>=65&&e.keyCode<=90?o.key=String.fromCharCode(e.keyCode-64):32===e.keyCode?o.key="\0":e.keyCode>=51&&e.keyCode<=55?o.key=String.fromCharCode(e.keyCode-51+27):56===e.keyCode?o.key="":"/"===e.key?o.key="":219===e.keyCode?o.key="":220===e.keyCode?o.key="":221===e.keyCode&&(o.key="")}return o};const i={48:["0",")"],49:["1","!"],50:["2","@"],51:["3","#"],52:["4","$"],53:["5","%"],54:["6","^"],55:["7","&"],56:["8","*"],57:["9","("],186:[";",":"],187:["=","+"],188:[",","<"],189:["-","_"],190:[".",">"],191:["/","?"],192:["`","~"],219:["[","{"],220:["\\","|"],221:["]","}"],222:["'",'"']}},7241(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.KittyKeyboard=void 0,t.KittyKeyboard=class{constructor(){this._functionalKeyCodes={Escape:27,Enter:13,Tab:9,Backspace:127,CapsLock:57358,ScrollLock:57359,NumLock:57360,PrintScreen:57361,Pause:57362,ContextMenu:57363,F13:57376,F14:57377,F15:57378,F16:57379,F17:57380,F18:57381,F19:57382,F20:57383,F21:57384,F22:57385,F23:57386,F24:57387,F25:57388,KP_0:57399,KP_1:57400,KP_2:57401,KP_3:57402,KP_4:57403,KP_5:57404,KP_6:57405,KP_7:57406,KP_8:57407,KP_9:57408,KP_Decimal:57409,KP_Divide:57410,KP_Multiply:57411,KP_Subtract:57412,KP_Add:57413,KP_Enter:57414,KP_Equal:57415,ShiftLeft:57441,ShiftRight:57447,ControlLeft:57442,ControlRight:57448,AltLeft:57443,AltRight:57449,MetaLeft:57444,MetaRight:57450,MediaPlayPause:57430,MediaStop:57432,MediaTrackNext:57435,MediaTrackPrevious:57436,AudioVolumeDown:57438,AudioVolumeUp:57439,AudioVolumeMute:57440},this._csiTildeKeys={Insert:2,Delete:3,PageUp:5,PageDown:6,F5:15,F6:17,F7:18,F8:19,F9:20,F10:21,F11:23,F12:24},this._csiLetterKeys={ArrowUp:"A",ArrowDown:"B",ArrowRight:"C",ArrowLeft:"D",Home:"H",End:"F"},this._ss3FunctionKeys={F1:"P",F2:"Q",F3:"R",F4:"S"}}_getNumpadKeyCode(e){if(e.code.startsWith("Numpad")){const t=e.code.slice(6);if(t>="0"&&t<="9")return 57399+parseInt(t,10);switch(t){case"Decimal":return 57409;case"Divide":return 57410;case"Multiply":return 57411;case"Subtract":return 57412;case"Add":return 57413;case"Enter":return 57414;case"Equal":return 57415}}}_getModifierKeyCode(e){switch(e.code){case"ShiftLeft":return 57441;case"ShiftRight":return 57447;case"ControlLeft":return 57442;case"ControlRight":return 57448;case"AltLeft":return 57443;case"AltRight":return 57449;case"MetaLeft":return 57444;case"MetaRight":return 57450}}_encodeModifiers(e){let t=0;return e.shiftKey&&(t|=1),e.altKey&&(t|=2),e.ctrlKey&&(t|=4),e.metaKey&&(t|=8),t>0?t+1:0}_getKeyCode(e,t){const i=this._getNumpadKeyCode(e);if(void 0!==i)return i;const s=this._getModifierKeyCode(e);if(void 0!==s)return s;const r=this._functionalKeyCodes[e.key];if(void 0!==r)return r;if((e.shiftKey||t&&e.altKey)&&e.code){if(e.code.startsWith("Digit")&&6===e.code.length){const t=e.code.charAt(5);if(t>="0"&&t<="9")return t.charCodeAt(0)}if(e.code.startsWith("Key")&&4===e.code.length)return e.code.charAt(3).toLowerCase().charCodeAt(0)}if(1===e.key.length){const t=e.key.codePointAt(0);return t>=65&&t<=90?t+32:t}}_isModifierKey(e){return"Shift"===e.key||"Control"===e.key||"Alt"===e.key||"Meta"===e.key}_isLockKey(e){return"CapsLock"===e.key||"NumLock"===e.key||"ScrollLock"===e.key}_buildCsiLetterSequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"["+e}_buildSs3Sequence(e,t,i,s){const r=s&&1!==i;if(t>0||r){let s="[1;"+(t>0?t:"1");return r&&(s+=":"+i),s+=e,s}return"O"+e}_buildCsiTildeSequence(e,t,i,s){const r=s&&1!==i;let o="["+e;return(t>0||r)&&(o+=";"+(t>0?t:"1"),r&&(o+=":"+i)),o+="~",o}_buildCsiUSequence(e,t,i,s,r,o,n){const a=!!(2&r);let h,l="["+t;4&r&&e.shiftKey&&1===e.key.length&&!o&&!n&&(h=e.key.codePointAt(0),l+=":"+h);const c=16&r&&3!==s&&1===e.key.length&&!o&&!n&&!e.ctrlKey?e.key.codePointAt(0):void 0,d=a&&1!==s&&(3===s||void 0===c);return(i>0||d||void 0!==c)&&(l+=";",i>0?l+=i:d&&(l+="1"),d&&(l+=":"+s)),void 0!==c&&(l+=";"+c),l+="u",l}evaluate(e,t,i=1,s=!1){const r={type:0,cancel:!1,key:void 0},o=this._encodeModifiers(e),n=this._isModifierKey(e),a=!!(2&t);if(!a&&3===i)return r;if(n&&!(8&t))return r;if(this._isLockKey(e)&&!(8&t))return r;const h=this._csiLetterKeys[e.key];if(h)return r.key=this._buildCsiLetterSequence(h,o,i,a),r.cancel=!0,r;const l=this._ss3FunctionKeys[e.key];if(l)return r.key=this._buildSs3Sequence(l,o,i,a),r.cancel=!0,r;const c=this._csiTildeKeys[e.key];if(void 0!==c)return r.key=this._buildCsiTildeSequence(c,o,i,a),r.cancel=!0,r;const d=this._getKeyCode(e,s);if(void 0===d)return r;const _=13===d||9===d||127===d;if(_&&3===i&&!(8&t))return r;const u=void 0!==this._functionalKeyCodes[e.key]||void 0!==this._getNumpadKeyCode(e);if(8&t||a&&3===i||(1&t||a)&&(u&&!_||o>0&&1!==e.key.length||o-1>1))r.key=this._buildCsiUSequence(e,d,o,i,t,u,n),r.cancel=!0;else{const t=13===d?"\r":9===d?"\t":127===d?"":void 0;t?r.key=t:1!==e.key.length||e.ctrlKey||e.altKey||e.metaKey||(r.key=e.key)}return r}static shouldUseProtocol(e){return e>0}}},726(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Utf8ToUtf32=t.StringToUtf32=void 0,t.stringFromCodePoint=function(e){return e>65535?(e-=65536,String.fromCharCode(55296+(e>>10))+String.fromCharCode(e%1024+56320)):String.fromCharCode(e)},t.utf32ToString=function(e,t=0,i=e.length){let s="";for(let r=t;r65535?(t-=65536,s+=String.fromCharCode(55296+(t>>10))+String.fromCharCode(t%1024+56320)):s+=String.fromCharCode(t)}return s},t.StringToUtf32=class{constructor(){this._interim=0}clear(){this._interim=0}decode(e,t){const i=e.length;if(!i)return 0;let s=0,r=0;if(this._interim){const i=e.charCodeAt(r++);56320<=i&&i<=57343?t[s++]=1024*(this._interim-55296)+i-56320+65536:(t[s++]=this._interim,t[s++]=i),this._interim=0}for(let o=r;o=i)return this._interim=r,s;const n=e.charCodeAt(o);56320<=n&&n<=57343?t[s++]=1024*(r-55296)+n-56320+65536:(t[s++]=r,t[s++]=n);continue}65279!==r&&(t[s++]=r)}return s}},t.Utf8ToUtf32=class{constructor(){this.interim=new Uint8Array(3)}clear(){this.interim.fill(0)}decode(e,t){const i=e.length;if(!i)return 0;let s,r,o,n,a,h=0,l=0;if(this.interim[0]){let s=!1,r=this.interim[0];r&=192==(224&r)?31:224==(240&r)?15:7;let o,n=0;for(;(o=this.interim[++n])&&n<4;)r<<=6,r|=63&o;const a=192==(224&this.interim[0])?2:224==(240&this.interim[0])?3:4,c=a-n;for(;l=i)return 0;if(o=e[l++],128!=(192&o)){l--,s=!0;break}this.interim[n++]=o,r<<=6,r|=63&o}s||(2===a?r<128?l--:t[h++]=r:3===a?r<2048||r>=55296&&r<=57343||65279===r||(t[h++]=r):r<65536||r>1114111||(t[h++]=r)),this.interim.fill(0)}const c=i-4;let d=l;for(;d=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(a=(31&s)<<6|63&r,a<128){d--;continue}t[h++]=a}else if(224==(240&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(a=(15&s)<<12|(63&r)<<6|63&o,a<2048||a>=55296&&a<=57343||65279===a)continue;t[h++]=a}else if(240==(248&s)){if(d>=i)return this.interim[0]=s,h;if(r=e[d++],128!=(192&r)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,h;if(o=e[d++],128!=(192&o)){d--;continue}if(d>=i)return this.interim[0]=s,this.interim[1]=r,this.interim[2]=o,h;if(n=e[d++],128!=(192&n)){d--;continue}if(a=(7&s)<<18|(63&r)<<12|(63&o)<<6|63&n,a<65536||a>1114111)continue;t[h++]=a}}return h}}},7428(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeV6=void 0;const s=i(6415),r=[[768,879],[1155,1158],[1160,1161],[1425,1469],[1471,1471],[1473,1474],[1476,1477],[1479,1479],[1536,1539],[1552,1557],[1611,1630],[1648,1648],[1750,1764],[1767,1768],[1770,1773],[1807,1807],[1809,1809],[1840,1866],[1958,1968],[2027,2035],[2305,2306],[2364,2364],[2369,2376],[2381,2381],[2385,2388],[2402,2403],[2433,2433],[2492,2492],[2497,2500],[2509,2509],[2530,2531],[2561,2562],[2620,2620],[2625,2626],[2631,2632],[2635,2637],[2672,2673],[2689,2690],[2748,2748],[2753,2757],[2759,2760],[2765,2765],[2786,2787],[2817,2817],[2876,2876],[2879,2879],[2881,2883],[2893,2893],[2902,2902],[2946,2946],[3008,3008],[3021,3021],[3134,3136],[3142,3144],[3146,3149],[3157,3158],[3260,3260],[3263,3263],[3270,3270],[3276,3277],[3298,3299],[3393,3395],[3405,3405],[3530,3530],[3538,3540],[3542,3542],[3633,3633],[3636,3642],[3655,3662],[3761,3761],[3764,3769],[3771,3772],[3784,3789],[3864,3865],[3893,3893],[3895,3895],[3897,3897],[3953,3966],[3968,3972],[3974,3975],[3984,3991],[3993,4028],[4038,4038],[4141,4144],[4146,4146],[4150,4151],[4153,4153],[4184,4185],[4448,4607],[4959,4959],[5906,5908],[5938,5940],[5970,5971],[6002,6003],[6068,6069],[6071,6077],[6086,6086],[6089,6099],[6109,6109],[6155,6157],[6313,6313],[6432,6434],[6439,6440],[6450,6450],[6457,6459],[6679,6680],[6912,6915],[6964,6964],[6966,6970],[6972,6972],[6978,6978],[7019,7027],[7616,7626],[7678,7679],[8203,8207],[8234,8238],[8288,8291],[8298,8303],[8400,8431],[12330,12335],[12441,12442],[43014,43014],[43019,43019],[43045,43046],[64286,64286],[65024,65039],[65056,65059],[65279,65279],[65529,65531]],o=[[68097,68099],[68101,68102],[68108,68111],[68152,68154],[68159,68159],[119143,119145],[119155,119170],[119173,119179],[119210,119213],[119362,119364],[917505,917505],[917536,917631],[917760,917999]];let n;t.UnicodeV6=class{constructor(){if(this.version="6",!n){n=new Uint8Array(65536),n.fill(1),n[0]=0,n.fill(0,1,32),n.fill(0,127,160),n.fill(2,4352,4448),n[9001]=2,n[9002]=2,n.fill(2,11904,42192),n[12351]=1,n.fill(2,44032,55204),n.fill(2,63744,64256),n.fill(2,65040,65050),n.fill(2,65072,65136),n.fill(2,65280,65377),n.fill(2,65504,65511);for(let e=0;et[r][1])return!1;for(;r>=s;)if(i=s+r>>1,e>t[i][1])s=i+1;else{if(!(e=131072&&e<=196605||e>=196608&&e<=262141?2:1}charProperties(e,t){let i=this.wcwidth(e),r=0===i&&0!==t;if(r){const e=s.UnicodeService.extractWidth(t);0===e?r=!1:e>i&&(i=e)}return s.UnicodeService.createPropertyValue(0,i,r)}}},9249(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Win32InputMode=void 0,t.Win32InputMode=class{constructor(){this._codeToVk={KeyA:65,KeyB:66,KeyC:67,KeyD:68,KeyE:69,KeyF:70,KeyG:71,KeyH:72,KeyI:73,KeyJ:74,KeyK:75,KeyL:76,KeyM:77,KeyN:78,KeyO:79,KeyP:80,KeyQ:81,KeyR:82,KeyS:83,KeyT:84,KeyU:85,KeyV:86,KeyW:87,KeyX:88,KeyY:89,KeyZ:90,Digit0:48,Digit1:49,Digit2:50,Digit3:51,Digit4:52,Digit5:53,Digit6:54,Digit7:55,Digit8:56,Digit9:57,F1:112,F2:113,F3:114,F4:115,F5:116,F6:117,F7:118,F8:119,F9:120,F10:121,F11:122,F12:123,F13:124,F14:125,F15:126,F16:127,F17:128,F18:129,F19:130,F20:131,F21:132,F22:133,F23:134,F24:135,Numpad0:96,Numpad1:97,Numpad2:98,Numpad3:99,Numpad4:100,Numpad5:101,Numpad6:102,Numpad7:103,Numpad8:104,Numpad9:105,NumpadMultiply:106,NumpadAdd:107,NumpadSeparator:108,NumpadSubtract:109,NumpadDecimal:110,NumpadDivide:111,NumpadEnter:13,NumLock:144,ArrowUp:38,ArrowDown:40,ArrowLeft:37,ArrowRight:39,Home:36,End:35,PageUp:33,PageDown:34,Insert:45,Delete:46,ShiftLeft:16,ShiftRight:16,ControlLeft:17,ControlRight:17,AltLeft:18,AltRight:18,MetaLeft:91,MetaRight:92,CapsLock:20,ScrollLock:145,Escape:27,Enter:13,Tab:9,Space:32,Backspace:8,Pause:19,ContextMenu:93,PrintScreen:44,Semicolon:186,Equal:187,Comma:188,Minus:189,Period:190,Slash:191,Backquote:192,BracketLeft:219,Backslash:220,BracketRight:221,Quote:222,IntlBackslash:226},this._codeToScancode={KeyQ:16,KeyW:17,KeyE:18,KeyR:19,KeyT:20,KeyY:21,KeyU:22,KeyI:23,KeyO:24,KeyP:25,KeyA:30,KeyS:31,KeyD:32,KeyF:33,KeyG:34,KeyH:35,KeyJ:36,KeyK:37,KeyL:38,KeyZ:44,KeyX:45,KeyC:46,KeyV:47,KeyB:48,KeyN:49,KeyM:50,Digit1:2,Digit2:3,Digit3:4,Digit4:5,Digit5:6,Digit6:7,Digit7:8,Digit8:9,Digit9:10,Digit0:11,F1:59,F2:60,F3:61,F4:62,F5:63,F6:64,F7:65,F8:66,F9:67,F10:68,F11:87,F12:88,Numpad0:82,Numpad1:79,Numpad2:80,Numpad3:81,Numpad4:75,Numpad5:76,Numpad6:77,Numpad7:71,Numpad8:72,Numpad9:73,NumpadMultiply:55,NumpadAdd:78,NumpadSubtract:74,NumpadDecimal:83,NumpadDivide:53,NumpadEnter:28,NumLock:69,ArrowUp:72,ArrowDown:80,ArrowLeft:75,ArrowRight:77,Home:71,End:79,PageUp:73,PageDown:81,Insert:82,Delete:83,ShiftLeft:42,ShiftRight:54,ControlLeft:29,ControlRight:29,AltLeft:56,AltRight:56,CapsLock:58,ScrollLock:70,Escape:1,Enter:28,Tab:15,Space:57,Backspace:14,Pause:69,Semicolon:39,Equal:13,Comma:51,Minus:12,Period:52,Slash:53,Backquote:41,BracketLeft:26,Backslash:43,BracketRight:27,Quote:40},this._enhancedKeyCodes=new Set(["ArrowUp","ArrowDown","ArrowLeft","ArrowRight","Home","End","PageUp","PageDown","Insert","Delete","NumpadEnter","NumpadDivide","ControlRight","AltRight","PrintScreen","Pause","ContextMenu","MetaLeft","MetaRight"]),this._keyToControlChar={Enter:13,Backspace:8,Tab:9,Escape:27}}_getVirtualKeyCode(e){const t=this._codeToVk[e.code];return void 0!==t?t:e.keyCode||0}_getScanCode(e){return this._codeToScancode[e.code]||0}_getUnicodeChar(e){if(e.ctrlKey&&!e.altKey&&!e.metaKey){if("Enter"===e.key)return 10;if("Backspace"===e.key)return 127}const t=this._keyToControlChar[e.key];if(void 0!==t)return t;if(1===e.key.length){const t=e.key.codePointAt(0)||0;if(e.ctrlKey&&!e.altKey&&!e.metaKey){if(t>=65&&t<=90)return t-64;if(t>=97&&t<=122)return t-96}return t}return 0}_getControlKeyState(e){let t=0;return e.shiftKey&&(t|=16),e.ctrlKey&&("ControlRight"===e.code?t|=4:t|=8),e.altKey&&("AltRight"===e.code?t|=1:t|=2),this._enhancedKeyCodes.has(e.code)&&(t|=256),t}evaluateKeyboardEvent(e,t){return{type:0,cancel:!0,key:`[${this._getVirtualKeyCode(e)};${this._getScanCode(e)};${this._getUnicodeChar(e)};${t?1:0};${this._getControlKeyState(e)};1_`}}}},3562(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.WriteBuffer=void 0;const s=i(3132),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._action=e,this._writeBuffer=[],this._callbacks=[],this._pendingData=0,this._bufferOffset=0,this._isSyncWriting=!1,this._syncCalls=0,this._didUserInput=!1,this._innerWriteTimer=this._register(new s.TimeoutTimer),this._onWriteParsed=this._register(new o.Emitter),this.onWriteParsed=this._onWriteParsed.event,this._register((0,r.toDisposable)(()=>{this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0}))}handleUserInput(){this._didUserInput=!0}flushSync(){if(this._store.isDisposed)return;if(this._isSyncWriting)return;let e;this._isSyncWriting=!0;let t=!1;for(;e=this._writeBuffer.shift();){t=!0,this._action(e);const i=this._callbacks.shift();i&&i()}this._pendingData=0,this._bufferOffset=2147483647,this._writeBuffer.length=0,this._callbacks.length=0,this._isSyncWriting=!1,t&&this._onWriteParsed.fire()}writeSync(e,t){if(this._store.isDisposed)return;if(void 0!==t&&this._syncCalls>t)return void(this._syncCalls=0);if(this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(void 0),this._syncCalls++,this._isSyncWriting)return;let i;for(this._isSyncWriting=!0;i=this._writeBuffer.shift();){this._action(i);const e=this._callbacks.shift();e&&e()}this._pendingData=0,this._bufferOffset=2147483647,this._isSyncWriting=!1,this._syncCalls=0}write(e,t){if(!this._store.isDisposed){if(this._pendingData>5e7)throw new Error("write data discarded, use flow control to avoid losing data");if(!this._writeBuffer.length){if(this._bufferOffset=0,this._didUserInput)return this._didUserInput=!1,this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t),void this._innerWrite();this._scheduleInnerWrite()}this._pendingData+=e.length,this._writeBuffer.push(e),this._callbacks.push(t)}}_scheduleInnerWrite(e=0,t=!0){this._store.isDisposed||this._innerWriteTimer.cancelAndSet(()=>this._innerWrite(e,t),0)}_innerWrite(e=0,t=!0){if(this._store.isDisposed)return;const i=e||performance.now();for(;this._writeBuffer.length>this._bufferOffset;){const e=this._writeBuffer[this._bufferOffset],s=this._action(e,t);if(s){const e=e=>{this._store.isDisposed||(performance.now()-i>=12?this._scheduleInnerWrite(0,e):this._innerWrite(i,e))};return void s.catch(e=>(queueMicrotask(()=>{throw e}),Promise.resolve(!1))).then(e)}const r=this._callbacks[this._bufferOffset];if(r&&r(),this._bufferOffset++,this._pendingData-=e.length,performance.now()-i>=12)break}this._writeBuffer.length>this._bufferOffset?(this._bufferOffset>50&&(this._writeBuffer=this._writeBuffer.slice(this._bufferOffset),this._callbacks=this._callbacks.slice(this._bufferOffset),this._bufferOffset=0),this._scheduleInnerWrite()):(this._writeBuffer.length=0,this._callbacks.length=0,this._pendingData=0,this._bufferOffset=0),this._onWriteParsed.fire()}}t.WriteBuffer=n},8693(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.parseColor=function(e){if(!e)return;let t=e.toLowerCase();if(t.startsWith("rgb:")){t=t.slice(4);const e=i.exec(t);if(e){const t=e[1]?15:e[4]?255:e[7]?4095:65535;return[Math.round(parseInt(e[1]||e[4]||e[7]||e[10],16)/t*255),Math.round(parseInt(e[2]||e[5]||e[8]||e[11],16)/t*255),Math.round(parseInt(e[3]||e[6]||e[9]||e[12],16)/t*255)]}}else if(t.startsWith("#")&&(t=t.slice(1),s.exec(t)&&[3,6,9,12].includes(t.length))){const e=t.length/3,i=[0,0,0];for(let s=0;s<3;++s){const r=parseInt(t.slice(e*s,e*s+e),16);i[s]=1===e?r<<4:2===e?r:3===e?r>>4:r>>8}return i}},t.toRgbString=function(e,t=16){const[i,s,o]=e;return`rgb:${r(i,t)}/${r(s,t)}/${r(o,t)}`};const i=/^([\da-f])\/([\da-f])\/([\da-f])$|^([\da-f]{2})\/([\da-f]{2})\/([\da-f]{2})$|^([\da-f]{3})\/([\da-f]{3})\/([\da-f]{3})$|^([\da-f]{4})\/([\da-f]{4})\/([\da-f]{4})$/,s=/^[\da-f]+$/;function r(e,t){const i=e.toString(16),s=i.length<2?"0"+i:i;switch(t){case 4:return i[0];case 8:return s;case 12:return(s+s).slice(0,3);default:return s+s}}},2607(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.ApcHandler=t.ApcParser=void 0;const s=i(726),r=i(4220),o=[];t.ApcParser=class{constructor(){this._handlers=Object.create(null),this._active=o,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._ident=0}start(e){if(this.reset(),this._ident=e,this._active=this._handlers[e]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._ident,"START")}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}end(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"END",e);this._active=o,this._ident=0}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.ApcHandler=n,n._payloadLimit=1e7},9823(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.DcsHandler=t.DcsParser=void 0;const s=i(726),r=i(7262),o=i(4220),n=[];t.DcsParser=class{constructor(){this._handlers=Object.create(null),this._active=n,this._ident=0,this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=n}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}reset(){if(this._active.length)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].unhook(!1);this._stack.paused=!1,this._active=n,this._ident=0}hook(e,t){if(this.reset(),this._ident=e,this._active=this._handlers[e]||n,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].hook(t);else this._handlerFb(this._ident,"HOOK",t)}put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._ident,"PUT",(0,s.utf32ToString)(e,t,i))}unhook(e,t=!0){if(this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].unhook(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].unhook(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._ident,"UNHOOK",e);this._active=n,this._ident=0}};const a=new r.Params;a.addParam(0);class h{constructor(e){this._handler=e,this._data=new o.LimitedStringBuilder(h._payloadLimit),this._params=a,this._hitLimit=!1}hook(e){this._params=e.length>1||e.params[0]?e.clone():a,this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}unhook(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString(),this._params),t instanceof Promise))return t.then(e=>(this._params=a,this._data.reset(),this._hitLimit=!1,e));return this._params=a,this._data.reset(),this._hitLimit=!1,t}}t.DcsHandler=h,h._payloadLimit=1e7},6717(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.EscapeSequenceParser=t.VT500_TRANSITION_TABLE=t.TransitionTable=void 0;const s=i(4812),r=i(7262),o=i(1346),n=i(9823),a=i(2607);class h{constructor(e){this.table=new Uint16Array(e)}setDefault(e,t){this.table.fill(e<<8|t)}add(e,t,i,s){this.table[t<<8|e]=i<<8|s}addMany(e,t,i,s){for(let r=0;rt),i=(e,i)=>t.slice(e,i),s=i(32,127),r=i(0,24);r.push(25),r.push.apply(r,i(28,32));const o=i(0,17);e.setDefault(1,0),e.addMany(s,0,2,0);for(const t of o)e.addMany([24,26,153,154],t,3,0),e.addMany(i(128,144),t,3,0),e.addMany(i(144,152),t,3,0),e.add(156,t,0,0),e.add(27,t,11,1),e.add(157,t,4,8),e.addMany([152,158],t,0,7),e.add(159,t,11,14),e.add(155,t,11,3),e.add(144,t,11,9);return e.addMany(r,0,3,0),e.addMany(r,1,3,1),e.add(127,1,0,1),e.addMany(r,8,0,8),e.addMany(r,3,3,3),e.add(127,3,0,3),e.addMany(r,4,3,4),e.add(127,4,0,4),e.addMany(r,6,3,6),e.addMany(r,5,3,5),e.add(127,5,0,5),e.addMany(r,2,3,2),e.add(127,2,0,2),e.add(93,1,4,8),e.addMany(s,8,5,8),e.add(127,8,5,8),e.addMany([156,27,24,26,7],8,6,0),e.addMany(i(28,32),8,0,8),e.addMany([88,94],1,0,7),e.addMany(s,7,0,7),e.addMany(r,7,0,7),e.add(156,7,0,0),e.add(127,7,0,7),e.add(95,1,11,14),e.addMany(r,14,0,14),e.add(127,14,0,14),e.addMany(i(32,48),14,9,15),e.addMany(i(48,127),14,15,16),e.addMany(i(48,127),15,15,16),e.addMany(r,15,0,15),e.addMany(i(32,48),15,9,15),e.add(127,15,0,15),e.addMany(s,16,16,16),e.addMany(r,16,0,16),e.addMany(i(8,14),16,16,16),e.add(127,16,0,16),e.addMany([27,156,24,26],16,17,0),e.add(91,1,11,3),e.addMany(i(64,127),3,7,0),e.addMany(i(48,60),3,8,4),e.addMany([60,61,62,63],3,9,4),e.addMany(i(48,60),4,8,4),e.addMany(i(64,127),4,7,0),e.addMany([60,61,62,63],4,0,6),e.addMany(i(32,64),6,0,6),e.add(127,6,0,6),e.addMany(i(64,127),6,0,0),e.addMany(i(32,48),3,9,5),e.addMany(i(32,48),5,9,5),e.addMany(i(48,64),5,0,6),e.addMany(i(64,127),5,7,0),e.addMany(i(32,48),4,9,5),e.addMany(i(32,48),1,9,2),e.addMany(i(32,48),2,9,2),e.addMany(i(48,127),2,10,0),e.addMany(i(48,80),1,10,0),e.addMany(i(81,88),1,10,0),e.addMany([89,90,92],1,10,0),e.addMany(i(96,127),1,10,0),e.add(80,1,11,9),e.addMany(r,9,0,9),e.add(127,9,0,9),e.addMany(i(32,48),9,9,12),e.addMany(i(48,60),9,8,10),e.addMany([60,61,62,63],9,9,10),e.addMany(r,11,0,11),e.addMany(i(32,128),11,0,11),e.addMany(r,10,0,10),e.add(127,10,0,10),e.addMany(i(48,60),10,8,10),e.addMany([60,61,62,63],10,0,11),e.addMany(i(32,48),10,9,12),e.addMany(r,12,0,12),e.add(127,12,0,12),e.addMany(i(32,48),12,9,12),e.addMany(i(48,64),12,0,11),e.addMany(i(64,127),12,12,13),e.addMany(i(64,127),10,12,13),e.addMany(i(64,127),9,12,13),e.addMany(r,13,13,13),e.addMany(s,13,13,13),e.add(127,13,0,13),e.addMany([27,156,24,26],13,14,0),e.add(l,0,2,0),e.add(l,8,5,8),e.add(l,6,0,6),e.add(l,11,0,11),e.add(l,13,13,13),e.add(l,16,16,16),e}();class c extends s.Disposable{constructor(e=t.VT500_TRANSITION_TABLE){super(),this._transitions=e,this._parseStack={state:0,handlers:[],handlerPos:0,transition:0,chunkPos:0},this.initialState=0,this.currentState=this.initialState,this._params=new r.Params,this._params.addParam(0),this._collect=0,this.precedingJoinState=0,this._printHandlerFb=(e,t,i)=>{},this._executeHandlerFb=e=>{},this._csiHandlerFb=(e,t)=>{},this._escHandlerFb=e=>{},this._errorHandlerFb=e=>e,this._printHandler=this._printHandlerFb,this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._csiHandlers=Object.create(null),this._escHandlers=Object.create(null),this._register((0,s.toDisposable)(()=>{this._csiHandlers=Object.create(null),this._executeHandlers=Object.create(null),this._executeHandlersArr=new Array(24).fill(void 0),this._escHandlers=Object.create(null)})),this._oscParser=this._register(new o.OscParser),this._dcsParser=this._register(new n.DcsParser),this._apcParser=this._register(new a.ApcParser),this._errorHandler=this._errorHandlerFb,this.registerEscHandler({final:"\\"},()=>!0)}_identifier(e,t=[64,126]){let i=0;if(e.prefix){if(e.prefix.length>1)throw new Error("only one byte as prefix supported");if(i=e.prefix.charCodeAt(0),i<60||i>63)throw new Error("prefix must be in range 0x3c .. 0x3f")}if(e.intermediates){if(e.intermediates.length>2)throw new Error("only two bytes as intermediates are supported");for(let t=0;ts||s>47)throw new Error("intermediate must be in range 0x20 .. 0x2f");i<<=8,i|=s}}if(1!==e.final.length)throw new Error("final must be a single byte");const s=e.final.charCodeAt(0);if(t[0]>s||s>t[1])throw new Error(`final must be in range ${t[0]} .. ${t[1]}`);return i<<=8,i|=s,i}identToString(e){const t=[];for(;e;)t.push(String.fromCharCode(255&e)),e>>=8;return t.reverse().join("")}setPrintHandler(e){this._printHandler=e}clearPrintHandler(){this._printHandler=this._printHandlerFb}registerEscHandler(e,t){const i=this._identifier(e,[48,126]);this._escHandlers[i]??=[];const s=this._escHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearEscHandler(e){this._escHandlers[this._identifier(e,[48,126])]&&delete this._escHandlers[this._identifier(e,[48,126])]}setEscHandlerFallback(e){this._escHandlerFb=e}setExecuteHandler(e,t){const i=e.charCodeAt(0);this._executeHandlers[i]=t,i<24&&(this._executeHandlersArr[i]=t)}clearExecuteHandler(e){const t=e.charCodeAt(0);this._executeHandlers[t]&&delete this._executeHandlers[t],t<24&&(this._executeHandlersArr[t]=void 0)}setExecuteHandlerFallback(e){this._executeHandlerFb=e}registerCsiHandler(e,t){const i=this._identifier(e);this._csiHandlers[i]??=[];const s=this._csiHandlers[i];return s.push(t),{dispose:()=>{const e=s.indexOf(t);-1!==e&&s.splice(e,1)}}}clearCsiHandler(e){this._csiHandlers[this._identifier(e)]&&delete this._csiHandlers[this._identifier(e)]}setCsiHandlerFallback(e){this._csiHandlerFb=e}registerDcsHandler(e,t){return this._dcsParser.registerHandler(this._identifier(e),t)}clearDcsHandler(e){this._dcsParser.clearHandler(this._identifier(e))}setDcsHandlerFallback(e){this._dcsParser.setHandlerFallback(e)}registerOscHandler(e,t){return this._oscParser.registerHandler(e,t)}clearOscHandler(e){this._oscParser.clearHandler(e)}setOscHandlerFallback(e){this._oscParser.setHandlerFallback(e)}registerApcHandler(e,t){return e.prefix=void 0,this._apcParser.registerHandler(this._identifier(e,[48,126]),t)}clearApcHandler(e){e.prefix=void 0,this._apcParser.clearHandler(this._identifier(e,[48,126]))}setApcHandlerFallback(e){this._apcParser.setHandlerFallback(e)}setErrorHandler(e){this._errorHandler=e}clearErrorHandler(){this._errorHandler=this._errorHandlerFb}reset(){this.currentState=this.initialState,this._oscParser.reset(),this._dcsParser.reset(),this._apcParser.reset(),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0,0!==this._parseStack.state&&(this._parseStack.state=2,this._parseStack.handlers=[])}_preserveStack(e,t,i,s,r){this._parseStack.state=e,this._parseStack.handlers=t,this._parseStack.handlerPos=i,this._parseStack.transition=s,this._parseStack.chunkPos=r}parse(e,t,i){let s,r,o,n=0;if(this._parseStack.state)if(2===this._parseStack.state)this._parseStack.state=0,n=this._parseStack.chunkPos+1;else{if(void 0===i||1===this._parseStack.state)throw this._parseStack.state=1,new Error("improper continuation due to previous async handler, giving up parsing");const t=this._parseStack.handlers;let r=this._parseStack.handlerPos-1;switch(this._parseStack.state){case 3:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](this._params),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 4:if(!1===i&&r>-1)for(;r>=0&&(o=t[r](),!0!==o);r--)if(o instanceof Promise)return this._parseStack.handlerPos=r,o;this._parseStack.handlers=[];break;case 6:if(s=e[this._parseStack.chunkPos],o=this._dcsParser.unhook(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 5:if(s=e[this._parseStack.chunkPos],o=this._oscParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0;break;case 7:if(s=e[this._parseStack.chunkPos],o=this._apcParser.end(24!==s&&26!==s,i),o)return o;27===s&&(this._parseStack.transition|=1),this._params.resetZdm(),this._collect=0}this._parseStack.state=0,n=this._parseStack.chunkPos+1,this.precedingJoinState=0,this.currentState=255&this._parseStack.transition}for(let i=n;i=60&&n<=63&&(this._collect=n,s++);let a=!1;for(;s=48&&n<=57)this._params.addDigit(n-48);else if(59===n)this._params.addParam(0);else{if(58!==n){if(n>=64&&n<=126){const e=this._csiHandlers[this._collect<<8|n];let t=e?e.length-1:-1;for(;t>=0&&(o=e[t](this._params),!0!==o);t--)if(o instanceof Promise)return r=1792,this._preserveStack(3,e,t,r,s),o;t<0&&this._csiHandlerFb(this._collect<<8|n,this._params),this.precedingJoinState=0,i=s,this.currentState=0,a=!0;break}break}this._params.addSubParam(-1)}a||(i=s-1,this.currentState=4);continue}switch(r=this._transitions.table[this.currentState<<8|(s>8){case 2:let n=i;const a=t-4;for(;n=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l)&&e[++n]>=32&&(e[n]<=126||e[n]>=l););if(n>=a)for(;n=32&&(e[n]<=126||e[n]>=l);)n++;this._printHandler(e,i,n),i=n-1;break;case 3:this._executeHandlers[s]?this._executeHandlers[s]():this._executeHandlerFb(s),this.precedingJoinState=0;break;case 0:break;case 1:if(this._errorHandler({position:i,code:s,currentState:this.currentState,collect:this._collect,params:this._params,abort:!1}).abort)return;break;case 7:const h=this._csiHandlers[this._collect<<8|s];let c=h?h.length-1:-1;for(;c>=0&&(o=h[c](this._params),!0!==o);c--)if(o instanceof Promise)return this._preserveStack(3,h,c,r,i),o;c<0&&this._csiHandlerFb(this._collect<<8|s,this._params),this.precedingJoinState=0;break;case 8:do{switch(s){case 59:this._params.addParam(0);break;case 58:this._params.addSubParam(-1);break;default:this._params.addDigit(s-48)}}while(++i47&&s<60);i--;break;case 9:this._collect<<=8,this._collect|=s;break;case 10:const d=this._escHandlers[this._collect<<8|s];let _=d?d.length-1:-1;for(;_>=0&&(o=d[_](),!0!==o);_--)if(o instanceof Promise)return this._preserveStack(4,d,_,r,i),o;_<0&&this._escHandlerFb(this._collect<<8|s),this.precedingJoinState=0;break;case 11:this._params.resetZdm(),this._collect=0;break;case 12:this._dcsParser.hook(this._collect<<8|s,this._params);break;case 13:for(let r=i+1;;++r)if(r>=t||24===(s=e[r])||26===s||27===s||s>127&&s=t||(s=e[r])<32||s>127&&s=32&&e[s]<127||e[s]>=8&&e[s]<14||e[s]>=l))){this._apcParser.put(e,i,s),i=s-1;break}break;case 17:if(o=this._apcParser.end(24!==s&&26!==s),o)return this._preserveStack(7,[],0,r,i),o;27===s&&(r|=1),this._params.resetZdm(),this._collect=0,this.precedingJoinState=0}this.currentState=255&r}}}t.EscapeSequenceParser=c},1346(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OscHandler=t.OscParser=void 0;const s=i(726),r=i(4220),o=[];t.OscParser=class{constructor(){this._state=0,this._active=o,this._id=-1,this._handlers=Object.create(null),this._handlerFb=()=>{},this._stack={paused:!1,loopPosition:0,fallThrough:!1}}registerHandler(e,t){this._handlers[e]??=[];const i=this._handlers[e];return i.push(t),{dispose:()=>{const e=i.indexOf(t);-1!==e&&i.splice(e,1)}}}clearHandler(e){this._handlers[e]&&delete this._handlers[e]}setHandlerFallback(e){this._handlerFb=e}dispose(){this._handlers=Object.create(null),this._handlerFb=()=>{},this._active=o}reset(){if(2===this._state)for(let e=this._stack.paused?this._stack.loopPosition-1:this._active.length-1;e>=0;--e)this._active[e].end(!1);this._stack.paused=!1,this._active=o,this._id=-1,this._state=0}_start(){if(this._active=this._handlers[this._id]||o,this._active.length)for(let e=this._active.length-1;e>=0;e--)this._active[e].start();else this._handlerFb(this._id,"START")}_put(e,t,i){if(this._active.length)for(let s=this._active.length-1;s>=0;s--)this._active[s].put(e,t,i);else this._handlerFb(this._id,"PUT",(0,s.utf32ToString)(e,t,i))}start(){this.reset(),this._state=1}put(e,t,i){if(3!==this._state){if(1===this._state)for(;t0&&this._put(e,t,i)}}end(e,t=!0){if(0!==this._state){if(3!==this._state)if(1===this._state&&this._start(),this._active.length){let i=!1,s=this._active.length-1,r=!1;if(this._stack.paused&&(s=this._stack.loopPosition-1,i=t,r=this._stack.fallThrough,this._stack.paused=!1),!r&&!1===i){for(;s>=0&&(i=this._active[s].end(e),!0!==i);s--)if(i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!1,i;s--}for(;s>=0;s--)if(i=this._active[s].end(!1),i instanceof Promise)return this._stack.paused=!0,this._stack.loopPosition=s,this._stack.fallThrough=!0,i}else this._handlerFb(this._id,"END",e);this._active=o,this._id=-1,this._state=0}}};class n{constructor(e){this._handler=e,this._data=new r.LimitedStringBuilder(n._payloadLimit),this._hitLimit=!1}start(){this._data.reset(),this._hitLimit=!1}put(e,t,i){this._hitLimit||this._data.append((0,s.utf32ToString)(e,t,i))&&(this._hitLimit=!0)}end(e){let t=!1;if(this._hitLimit)t=!1;else if(e&&(t=this._handler(this._data.toString()),t instanceof Promise))return t.then(e=>(this._data.reset(),this._hitLimit=!1,e));return this._data.reset(),this._hitLimit=!1,t}}t.OscHandler=n,n._payloadLimit=1e7},7262(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.Params=void 0;class i{static fromArray(e){const t=new i;if(!e.length)return t;for(let i=Array.isArray(e[0])?1:0;i256)throw new Error("maxSubParamsLength must not be greater than 256");this.params=new Int32Array(e),this.length=0,this._subParams=new Int32Array(t),this._subParamsLength=0,this._subParamsIdx=new Uint16Array(e),this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}clone(){const e=new i(this.maxLength,this.maxSubParamsLength);return e.params.set(this.params),e.length=this.length,e._subParams.set(this._subParams),e._subParamsLength=this._subParamsLength,e._subParamsIdx.set(this._subParamsIdx),e._rejectDigits=this._rejectDigits,e._rejectSubDigits=this._rejectSubDigits,e._digitIsSub=this._digitIsSub,e}toArray(){const e=[];for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&e.push(Array.prototype.slice.call(this._subParams,i,s))}return e}reset(){this.length=0,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1}resetZdm(){this.length=1,this._subParamsLength=0,this._rejectDigits=!1,this._rejectSubDigits=!1,this._digitIsSub=!1,this._subParamsIdx[0]=0,this.params[0]=0}addParam(e){if(this._digitIsSub=!1,this.length>=this.maxLength)this._rejectDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParamsIdx[this.length]=this._subParamsLength<<8|this._subParamsLength,this.params[this.length++]=e>2147483647?2147483647:e}}addSubParam(e){if(this._digitIsSub=!0,this.length)if(this._rejectDigits||this._subParamsLength>=this.maxSubParamsLength)this._rejectSubDigits=!0;else{if(e<-1)throw new Error("values less than -1 are not allowed");this._subParams[this._subParamsLength++]=e>2147483647?2147483647:e,this._subParamsIdx[this.length-1]++}}hasSubParams(e){return(255&this._subParamsIdx[e])-(this._subParamsIdx[e]>>8)>0}getSubParams(e){const t=this._subParamsIdx[e]>>8,i=255&this._subParamsIdx[e];return i-t>0?this._subParams.subarray(t,i):null}getSubParamsAll(){const e={};for(let t=0;t>8,s=255&this._subParamsIdx[t];s-i>0&&(e[t]=this._subParams.slice(i,s))}return e}addDigit(e){let t;if(this._rejectDigits||!(t=this._digitIsSub?this._subParamsLength:this.length)||this._digitIsSub&&this._rejectSubDigits)return;const i=this._digitIsSub?this._subParams:this.params,s=i[t-1];i[t-1]=~s?Math.min(10*s+e,2147483647):e}}t.Params=i},3027(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.AddonManager=void 0,t.AddonManager=class{constructor(){this._addons=[]}dispose(){for(let e=this._addons.length-1;e>=0;e--)this._addons[e].instance.dispose()}loadAddon(e,t){const i={instance:t,dispose:t.dispose,isDisposed:!1};this._addons.push(i),t.dispose=()=>this._wrappedAddonDispose(i),t.activate(e)}_wrappedAddonDispose(e){if(e.isDisposed)return;let t=-1;for(let i=0;i=this._line.length))return t?(this._line.loadCell(e,t),t):this._line.loadCell(e,new s.CellData)}translateToString(e,t,i){return this._line.translateToString(e,t,i)}}},5101(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.BufferNamespaceApi=void 0;const s=i(3235),r=i(4812),o=i(8636);class n extends r.Disposable{constructor(e){super(),this._core=e,this._onBufferChange=this._register(new o.Emitter),this.onBufferChange=this._onBufferChange.event,this._normal=new s.BufferApiView(this._core.buffers.normal,"normal"),this._alternate=new s.BufferApiView(this._core.buffers.alt,"alternate"),this._register(this._core.buffers.onBufferActivate(()=>this._onBufferChange.fire(this.active)))}get active(){if(this._core.buffers.active===this._core.buffers.normal)return this.normal;if(this._core.buffers.active===this._core.buffers.alt)return this.alternate;throw new Error("Active buffer is neither normal nor alternate")}get normal(){return this._normal.init(this._core.buffers.normal)}get alternate(){return this._alternate.init(this._core.buffers.alt)}}t.BufferNamespaceApi=n},6097(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.ParserApi=void 0,t.ParserApi=class{constructor(e){this._core=e}registerCsiHandler(e,t){return this._core.registerCsiHandler(e,e=>t(e.toArray()))}addCsiHandler(e,t){return this.registerCsiHandler(e,t)}registerDcsHandler(e,t){return this._core.registerDcsHandler(e,(e,i)=>t(e,i.toArray()))}addDcsHandler(e,t){return this.registerDcsHandler(e,t)}registerEscHandler(e,t){return this._core.registerEscHandler(e,t)}addEscHandler(e,t){return this.registerEscHandler(e,t)}registerOscHandler(e,t){return this._core.registerOscHandler(e,t)}addOscHandler(e,t){return this.registerOscHandler(e,t)}registerApcHandler(e,t){return this._core.registerApcHandler(e,t)}}},4335(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeApi=void 0,t.UnicodeApi=class{constructor(e){this._core=e}register(e){this._core.unicodeService.register(e)}get versions(){return this._core.unicodeService.versions}get activeVersion(){return this._core.unicodeService.activeVersion}set activeVersion(e){this._core.unicodeService.activeVersion=e}}},9640(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.BufferService=void 0;const o=i(4812),n=i(4097),a=i(6501),h=i(8636);let l=class extends o.Disposable{get buffer(){return this.buffers.active}constructor(e,t){super(),this.isUserScrolling=!1,this._onResize=this._register(new h.Emitter),this.onResize=this._onResize.event,this._onScroll=this._register(new h.Emitter),this.onScroll=this._onScroll.event,this.cols=Math.max(e.rawOptions.cols||0,2),this.rows=Math.max(e.rawOptions.rows||0,1),this.buffers=this._register(new n.BufferSet(e,this,t)),this._register(this.buffers.onBufferActivate(e=>{this._onScroll.fire(e.activeBuffer.ydisp)}))}resize(e,t){const i=this.cols!==e,s=this.rows!==t;this.cols=e,this.rows=t,this.buffers.resize(e,t),this._onResize.fire({cols:e,rows:t,colsChanged:i,rowsChanged:s})}reset(){this.buffers.reset(),this.isUserScrolling=!1}scroll(e,t=!1){const i=this.buffer;let s;s=this._cachedBlankLine,s&&s.length===this.cols&&s.getFg(0)===e.fg&&s.getBg(0)===e.bg||(s=i.getBlankLine(e,t),this._cachedBlankLine=s),s.isWrapped=t;const r=i.ybase+i.scrollTop,o=i.ybase+i.scrollBottom;if(0===i.scrollTop){const e=i.lines.isFull;o===i.lines.length-1?e?i.lines.recycle().copyFrom(s,!0):i.lines.push(s.clone(!0)):i.lines.splice(o+1,0,s.clone(!0)),e?this.isUserScrolling&&(i.ydisp=Math.max(i.ydisp-1,0)):(i.ybase++,this.isUserScrolling||i.ydisp++)}else{const e=o-r+1;i.lines.shiftElements(r+1,e-1,-1),i.lines.set(o,s.clone(!0))}this.isUserScrolling||(i.ydisp=i.ybase),this._onScroll.fire(i.ydisp)}scrollLines(e,t){const i=this.buffer;if(e<0){if(0===i.ydisp)return;this.isUserScrolling=!0}else e+i.ydisp>=i.ybase&&(this.isUserScrolling=!1);const s=i.ydisp;i.ydisp=Math.max(Math.min(i.ydisp+e,i.ybase),0),s!==i.ydisp&&(t||this._onScroll.fire(i.ydisp))}};t.BufferService=l,t.BufferService=l=s([r(0,a.IOptionsService),r(1,a.ILogService)],l)},5746(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.CharsetService=void 0,t.CharsetService=class{constructor(){this.glevel=0,this._charsets=[]}get charsets(){return this._charsets}reset(){this.charset=void 0,this._charsets=[],this.glevel=0}setgLevel(e){this.glevel=e,this.charset=this._charsets[e]}setgCharset(e,t){this._charsets[e]=t,this.glevel===e&&(this.charset=t)}}},4071(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.CoreService=void 0;const o=i(4812),n=i(6501),a=i(8636),h=Object.freeze({insertMode:!1}),l=Object.freeze({applicationCursorKeys:!1,applicationKeypad:!1,bracketedPasteMode:!1,colorSchemeUpdates:!1,cursorBlink:void 0,cursorStyle:void 0,origin:!1,reverseWraparound:!1,sendFocus:!1,synchronizedOutput:!1,win32InputMode:!1,wraparound:!0});let c=class extends o.Disposable{constructor(e,t,i){super(),this._bufferService=e,this._logService=t,this._optionsService=i,this.isCursorHidden=!1,this._onData=this._register(new a.Emitter),this.onData=this._onData.event,this._onUserInput=this._register(new a.Emitter),this.onUserInput=this._onUserInput.event,this._onBinary=this._register(new a.Emitter),this.onBinary=this._onBinary.event,this._onRequestScrollToBottom=this._register(new a.Emitter),this.onRequestScrollToBottom=this._onRequestScrollToBottom.event,this.isCursorInitialized=i.rawOptions.showCursorImmediately??!1,this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}reset(){this.modes=structuredClone(h),this.decPrivateModes=structuredClone(l),this.kittyKeyboard={flags:0,mainFlags:0,altFlags:0,mainStack:[],altStack:[]}}triggerDataEvent(e,t=!1){if(this._optionsService.rawOptions.disableStdin)return;const i=this._bufferService.buffer;t&&this._optionsService.rawOptions.scrollOnUserInput&&i.ybase!==i.ydisp&&this._onRequestScrollToBottom.fire(),t&&this._onUserInput.fire(),this._logService.debug(`sending data "${e}"`),this._logService.trace("sending data (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onData.fire(e)}triggerBinaryEvent(e){this._optionsService.rawOptions.disableStdin||(this._logService.debug(`sending binary "${e}"`),this._logService.trace("sending binary (codes)",()=>e.split("").map(e=>e.charCodeAt(0))),this._onBinary.fire(e))}};t.CoreService=c,t.CoreService=c=s([r(0,n.IBufferService),r(1,n.ILogService),r(2,n.IOptionsService)],c)},4720(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.DecorationLineCache=t.DecorationService=void 0;const o=i(3132),n=i(4103),a=i(4812),h=i(6501),l=i(3087),c=i(8636);let d=0,_=0,u=class extends a.Disposable{get decorations(){return this._decorations.values()}constructor(e,t){super(),this._logService=e,this._bufferService=t,this._lineCache=this._register(new f),this._onDecorationRegistered=this._register(new c.Emitter),this.onDecorationRegistered=this._onDecorationRegistered.event,this._onDecorationRemoved=this._register(new c.Emitter),this.onDecorationRemoved=this._onDecorationRemoved.event,this._decorations=new l.SortedList(e=>e?.marker.line,this._logService),this._register((0,a.toDisposable)(()=>this.reset())),this._register(this._bufferService.buffers.onBufferActivate(()=>{this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)})),this._lineCache.attachToBufferLines(this._bufferService.buffer.lines)}registerDecoration(e){if(e.marker.isDisposed)return;const t=new p(e);if(t){const e=t.marker.onDispose(()=>t.dispose()),i=t.onDispose(()=>{i.dispose(),t&&(this._decorations.delete(t)&&(this._lineCache.remove(t),this._onDecorationRemoved.fire(t)),e.dispose())});this._decorations.insert(t),this._lineCache.add(t),this._onDecorationRegistered.fire(t)}return t}reset(){for(const e of this._decorations.values())e.dispose();this._decorations.clear(),this._lineCache.clear()}*getDecorationsAtCell(e,t,i){const s=this._lineCache.getDecorationsOnLine(t);if(s)for(const t of s)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&(yield t)}forEachDecorationAtCell(e,t,i,s){const r=this._lineCache.getDecorationsOnLine(t);if(r)for(const t of r)d=t.options.x??0,_=d+(t.options.width??1),e>=d&&e<_&&(!i||(t.options.layer??"bottom")===i)&&s(t)}};t.DecorationService=u,t.DecorationService=u=s([r(0,h.ILogService),r(1,h.IBufferService)],u);class f extends a.Disposable{constructor(){super(...arguments),this._decorationsByLine=new Map,this._decorations=new Set,this._bufferLineListeners=this._register(new a.MutableDisposable),this._lineIndexSyncTimer=this._register(new o.MicrotaskTimer),this._lineIndexSyncCallbacks=[]}clear(){this._lineIndexSyncCallbacks.length=0,this._lineIndexSyncTimer.cancel(),this._decorationsByLine.clear(),this._decorations.clear()}add(e){this._decorations.add(e),this._addToLineBuckets(e)}remove(e){this._decorations.delete(e),this._removeFromLineBuckets(e)}getDecorationsOnLine(e){return this._decorationsByLine.get(e)}attachToBufferLines(e){const t=new a.DisposableStore;this._bufferLineListeners.value=t,t.add(e.onTrim(e=>this._handleBufferLinesTrim(e))),t.add(e.onInsert(e=>this._handleBufferLinesInsert(e))),t.add(e.onDelete(e=>this._handleBufferLinesDelete(e)))}_getDecorationHeight(e){return e.options.height??1}_addToLineBuckets(e){const t=e.marker.line;if(t<0)return;e._indexedStartLine=t;const i=this._getDecorationHeight(e);for(let s=t;s=0&&this._addToLineBuckets(e)}_scheduleLineIndexSync(e){this._lineIndexSyncCallbacks.push(e),this._lineIndexSyncTimer.set(()=>{const e=this._lineIndexSyncCallbacks;this._lineIndexSyncCallbacks=[];for(const t of e)t()})}_handleBufferLinesTrim(e){if(e<=0||!this._decorationsByLine.size)return;const t=new Map;for(const[i,s]of this._decorationsByLine){const r=i-e;r<0||this._mergeLineBucket(t,r,s)}this._decorationsByLine.clear();for(const[e,i]of t)this._decorationsByLine.set(e,i);for(const t of this._decorations)t.marker.isDisposed||(t._indexedStartLine-=e)}_handleBufferLinesInsert(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesInsert(e))}_handleBufferLinesDelete(e){this._scheduleLineIndexSync(()=>this._applyBufferLinesDelete(e))}_mergeLineBucket(e,t,i){const s=e.get(t);if(s)for(let e=0,t=i.length;et&&(s.push(e),this._removeFromLineBuckets(e))}const r=new Map;for(const[e,s]of this._decorationsByLine){const o=e>=t?e+i:e;this._mergeLineBucket(r,o,s)}this._decorationsByLine.clear();for(const[e,t]of r)this._decorationsByLine.set(e,t);for(const e of this._decorations)e.marker.isDisposed||e._indexedStartLine>=t&&(e._indexedStartLine=e.marker.line);for(const e of s)this._addToLineBuckets(e)}_applyBufferLinesDelete(e){const t=e.index+e.amount,i=new Map;for(const[s,r]of this._decorationsByLine){if(s>=e.index&&s=t?s-e.amount:s;this._mergeLineBucket(i,o,r)}this._decorationsByLine.clear();for(const[e,t]of i)this._decorationsByLine.set(e,t);const s=[];for(const i of this._decorations){if(i.marker.isDisposed)continue;const r=i._indexedStartLine,o=this._getDecorationHeight(i);r>=t?i._indexedStartLine=i.marker.line:rt&&s.push(i)}for(const e of s)this._reindexDecoration(e)}}t.DecorationLineCache=f;class p extends a.DisposableStore{get backgroundColorRGB(){return null===this._cachedBg&&(this.options.backgroundColor?this._cachedBg=n.css.toColor(this.options.backgroundColor):this._cachedBg=void 0),this._cachedBg}get foregroundColorRGB(){return null===this._cachedFg&&(this.options.foregroundColor?this._cachedFg=n.css.toColor(this.options.foregroundColor):this._cachedFg=void 0),this._cachedFg}constructor(e){super(),this.options=e,this.onRenderEmitter=this.add(new c.Emitter),this.onRender=this.onRenderEmitter.event,this._onDispose=this.add(new c.Emitter),this.onDispose=this._onDispose.event,this._cachedBg=null,this._cachedFg=null,this.marker=e.marker,this._indexedStartLine=e.marker.line,this.options.overviewRulerOptions&&!this.options.overviewRulerOptions.position&&(this.options.overviewRulerOptions.position="full")}dispose(){this._onDispose.fire(),super.dispose()}}},6025(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.InstantiationService=t.ServiceCollection=void 0;const s=i(6501),r=i(6201);class o{constructor(...e){this._entries=new Map;for(const[t,i]of e)this.set(t,i)}set(e,t){const i=this._entries.get(e);return this._entries.set(e,t),i}forEach(e){for(const[t,i]of this._entries.entries())e(t,i)}has(e){return this._entries.has(e)}get(e){return this._entries.get(e)}}t.ServiceCollection=o,t.InstantiationService=class{constructor(){this._services=new o,this._services.set(s.IInstantiationService,this)}setService(e,t){this._services.set(e,t)}getService(e){return this._services.get(e)}createInstance(e,...t){const i=(0,r.getServiceDependencies)(e).sort((e,t)=>e.index-t.index),s=[];for(const t of i){const i=this._services.get(t.id);if(!i)throw new Error(`[createInstance] ${e.name} depends on UNKNOWN service ${t.id._id}.`);s.push(i)}const o=i.length>0?i[0].index:t.length;if(t.length!==o)throw new Error(`[createInstance] First service dependency of ${e.name} at position ${o+1} conflicts with ${t.length} static arguments`);return new e(...[...t,...s])}}},7276(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.LogService=void 0;const o=i(4812),n=i(6501),a={trace:n.LogLevelEnum.TRACE,debug:n.LogLevelEnum.DEBUG,info:n.LogLevelEnum.INFO,warn:n.LogLevelEnum.WARN,error:n.LogLevelEnum.ERROR,off:n.LogLevelEnum.OFF};let h=class extends o.Disposable{get logLevel(){return this._logLevel}constructor(e){super(),this._optionsService=e,this._logLevel=n.LogLevelEnum.OFF,this._updateLogLevel(),this._register(this._optionsService.onSpecificOptionChange("logLevel",()=>this._updateLogLevel()))}_updateLogLevel(){this._logLevel=a[this._optionsService.rawOptions.logLevel]}_evalLazyOptionalParams(e){for(let t=0;t!1},X10:{events:1,restrict:e=>4!==e.button&&1===e.action&&(e.ctrl=!1,e.alt=!1,e.shift=!1,!0)},VT200:{events:19,restrict:e=>32!==e.action},DRAG:{events:23,restrict:e=>32!==e.action||3!==e.button},ANY:{events:31,restrict:e=>!0}};function n(e,t){let i=(e.ctrl?16:0)|(e.shift?4:0)|(e.alt?8:0);return 4===e.button?(i|=64,i|=e.action):(i|=3&e.button,4&e.button&&(i|=64),8&e.button&&(i|=128),32===e.action?i|=32:0!==e.action||t||(i|=3)),i}const a=String.fromCharCode,h={DEFAULT:e=>{const t=[n(e,!1)+32,e.col+32,e.row+32];return t[0]>255||t[1]>255||t[2]>255?"":`[M${a(t[0])}${a(t[1])}${a(t[2])}`},SGR:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.col};${e.row}${t}`},SGR_PIXELS:e=>{const t=0===e.action&&4!==e.button?"m":"M";return`[<${n(e,!0)};${e.x};${e.y}${t}`}};class l extends s.Disposable{constructor(){super(),this._protocols={},this._encodings={},this._activeProtocol="",this._activeEncoding="",this._onProtocolChange=this._register(new r.Emitter),this.onProtocolChange=this._onProtocolChange.event;for(const e of Object.keys(o))this.addProtocol(e,o[e]);for(const e of Object.keys(h))this.addEncoding(e,h[e]);this.reset()}addProtocol(e,t){this._protocols[e]=t}addEncoding(e,t){this._encodings[e]=t}get activeProtocol(){return this._activeProtocol}get areMouseEventsActive(){return 0!==this._protocols[this._activeProtocol].events}set activeProtocol(e){if(!this._protocols[e])throw new Error(`unknown protocol "${e}"`);this._activeProtocol=e,this._onProtocolChange.fire(this._protocols[e].events)}get activeEncoding(){return this._activeEncoding}set activeEncoding(e){if(!this._encodings[e])throw new Error(`unknown encoding "${e}"`);this._activeEncoding=e}reset(){this.activeProtocol="NONE",this.activeEncoding="DEFAULT"}setCustomWheelEventHandler(e){this._customWheelEventHandler=e}allowCustomWheelEvent(e){return!this._customWheelEventHandler||!1!==this._customWheelEventHandler(e)}restrictMouseEvent(e){return this._protocols[this._activeProtocol].restrict(e)}encodeMouseEvent(e){return this._encodings[this._activeEncoding](e)}get isDefaultEncoding(){return"DEFAULT"===this._activeEncoding}get isPixelEncoding(){return"SGR_PIXELS"===this._activeEncoding}}t.MouseStateService=l},56(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.OptionsService=t.DEFAULT_OPTIONS=void 0;const s=i(4812),r=i(701),o=i(8636);t.DEFAULT_OPTIONS={cols:80,rows:24,showCursorImmediately:!1,cursorBlink:!1,blinkIntervalDuration:0,cursorStyle:"block",cursorWidth:1,cursorInactiveStyle:"outline",drawBoldTextInBrightColors:!0,documentOverride:null,fastScrollSensitivity:5,fontFamily:"monospace",fontSize:15,fontWeight:"normal",fontWeightBold:"bold",ignoreBracketedPasteMode:!1,lineHeight:1,letterSpacing:0,linkHandler:null,logLevel:"info",logger:null,scrollback:1e3,scrollbar:{showScrollbar:!0},scrollOnEraseInDisplay:!1,scrollOnUserInput:!0,scrollSensitivity:1,screenReaderMode:!1,smoothScrollDuration:0,macOptionIsMeta:!1,macOptionClickForcesSelection:!1,minimumContrastRatio:1,mouseEventsRequireAlt:!1,disableStdin:!1,allowProposedApi:!1,allowTransparency:!1,tabStopWidth:8,theme:{},reflowCursorLine:!1,rescaleOverlappingGlyphs:!1,rightClickSelectsWord:r.isMac,windowOptions:{},windowsPty:{},wordSeparator:" ()[]{}',\"`",altClickMovesCursor:!0,convertEol:!1,termName:"xterm",quirks:{},vtExtensions:{}};const n=["normal","bold","100","200","300","400","500","600","700","800","900"];class a extends s.Disposable{constructor(e){super(),this._onOptionChange=this._register(new o.Emitter),this.onOptionChange=this._onOptionChange.event;const i={...t.DEFAULT_OPTIONS};for(const t in e)if(t in i)try{const s=e[t];i[t]=this._sanitizeAndValidateOption(t,s)}catch(e){console.error(e)}this.rawOptions=i,this.options={...i},this._setupOptions(),this._register((0,s.toDisposable)(()=>{this.rawOptions.linkHandler=null,this.rawOptions.documentOverride=null}))}onSpecificOptionChange(e,t){return this.onOptionChange(i=>{i===e&&t(this.rawOptions[e])})}onMultipleOptionChange(e,t){return this.onOptionChange(i=>{-1!==e.indexOf(i)&&t()})}_setupOptions(){const e=e=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);return this.rawOptions[e]},i=(e,i)=>{if(!(e in t.DEFAULT_OPTIONS))throw new Error(`No option with key "${e}"`);i=this._sanitizeAndValidateOption(e,i),this.rawOptions[e]!==i&&(this.rawOptions[e]=i,this._onOptionChange.fire(e))};for(const t in this.rawOptions){const s={get:e.bind(this,t),set:i.bind(this,t)};Object.defineProperty(this.options,t,s)}}_sanitizeAndValidateOption(e,i){switch(e){case"cursorStyle":if(i||(i=t.DEFAULT_OPTIONS[e]),!function(e){return"block"===e||"underline"===e||"bar"===e}(i))throw new Error(`"${i}" is not a valid value for ${e}`);break;case"wordSeparator":i||(i=t.DEFAULT_OPTIONS[e]);break;case"fontWeight":case"fontWeightBold":if("number"==typeof i&&1<=i&&i<=1e3)break;i=n.includes(i)?i:t.DEFAULT_OPTIONS[e];break;case"blinkIntervalDuration":if((i=Math.floor(i))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"cursorWidth":i=Math.floor(i);case"lineHeight":case"tabStopWidth":if(i<1)throw new Error(`${e} cannot be less than 1, value: ${i}`);break;case"minimumContrastRatio":i=Math.max(1,Math.min(21,Math.round(10*i)/10));break;case"scrollback":if((i=Math.min(i,4294967295))<0)throw new Error(`${e} cannot be less than 0, value: ${i}`);break;case"fastScrollSensitivity":case"scrollSensitivity":if(i<=0)throw new Error(`${e} cannot be less than or equal to 0, value: ${i}`);break;case"rows":case"cols":if(!i&&0!==i)throw new Error(`${e} must be numeric, value: ${i}`);break;case"windowsPty":i=i??{}}return i}}t.OptionsService=a},8811(e,t,i){var s=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},r=this&&this.__param||function(e,t){return function(i,s){t(i,s,e)}};Object.defineProperty(t,"__esModule",{value:!0}),t.OscLinkService=void 0;const o=i(6501);let n=class{constructor(e){this._bufferService=e,this._nextId=1,this._entriesWithId=new Map,this._dataByLinkId=new Map}registerLink(e){const t=this._bufferService.buffer;if(void 0===e.id){const i=t.addMarker(t.ybase+t.y),s={data:e,id:this._nextId++,lines:[i]};return i.onDispose(()=>this._removeMarkerFromLink(s,i)),this._dataByLinkId.set(s.id,s),s.id}const i=e,s=this._getEntryIdKey(i),r=this._entriesWithId.get(s);if(r)return this.addLineToLink(r.id,t.ybase+t.y),r.id;const o=t.addMarker(t.ybase+t.y),n={id:this._nextId++,key:this._getEntryIdKey(i),data:i,lines:[o]};return o.onDispose(()=>this._removeMarkerFromLink(n,o)),this._entriesWithId.set(n.key,n),this._dataByLinkId.set(n.id,n),n.id}addLineToLink(e,t){const i=this._dataByLinkId.get(e);if(i&&i.lines.every(e=>e.line!==t)){const e=this._bufferService.buffer.addMarker(t);i.lines.push(e),e.onDispose(()=>this._removeMarkerFromLink(i,e))}}getLinkData(e){return this._dataByLinkId.get(e)?.data}_getEntryIdKey(e){return`${e.id};;${e.uri}`}_removeMarkerFromLink(e,t){const i=e.lines.indexOf(t);-1!==i&&(e.lines.splice(i,1),0===e.lines.length&&(void 0!==e.data.id&&this._entriesWithId.delete(e.key),this._dataByLinkId.delete(e.id)))}};t.OscLinkService=n,t.OscLinkService=n=s([r(0,o.IBufferService)],n)},6201(e,t){Object.defineProperty(t,"__esModule",{value:!0}),t.serviceRegistry=void 0,t.getServiceDependencies=function(e){return e.di$dependencies||[]},t.createDecorator=function(e){if(t.serviceRegistry.has(e))return t.serviceRegistry.get(e);const i=function(e,t,s){if(3!==arguments.length)throw new Error("@IServiceName-decorator can only be used to decorate a parameter");!function(e,t,i){t.di$target===t?t.di$dependencies.push({id:e,index:i}):(t.di$dependencies=[{id:e,index:i}],t.di$target=t)}(i,e,s)};return i._id=e,t.serviceRegistry.set(e,i),i},t.serviceRegistry=new Map},6501(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.IDecorationService=t.IUnicodeService=t.IOscLinkService=t.IOptionsService=t.ILogService=t.LogLevelEnum=t.IInstantiationService=t.ICharsetService=t.ICoreService=t.IMouseStateService=t.IBufferService=void 0;const s=i(6201);var r;t.IBufferService=(0,s.createDecorator)("BufferService"),t.IMouseStateService=(0,s.createDecorator)("MouseStateService"),t.ICoreService=(0,s.createDecorator)("CoreService"),t.ICharsetService=(0,s.createDecorator)("CharsetService"),t.IInstantiationService=(0,s.createDecorator)("InstantiationService"),function(e){e[e.TRACE=0]="TRACE",e[e.DEBUG=1]="DEBUG",e[e.INFO=2]="INFO",e[e.WARN=3]="WARN",e[e.ERROR=4]="ERROR",e[e.OFF=5]="OFF"}(r||(t.LogLevelEnum=r={})),t.ILogService=(0,s.createDecorator)("LogService"),t.IOptionsService=(0,s.createDecorator)("OptionsService"),t.IOscLinkService=(0,s.createDecorator)("OscLinkService"),t.IUnicodeService=(0,s.createDecorator)("UnicodeService"),t.IDecorationService=(0,s.createDecorator)("DecorationService")},6415(e,t,i){Object.defineProperty(t,"__esModule",{value:!0}),t.UnicodeService=void 0;const s=i(8636);class r{constructor(){this._providers=Object.create(null),this._active="",this._onChange=new s.Emitter,this.onChange=this._onChange.event}static extractShouldJoin(e){return!!(1&e)}static extractWidth(e){return e>>1&3}static extractCharKind(e){return e>>3}static createPropertyValue(e,t,i=!1){return(16777215&e)<<3|(3&t)<<1|(i?1:0)}dispose(){this._onChange.dispose()}get versions(){return Object.keys(this._providers)}get activeVersion(){return this._active}set activeVersion(e){if(!this._providers[e])throw new Error(`unknown Unicode version "${e}"`);this._active=e,this._activeProvider=this._providers[e],this._onChange.fire(e)}register(e){this._providers[e.version]=e,this._active||(this.activeVersion=e.version)}wcwidth(e){return this._activeProvider.wcwidth(e)}getStringCellWidth(e){let t=0,i=0;const s=e.length;for(let o=0;o=s)return t+this.wcwidth(n);const i=e.charCodeAt(o);56320<=i&&i<=57343?n=1024*(n-55296)+i-56320+65536:t+=this.wcwidth(i)}const a=this.charProperties(n,i);let h=r.extractWidth(a);r.extractShouldJoin(a)&&(h-=r.extractWidth(i)),t+=h,i=a}return t}charProperties(e,t){return this._activeProvider.charProperties(e,t)}}t.UnicodeService=r}},t={};return function i(s){var r=t[s];if(void 0!==r)return r.exports;var o=t[s]={exports:{}};return e[s].call(o.exports,o,o.exports,i),o.exports}(6081)})());
-+!function(e,t){if("object"==typeof exports&&"object"==typeof module)module.exports=t();else if("function"==typeof define&&define.amd)define([],t);else{var i=t();for(var s in i)("object"==typeof exports?exports:e)[s]=i[s]}}(globalThis,()=>(()=>{"use strict";var e={2840(e,t,i){var s,r=this&&this.__createBinding||(Object.create?function(e,t,i,s){void 0===s&&(s=i);var r=Object.getOwnPropertyDescriptor(t,i);r&&!("get"in r?!t.__esModule:r.writable||r.configurable)||(r={enumerable:!0,get:function(){return t[i]}}),Object.defineProperty(e,s,r)}:function(e,t,i,s){void 0===s&&(s=i),e[s]=t[i]}),o=this&&this.__setModuleDefault||(Object.create?function(e,t){Object.defineProperty(e,"default",{enumerable:!0,value:t})}:function(e,t){e.default=t}),n=this&&this.__decorate||function(e,t,i,s){var r,o=arguments.length,n=o<3?t:null===s?s=Object.getOwnPropertyDescriptor(t,i):s;if("object"==typeof Reflect&&"function"==typeof Reflect.decorate)n=Reflect.decorate(e,t,i,s);else for(var a=e.length-1;a>=0;a--)(r=e[a])&&(n=(o<3?r(n):o>3?r(t,i,n):r(t,i))||n);return o>3&&n&&Object.defineProperty(t,i,n),n},a=this&&this.__importStar||(s=function(e){return s=Object.getOwnPropertyNames||function(e){var t=[];for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&(t[t.length]=i);return t},s(e)},function(e){if(e&&e.__esModule)return e;var t={};if(null!=e)for(var i=s(e),n=0;nthis._handleBoundaryFocus(e,0),this._bottomBoundaryFocusListener=e=>this._handleBoundaryFocus(e,1),this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._accessibilityContainer.appendChild(this._rowContainer),this._liveRegion=r.createElement("div"),this._liveRegion.classList.add("live-region"),this._liveRegion.setAttribute("aria-live","assertive"),this._accessibilityContainer.appendChild(this._liveRegion),this._liveRegionDebouncer=this._register(new c.TimeBasedDebouncer(this._renderRows.bind(this))),!this._terminal.element)throw new Error("Cannot enable accessibility before Terminal.open");this._terminal.element.insertAdjacentElement("afterbegin",this._accessibilityContainer),this._register(this._terminal.onResize(e=>this._handleResize(e.rows))),this._register(this._terminal.onRender(e=>this._refreshRows(e.start,e.end))),this._register(this._terminal.onScroll(()=>this._refreshRows())),this._register(this._terminal.onA11yChar(e=>this._handleChar(e))),this._register(this._terminal.onLineFeed(()=>this._handleChar("\n"))),this._register(this._terminal.onA11yTab(e=>this._handleTab(e))),this._register(this._terminal.onKey(e=>this._handleKey(e.key))),this._register(this._terminal.onBlur(()=>this._clearLiveRegion())),this._register(this._renderService.onDimensionsChange(()=>this._refreshRowsDimensions())),this._register((0,f.addDisposableListener)(r,"selectionchange",()=>this._handleSelectionChange())),this._register(this._coreBrowserService.onDprChange(()=>this._refreshRowsDimensions())),this._refreshRowsDimensions(),this._refreshRows(),this._register((0,d.toDisposable)(()=>{this._accessibilityContainer.remove(),this._rowElements.length=0}))}_handleTab(e){for(let t=0;t0?this._charsToConsume.shift()!==e&&(this._charsToAnnounce+=e):this._charsToAnnounce+=e,"\n"===e&&(this._liveRegionLineCount++,21===this._liveRegionLineCount&&(this._liveRegion.textContent=l.tooMuchOutput.get())))}_clearLiveRegion(){this._liveRegion.textContent="",this._liveRegionLineCount=0}_handleKey(e){this._clearLiveRegion(),/\p{Control}/u.test(e)||this._charsToConsume.push(e)}_refreshRows(e,t){this._liveRegionDebouncer.refresh(e,t,this._terminal.rows)}_renderRows(e,t){const i=this._terminal.buffer,s=i.lines.length.toString();for(let r=e;r<=t;r++){const e=i.lines.get(i.ydisp+r),t=[],o=e?.translateToString(!0,void 0,void 0,t)||"",n=(i.ydisp+r+1).toString(),a=this._rowElements[r];a&&(0===o.length?(a.textContent=" ",this._rowColumns.set(a,[0,1])):(a.textContent=o,this._rowColumns.set(a,t)),a.setAttribute("aria-posinset",n),a.setAttribute("aria-setsize",s),this._alignRowWidth(a))}this._announceCharacters()}_announceCharacters(){0!==this._charsToAnnounce.length&&(this._liveRegion.textContent===l.tooMuchOutput.get()&&this._clearLiveRegion(),this._liveRegion.textContent+=this._charsToAnnounce,this._charsToAnnounce="")}_handleBoundaryFocus(e,t){const i=e.target,s=this._rowElements[0===t?1:this._rowElements.length-2];if(i.getAttribute("aria-posinset")===(0===t?"1":`${this._terminal.buffer.lines.length}`))return;if(e.relatedTarget!==s)return;let r,o;if(0===t?(r=i,o=this._rowElements.pop(),this._rowContainer.removeChild(o)):(r=this._rowElements.shift(),o=i,this._rowContainer.removeChild(r)),r.removeEventListener("focus",this._topBoundaryFocusListener),o.removeEventListener("focus",this._bottomBoundaryFocusListener),0===t){const e=this._createAccessibilityTreeNode();this._rowElements.unshift(e),this._rowContainer.insertAdjacentElement("afterbegin",e)}else{const e=this._createAccessibilityTreeNode();this._rowElements.push(e),this._rowContainer.appendChild(e)}this._rowElements[0].addEventListener("focus",this._topBoundaryFocusListener),this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._terminal.scrollLines(0===t?-1:1),this._rowElements[0===t?1:this._rowElements.length-2].focus(),e.preventDefault(),e.stopImmediatePropagation()}_handleSelectionChange(){if(0===this._rowElements.length)return;const e=this._coreBrowserService.mainDocument.getSelection();if(!e)return;if(e.isCollapsed)return void(this._rowContainer.contains(e.anchorNode)&&this._terminal.clearSelection());if(!e.anchorNode||!e.focusNode)return void console.error("anchorNode and/or focusNode are null");let t={node:e.anchorNode,offset:e.anchorOffset},i={node:e.focusNode,offset:e.focusOffset};if((t.node.compareDocumentPosition(i.node)&Node.DOCUMENT_POSITION_PRECEDING||t.node===i.node&&t.offset>i.offset)&&([t,i]=[i,t]),t.node.compareDocumentPosition(this._rowElements[0])&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_FOLLOWING)&&(t={node:this._rowElements[0].childNodes[0],offset:0}),!this._rowContainer.contains(t.node))return;const s=this._rowElements.slice(-1)[0];if(i.node.compareDocumentPosition(s)&(Node.DOCUMENT_POSITION_CONTAINED_BY|Node.DOCUMENT_POSITION_PRECEDING)&&(i={node:s,offset:s.textContent?.length??0}),!this._rowContainer.contains(i.node))return;const r=({node:e,offset:t})=>{const i=e instanceof Text?e.parentNode:e;let s=parseInt(i?.getAttribute("aria-posinset"),10)-1;if(isNaN(s))return console.warn("row is invalid. Race condition?"),null;const r=this._rowColumns.get(i);if(!r)return console.warn("columns is null. Race condition?"),null;let o=t=this._terminal.cols&&(++s,o=0),{row:s,column:o}},o=r(t),n=r(i);if(o&&n){if(o.row>n.row||o.row===n.row&&o.column>=n.column)throw new Error("invalid range");this._terminal.select(o.column,o.row,(n.row-o.row)*this._terminal.cols-o.column+n.column)}}_handleResize(e){this._rowElements[this._rowElements.length-1].removeEventListener("focus",this._bottomBoundaryFocusListener);for(let e=this._rowContainer.children.length;ee;)this._rowContainer.removeChild(this._rowElements.pop());this._rowElements[this._rowElements.length-1].addEventListener("focus",this._bottomBoundaryFocusListener),this._refreshRowsDimensions()}_createAccessibilityTreeNode(){const e=this._coreBrowserService.mainDocument.createElement("div");return e.setAttribute("role","listitem"),e.tabIndex=-1,this._refreshRowDimensions(e),e}_refreshRowsDimensions(){if(this._renderService.dimensions.css.cell.height){Object.assign(this._accessibilityContainer.style,{width:`${this._renderService.dimensions.css.canvas.width}px`,fontSize:`${this._terminal.options.fontSize}px`}),this._rowElements.length!==this._terminal.rows&&this._handleResize(this._terminal.rows);for(let e=0;ethis._onBell.fire())),this._register(this._inputHandler.onRequestRefreshRows(e=>this.refresh(e?.start??0,e?.end??this.rows-1))),this._register(this._inputHandler.onRequestSendFocus(()=>this._reportFocus())),this._register(this._inputHandler.onRequestReset(()=>this.reset())),this._register(this._inputHandler.onRequestWindowsOptionsReport(e=>this._reportWindowsOptions(e))),this._register(this._inputHandler.onColor(e=>this._handleColorEvent(e))),this._register(I.EventUtils.forward(this._inputHandler.onCursorMove,this._onCursorMove)),this._register(I.EventUtils.forward(this._inputHandler.onTitleChange,this._onTitleChange)),this._register(I.EventUtils.forward(this._inputHandler.onA11yChar,this._onA11yCharEmitter)),this._register(I.EventUtils.forward(this._inputHandler.onA11yTab,this._onA11yTabEmitter)),this._register(this._bufferService.onResize(e=>this._afterResize(e.cols,e.rows))),this._register((0,N.toDisposable)(()=>{this._customKeyEventHandler=void 0,this.element?.parentNode?.removeChild(this.element)}))}_handleColorEvent(e){if(this._themeService)for(const t of e){let e,i;switch(t.index){case 256:e="foreground",i="10";break;case 257:e="background",i="11";break;case 258:e="cursor",i="12";break;default:e="ansi",i="4;"+t.index}switch(t.type){case 0:const s=E.color.toColorRGB("ansi"===e?this._themeService.colors.ansi[t.index]:this._themeService.colors[e]);this.coreService.triggerDataEvent(`]${i};${(0,R.toRgbString)(s)}\\`);break;case 1:if("ansi"===e)this._themeService.modifyColors(e=>e.ansi[t.index]=E.channels.toColor(...t.color));else{const i=e;this._themeService.modifyColors(e=>e[i]=E.channels.toColor(...t.color))}break;case 2:this._themeService.restoreColor(t.index)}}}_reportColorScheme(){if(!this._themeService)return;const e=E.rgb.relativeLuminance(this._themeService.colors.background.rgba>>8)>8)?1:2;this.coreService.triggerDataEvent(`[?997;${e}n`)}_setup(){super._setup(),this._customKeyEventHandler=void 0}get buffer(){return this.buffers.active}focus(){this.textarea&&this.textarea.focus({preventScroll:!0})}_handleScreenReaderModeOptionChange(e){e?!this._accessibilityManager.value&&this._renderService&&(this._accessibilityManager.value=this._instantiationService.createInstance(A.AccessibilityManager,this)):this._accessibilityManager.clear()}_handleTextAreaFocus(e){this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent("[I"),this.element.classList.add("focus"),this._showCursor(),this._onFocus.fire()}blur(){return this.textarea?.blur()}_handleTextAreaBlur(){this._compositionHelper instanceof u.CompositionHelper&&this._compositionHelper.blur(),this.textarea.value="",this.refresh(this.buffer.y,this.buffer.y),this.coreService.decPrivateModes.sendFocus&&this.coreService.triggerDataEvent("[O"),this.element.classList.remove("focus"),this._onBlur.fire()}_syncTextArea(){if(!this.textarea||!this.buffer.isCursorInViewport||this._compositionHelper.isComposing||!this._renderService)return;const e=this.buffer.ybase+this.buffer.y,t=this.buffer.lines.get(e);if(!t)return;const i=Math.min(this.buffer.x,this.cols-1),s=this._renderService.dimensions.css.cell.height,r=t.getWidth(i),o=this._renderService.dimensions.css.cell.width*r,n=this.buffer.y*this._renderService.dimensions.css.cell.height,a=i*this._renderService.dimensions.css.cell.width;this.textarea.style.left=a+"px",this.textarea.style.top=n+"px",this.textarea.style.width=o+"px",this.textarea.style.height=s+"px",this.textarea.style.lineHeight=s+"px",this.textarea.style.zIndex="-5"}_initGlobal(){this._bindKeys(),this._register((0,H.addDisposableListener)(this.element,"copy",e=>{this.hasSelection()&&(0,a.copyHandler)(e,this._selectionService)}));const e=e=>(0,a.handlePasteEvent)(e,this.textarea,this.coreService,this.optionsService);this._register((0,H.addDisposableListener)(this.textarea,"paste",e)),this._register((0,H.addDisposableListener)(this.element,"paste",e)),L.isFirefox?this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>{2===e.button&&(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})):this._register((0,H.addDisposableListener)(this.element,"contextmenu",e=>{(0,a.rightClickHandler)(e,this.textarea,this.screenElement,this._selectionService,this.options.rightClickSelectsWord)})),L.isLinux&&this._register((0,H.addDisposableListener)(this.element,"auxclick",e=>{1===e.button&&(0,a.moveTextAreaUnderMouseCursor)(e,this.textarea,this.screenElement)}))}_bindKeys(){this._register((0,H.addDisposableListener)(this.textarea,"keyup",e=>this._keyUp(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keydown",e=>this._keyDown(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"keypress",e=>this._keyPress(e),!0)),this._register((0,H.addDisposableListener)(this.textarea,"compositionstart",()=>{this._syncTextArea(),this._compositionHelper.compositionstart(),this._compositionHelper.updateCompositionElements()})),this._register((0,H.addDisposableListener)(this.textarea,"compositionupdate",e=>this._compositionHelper.compositionupdate(e))),this._register((0,H.addDisposableListener)(this.textarea,"compositionend",e=>{this._compositionHelper instanceof u.CompositionHelper?this._compositionHelper.compositionend(e)&&this.textarea.dispatchEvent(new CustomEvent("xterm-composition-transaction-accepted",{bubbles:!0})):this._compositionHelper.compositionend()})),this._register((0,H.addDisposableListener)(this.textarea,"input",e=>this._inputEvent(e),!0)),this._register(this.onRender(()=>this._compositionHelper.updateCompositionElements()))}open(e){if(!e)throw new Error("Terminal requires a parent element.");if(e.isConnected||this._logService.debug("Terminal.open was called on an element that was not attached to the DOM"),this.element?.ownerDocument.defaultView&&this._coreBrowserService)return void(this.element.ownerDocument.defaultView!==this._coreBrowserService.window&&(this._coreBrowserService.window=this.element.ownerDocument.defaultView));this._document=e.ownerDocument,this.options.documentOverride&&this.options.documentOverride instanceof Document&&(this._document=this.optionsService.rawOptions.documentOverride),this.element=this._document.createElement("div"),this.element.dir="ltr",this.element.classList.add("terminal"),this.element.classList.add("xterm"),this.element.classList.toggle("allow-transparency",this.options.allowTransparency),this._register(this.optionsService.onSpecificOptionChange("allowTransparency",e=>this.element.classList.toggle("allow-transparency",e))),e.appendChild(this.element);const t=this._document.createDocumentFragment();this._viewportElement=this._document.createElement("div"),this._viewportElement.classList.add("xterm-viewport"),t.appendChild(this._viewportElement),this.screenElement=this._document.createElement("div"),this.screenElement.classList.add("xterm-screen"),this._register((0,H.addDisposableListener)(this.screenElement,"mousemove",e=>this.updateCursorStyle(e))),this._helperContainer=this._document.createElement("div"),this._helperContainer.classList.add("xterm-helpers"),this.screenElement.appendChild(this._helperContainer),t.appendChild(this.screenElement);const i=this.textarea=this._document.createElement("textarea");this.textarea.classList.add("xterm-helper-textarea"),this.textarea.setAttribute("aria-label",h.promptLabel.get()),L.isChromeOS||this.textarea.setAttribute("aria-multiline","false"),this.textarea.setAttribute("autocomplete","off"),this.textarea.setAttribute("autocorrect","off"),this.textarea.setAttribute("autocapitalize","off"),this.textarea.setAttribute("spellcheck","false"),this.textarea.tabIndex=0,this._register(this.optionsService.onSpecificOptionChange("disableStdin",()=>i.readOnly=this.optionsService.rawOptions.disableStdin)),this.textarea.readOnly=this.optionsService.rawOptions.disableStdin,this._coreBrowserService=this._register(this._instantiationService.createInstance(g.CoreBrowserService,this.textarea,e.ownerDocument.defaultView??window,this._document??("undefined"!=typeof window?window.document:null))),this._instantiationService.setService(C.ICoreBrowserService,this._coreBrowserService),this._register((0,H.addDisposableListener)(this.textarea,"focus",e=>this._handleTextAreaFocus(e))),this._register((0,H.addDisposableListener)(this.textarea,"blur",()=>this._handleTextAreaBlur())),this._helperContainer.appendChild(this.textarea),this._charSizeService=this._instantiationService.createInstance(p.CharSizeService,this._document,this._helperContainer),this._instantiationService.setService(C.ICharSizeService,this._charSizeService),this._themeService=this._instantiationService.createInstance(k.ThemeService),this._instantiationService.setService(C.IThemeService,this._themeService),this._register(this._inputHandler.onRequestColorSchemeQuery(()=>this._reportColorScheme())),this._register(this._themeService.onChangeColors(()=>{this.coreService.decPrivateModes.colorSchemeUpdates&&this._reportColorScheme()})),this._characterJoinerService=this._instantiationService.createInstance(v.CharacterJoinerService),this._instantiationService.setService(C.ICharacterJoinerService,this._characterJoinerService),this._renderService=this._register(this._instantiationService.createInstance(w.RenderService,this.rows,this.screenElement)),this._instantiationService.setService(C.IRenderService,this._renderService),this._register(this._renderService.onRenderedViewportChange(e=>this._onRender.fire(e))),this._register(this._renderService.onDimensionsChange(e=>this._onDimensionsChange.fire({css:{canvas:{...e.css.canvas},cell:{...e.css.cell}},device:{canvas:{...e.device.canvas},cell:{...e.device.cell},char:{...e.device.char}}}))),this.onResize(e=>this._renderService.resize(e.cols,e.rows)),this._compositionView=this._document.createElement("div"),this._compositionView.classList.add("composition-view"),this._compositionHelper=this._instantiationService.createInstance(u.CompositionHelper,this.textarea,this._compositionView),this._register((0,N.toDisposable)(()=>{this._compositionHelper instanceof u.CompositionHelper&&this._compositionHelper.dispose()})),this._helperContainer.appendChild(this._compositionView),this._mouseCoordsService=this._instantiationService.createInstance(S.MouseCoordsService),this._instantiationService.setService(C.IMouseCoordsService,this._mouseCoordsService);const s=this._linkifier.value=this._register(this._instantiationService.createInstance(O.Linkifier,this.screenElement));this.element.appendChild(t);try{this._onWillOpen.fire(this.element)}catch(e){this._logService.error("onWillOpen handler threw an exception",e)}this._renderService.hasRenderer()||this._renderService.setRenderer(this._createRenderer()),this._register(this.onCursorMove(()=>{this._renderService.handleCursorMove(),this._syncTextArea()})),this._register(this.onResize(()=>{this._renderService.handleResize(this.cols,this.rows),this._syncTextArea()})),this._register(this.onBlur(()=>this._renderService.handleBlur())),this._register(this.onFocus(()=>this._renderService.handleFocus())),this._viewport=this._register(this._instantiationService.createInstance(c.Viewport,this.element,this.screenElement)),this._register(this._viewport.onRequestScrollLines(e=>{super.scrollLines(e,!1),this.refresh(0,this.rows-1)})),this._selectionService=this._register(this._instantiationService.createInstance(y.SelectionService,this.element,this.screenElement,s)),this._instantiationService.setService(C.ISelectionService,this._selectionService),this._mouseService=this._instantiationService.createInstance(b.MouseService),this._instantiationService.setService(C.IMouseService,this._mouseService),this._register(this._selectionService.onRequestScrollLines(e=>this.scrollLines(e.amount,e.suppressScrollEvent))),this._register(this._selectionService.onSelectionChange(()=>this._onSelectionChange.fire())),this._register(this._selectionService.onRequestRedraw(e=>this._renderService.handleSelectionChanged(e.start,e.end,e.columnSelectMode))),this._register(this._selectionService.onLinuxMouseSelection(e=>{this.textarea.value=e,this.textarea.focus(),this.textarea.select()})),this._register(I.EventUtils.any(this._onScroll.event,this._inputHandler.onScroll)(()=>{this._selectionService.refresh(),this._viewport?.queueSync()})),this._register(this._instantiationService.createInstance(d.BufferDecorationRenderer,this.screenElement)),this._register((0,H.addDisposableListener)(this.element,"mousedown",e=>this._selectionService.handleMouseDown(e))),this.mouseStateService.areMouseEventsActive&&!this.options.mouseEventsRequireAlt?(this._selectionService.disable(),this.element.classList.add("enable-mouse-events")):(this._selectionService.enable(),this.element.classList.remove("enable-mouse-events")),this.options.screenReaderMode&&(this._accessibilityManager.value=this._instantiationService.createInstance(A.AccessibilityManager,this)),this._register(this.optionsService.onSpecificOptionChange("screenReaderMode",e=>this._handleScreenReaderModeOptionChange(e)));const r=this.options.scrollbar?.showScrollbar??!0,o=this.options.scrollbar?.width;r&&o&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement))),this.optionsService.onSpecificOptionChange("scrollbar",e=>{const t=(e?.showScrollbar??!0)&&!!e?.width;!this._overviewRulerRenderer&&t&&this._viewportElement&&this.screenElement&&(this._overviewRulerRenderer=this._register(this._instantiationService.createInstance(_.OverviewRulerRenderer,this._viewportElement,this.screenElement)))}),this._charSizeService.measure(),this.refresh(0,this.rows-1),this._initGlobal(),this._mouseService.bindMouse({element:this.element,screenElement:this.screenElement,document:this._document,handleTouchScroll:e=>this._viewport?.handleTouchScroll(e)},e=>this._register(e),()=>this.focus())}_createRenderer(){return this._instantiationService.createInstance(f.DomRenderer,this,this._document,this.element,this.screenElement,this._viewportElement,this._helperContainer,this.linkifier)}refresh(e,t,i=!1){this._renderService?.refreshRows(e,t,i)}updateCursorStyle(e){this._selectionService?.shouldColumnSelect(e)?this.element.classList.add("column-select"):this.element.classList.remove("column-select")}_showCursor(){this.coreService.isCursorInitialized||(this.coreService.isCursorInitialized=!0,this.refresh(this.buffer.y,this.buffer.y))}scrollLines(e,t){this._viewport?this._viewport.scrollLines(e):super.scrollLines(e,t),this.refresh(0,this.rows-1)}scrollPages(e){this.scrollLines(e*(this.rows-1))}scrollToTop(){this.scrollLines(-this._bufferService.buffer.ydisp)}scrollToBottom(e){e&&this._viewport?this._viewport.scrollToLine(this.buffer.ybase,!0):this.scrollLines(this._bufferService.buffer.ybase-this._bufferService.buffer.ydisp)}scrollToLine(e){const t=e-this._bufferService.buffer.ydisp;0!==t&&this.scrollLines(t)}paste(e){(0,a.paste)(e,this.textarea,this.coreService,this.optionsService)}attachCustomKeyEventHandler(e){this._customKeyEventHandler=e}attachCustomWheelEventHandler(e){this.mouseStateService.setCustomWheelEventHandler(e)}registerLinkProvider(e){return this._linkProviderService.registerLinkProvider(e)}registerCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");const t=this._characterJoinerService.register(e);return this.refresh(0,this.rows-1),t}deregisterCharacterJoiner(e){if(!this._characterJoinerService)throw new Error("Terminal must be opened first");this._characterJoinerService.deregister(e)&&this.refresh(0,this.rows-1)}get markers(){return this.buffer.markers}registerMarker(e){return this.buffer.addMarker(this.buffer.ybase+this.buffer.y+e)}registerDecoration(e){return this._decorationService.registerDecoration(e)}hasSelection(){return!!this._selectionService&&this._selectionService.hasSelection}select(e,t,i){this._selectionService.setSelection(e,t,i)}getSelection(){return this._selectionService?this._selectionService.selectionText:""}getSelectionPosition(){if(this._selectionService&&this._selectionService.hasSelection)return{start:{x:this._selectionService.selectionStart[0],y:this._selectionService.selectionStart[1]},end:{x:this._selectionService.selectionEnd[0],y:this._selectionService.selectionEnd[1]}}}clearSelection(){this._selectionService?.clearSelection()}selectAll(){this._selectionService?.selectAll()}selectLines(e,t){this._selectionService?.selectLines(e,t)}_keyDown(e){if(this._keyDownHandled=!1,this._keyDownSeen=!0,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;const t=this.browser.isMac&&this.options.macOptionIsMeta&&e.altKey;if(!t&&!this._compositionHelper.keydown(e))return this.options.scrollOnUserInput&&this.buffer.ybase!==this.buffer.ydisp&&this.scrollToBottom(!0),!1;t||"Dead"!==e.key&&"AltGraph"!==e.key||(this._unprocessedDeadKey=!0);const i=this._keyboardService.evaluateKeyDown(e);if(this.updateCursorStyle(e),3===i.type||2===i.type){const t=this.rows-1;return this.scrollLines(2===i.type?-t:t),e.preventDefault(),e.stopPropagation(),!1}if(1===i.type&&this.selectAll(),this._isThirdLevelShift(this.browser,e))return!0;if(i.cancel&&(e.preventDefault(),e.stopPropagation()),!i.key)return!0;if(!this._keyboardService.useKitty&&!this._keyboardService.useWin32InputMode&&e.key&&!e.ctrlKey&&!e.altKey&&!e.metaKey&&1===e.key.length&&e.key.charCodeAt(0)>=65&&e.key.charCodeAt(0)<=90)return!0;if(this._unprocessedDeadKey)return this._unprocessedDeadKey=!1,!0;""!==i.key&&"\r"!==i.key||(this.textarea.value="");const s=this._keyboardService.useWin32InputMode&&W(e);if(this._onKey.fire({key:i.key,domEvent:e}),this._showCursor(),this.coreService.triggerDataEvent(i.key,!s),!this.optionsService.rawOptions.screenReaderMode||e.altKey||e.ctrlKey)return e.preventDefault(),e.stopPropagation(),!1;this._keyDownHandled=!0}_isThirdLevelShift(e,t){const i=e.isMac&&!this.options.macOptionIsMeta&&t.altKey&&!t.ctrlKey&&!t.metaKey||e.isWindows&&t.altKey&&t.ctrlKey&&!t.metaKey||e.isWindows&&t.getModifierState("AltGraph");return"keypress"===t.type?i:i&&(!t.keyCode||t.keyCode>47)}_keyUp(e){if(this._keyDownSeen=!1,this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return;W(e)||this.focus();const t=this._keyboardService.evaluateKeyUp(e);if(t?.key){const i=this._keyboardService.useWin32InputMode&&W(e);this.coreService.triggerDataEvent(t.key,!i)}this.updateCursorStyle(e),this._keyPressHandled=!1}_keyPress(e){let t;if(this._keyPressHandled=!1,this._keyDownHandled)return!1;if(this._customKeyEventHandler&&!1===this._customKeyEventHandler(e))return!1;if(e.charCode)t=e.charCode;else if(null===e.which||void 0===e.which)t=e.keyCode;else{if(0===e.which||0===e.charCode)return!1;t=e.which}return!(!t||(e.altKey||e.ctrlKey||e.metaKey)&&!this._isThirdLevelShift(this.browser,e)||(t=String.fromCharCode(t),this._onKey.fire({key:t,domEvent:e}),this._showCursor(),this._compositionHelper.keypress?.(t)||this.coreService.triggerDataEvent(t,!0),this._keyPressHandled=!0,this._unprocessedDeadKey=!1,0))}_inputEvent(e){if(e.data&&"insertText"===e.inputType&&!this.optionsService.rawOptions.screenReaderMode&&this._compositionHelper instanceof u.CompositionHelper&&this._compositionHelper.input(e.data))return!0;if(e.data&&"insertText"===e.inputType&&(!e.composed||!this._keyDownSeen)&&!this.optionsService.rawOptions.screenReaderMode){if(this._keyPressHandled)return!1;this._unprocessedDeadKey=!1;const t=e.data;return this.coreService.triggerDataEvent(t,!0),!0}return!1}resize(e,t){e!==this.cols||t!==this.rows?super.resize(e,t):this._charSizeService&&!this._charSizeService.hasValidSize&&this._charSizeService.measure()}_afterResize(e,t){this._charSizeService?.measure()}clear(){this.buffer.clearAllMarkers(),this.buffer.lines.set(0,this.buffer.lines.get(this.buffer.ybase+this.buffer.y)),this.buffer.lines.length=1,this.buffer.ydisp=0,this.buffer.ybase=0,this.buffer.y=0;for(let e=1;e |