From 37b1eca498092f6520f627cf46cb74ba2a89e9a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Pierzcha=C5=82a?= Date: Mon, 14 Sep 2026 16:01:01 +0200 Subject: [PATCH] refactor(shell-quote): one implementation, reached through the runner host port Closes #2525. The Android perf copy is a direct import swap to @agent-device/host-kit/command, which platform-android already uses for shellQuoteIfNeeded. The apple runner copy goes through AppleRunnerHost because R77 (apple-runner-host-port) forbids a value import of host-kit from runner/**, and runner/ sits in the eager closure of seven Apple facades. --- packages/platform-android/src/perf-native-artifacts.ts | 5 +---- packages/platform-android/src/perf-native-simpleperf.ts | 2 +- packages/platform-apple/src/core/runner-host.ts | 2 ++ packages/platform-apple/src/runner/host.ts | 3 +++ packages/platform-apple/src/runner/runner-lease.ts | 5 +---- 5 files changed, 8 insertions(+), 9 deletions(-) diff --git a/packages/platform-android/src/perf-native-artifacts.ts b/packages/platform-android/src/perf-native-artifacts.ts index f606a30f9f..9b74d9c2ce 100644 --- a/packages/platform-android/src/perf-native-artifacts.ts +++ b/packages/platform-android/src/perf-native-artifacts.ts @@ -1,6 +1,7 @@ import path from 'node:path'; import type { DeviceInfo } from '@agent-device/kernel/device'; import { AppError } from '@agent-device/kernel/errors'; +import { shellQuote } from '@agent-device/host-kit/command'; import { sleep } from '@agent-device/host-kit/retry'; import { resolveAndroidAdbExecutor, type AndroidAdbExecutor } from './adb-executor.ts'; import { annotateAndroidNativePerfError } from './perf-native-errors.ts'; @@ -121,10 +122,6 @@ export async function readFileSize( } } -export function shellQuote(value: string): string { - return `'${value.replaceAll("'", `'\\''`)}'`; -} - async function stopAndroidBackgroundTool( adb: AndroidAdbExecutor, session: AndroidNativePerfSession, diff --git a/packages/platform-android/src/perf-native-simpleperf.ts b/packages/platform-android/src/perf-native-simpleperf.ts index 7463f0b746..06925fce58 100644 --- a/packages/platform-android/src/perf-native-simpleperf.ts +++ b/packages/platform-android/src/perf-native-simpleperf.ts @@ -1,11 +1,11 @@ import type { DeviceInfo } from '@agent-device/kernel/device'; import { AppError } from '@agent-device/kernel/errors'; +import { shellQuote } from '@agent-device/host-kit/command'; import { resolveAndroidAdbExecutor, type AndroidAdbExecutor } from './adb-executor.ts'; import { buildAndroidNativeRemotePath, cleanupAndroidRemotePath, readFileSize, - shellQuote, stopAndroidNativePerfSession, writeJsonArtifact, } from './perf-native-artifacts.ts'; diff --git a/packages/platform-apple/src/core/runner-host.ts b/packages/platform-apple/src/core/runner-host.ts index 6ef48432c9..12b73caa33 100644 --- a/packages/platform-apple/src/core/runner-host.ts +++ b/packages/platform-apple/src/core/runner-host.ts @@ -8,6 +8,7 @@ import { runCmdBackground, runCmdStreaming, runCmdSync, + shellQuote, } from '@agent-device/host-kit/command'; import { emitDiagnostic, withDiagnosticTimer } from '@agent-device/host-kit/diagnostics'; import { @@ -57,6 +58,7 @@ export const appleRunnerHost: AppleRunnerHost = { runCmdBackground, requireExecSuccess, isCommandTimeoutError, + shellQuote, emitDiagnostic, withDiagnosticTimer, retryWithPolicy, diff --git a/packages/platform-apple/src/runner/host.ts b/packages/platform-apple/src/runner/host.ts index 7899451691..c3f5b9c15b 100644 --- a/packages/platform-apple/src/runner/host.ts +++ b/packages/platform-apple/src/runner/host.ts @@ -167,6 +167,8 @@ export type AppleRunnerHost = { ): ExecResult; /** True only for the error the exec layer raises when it killed a command at `timeoutMs`. */ isCommandTimeoutError(error: unknown): boolean; + /** Single-quote one value for a POSIX shell fragment (hints and remote commands). */ + shellQuote(value: string): string; // Diagnostics (@agent-device/host-kit/diagnostics) emitDiagnostic(event: DiagnosticEventInput): void; withDiagnosticTimer( @@ -297,6 +299,7 @@ export const requireExecSuccess: AppleRunnerHost['requireExecSuccess'] = (result requireHost().requireExecSuccess(result, message, extra); export const isCommandTimeoutError: AppleRunnerHost['isCommandTimeoutError'] = (error) => requireHost().isCommandTimeoutError(error); +export const shellQuote: AppleRunnerHost['shellQuote'] = (value) => requireHost().shellQuote(value); export const emitDiagnostic: AppleRunnerHost['emitDiagnostic'] = (event) => requireHost().emitDiagnostic(event); export const withDiagnosticTimer = ( diff --git a/packages/platform-apple/src/runner/runner-lease.ts b/packages/platform-apple/src/runner/runner-lease.ts index f5c3edfca4..a5bf3a3438 100644 --- a/packages/platform-apple/src/runner/runner-lease.ts +++ b/packages/platform-apple/src/runner/runner-lease.ts @@ -12,6 +12,7 @@ import { readProcessStartTime, classifyOwnerLiveness, leaseOwnerStateDir, + shellQuote, } from './host.ts'; import { AppError } from '@agent-device/kernel/errors'; import type { DeviceInfo } from '@agent-device/kernel/device'; @@ -289,10 +290,6 @@ function formatEnvAssignment(name: string, value: string): string { return `${name}=${shellQuote(value)}`; } -function shellQuote(value: string): string { - return `'${value.replaceAll("'", String.raw`'\''`)}'`; -} - // A lease whose owner process is gone but whose runner may still be running: // the adoption path probes it instead of killing it. Detached leases (graceful // daemon shutdown rewrote the token) classify as stale too once the owner pid