diff --git a/src/infra/local-tui-processes.test.ts b/src/infra/local-tui-processes.test.ts index ae4ed8f1fe86..b8dcdf78e9b7 100644 --- a/src/infra/local-tui-processes.test.ts +++ b/src/infra/local-tui-processes.test.ts @@ -93,14 +93,28 @@ describe("local TUI processes", () => { const spawnSync = vi.fn().mockReturnValue({ status: 0, stdout: JSON.stringify([ - { ProcessId: 101, CommandLine: "C:\\openclaw.exe tui" }, - { ProcessId: 102, CommandLine: "C:\\openclaw.exe gateway" }, - { ProcessId: 103, CommandLine: '"C:\\Program Files\\OpenClaw\\openclaw.exe" chat' }, + { ProcessId: 101, CommandLine: "C:\\openclaw.exe tui", OwnerSid: "S-1", CurrentSid: "S-1" }, + { + ProcessId: 102, + CommandLine: "C:\\openclaw.exe gateway", + OwnerSid: "S-1", + CurrentSid: "S-1", + }, + { + ProcessId: 103, + CommandLine: '"C:\\Program Files\\OpenClaw\\openclaw.exe" chat', + OwnerSid: "S-1", + CurrentSid: "S-1", + }, { ProcessId: 104, CommandLine: '"C:\\Program Files\\nodejs\\node.exe" "C:\\Program Files\\OpenClaw\\openclaw.mjs" terminal', + OwnerSid: "S-1", + CurrentSid: "S-1", }, + { ProcessId: 105, CommandLine: "C:\\openclaw.exe tui", OwnerSid: "S-2", CurrentSid: "S-1" }, + { ProcessId: 106, CommandLine: "C:\\openclaw.exe tui", OwnerSid: null, CurrentSid: "S-1" }, ]), }); @@ -227,4 +241,18 @@ describe("local TUI processes", () => { await waitForLocalTuiUpdate(vi.fn(async () => ({ lockPath: "test", release }))); expect(release).toHaveBeenCalledOnce(); }); + + it("keeps waiting after the bounded lock attempt while an update is still running", async () => { + const release = vi.fn(async () => {}); + const timeout = Object.assign(new Error("busy"), { code: "file_lock_timeout" }); + const acquireLock = vi + .fn() + .mockRejectedValueOnce(timeout) + .mockResolvedValueOnce({ lockPath: "test", release }); + + await waitForLocalTuiUpdate(acquireLock); + + expect(acquireLock).toHaveBeenCalledTimes(2); + expect(release).toHaveBeenCalledOnce(); + }); }); diff --git a/src/infra/local-tui-processes.ts b/src/infra/local-tui-processes.ts index c3476f9575cb..d3682b5ee367 100644 --- a/src/infra/local-tui-processes.ts +++ b/src/infra/local-tui-processes.ts @@ -119,7 +119,7 @@ export function listLocalTuiProcesses( [ "-NoProfile", "-Command", - "Get-CimInstance Win32_Process | Select-Object ProcessId,CreationDate,CommandLine | ConvertTo-Json -Compress", + "$currentSid=[Security.Principal.WindowsIdentity]::GetCurrent().User.Value; Get-CimInstance Win32_Process | ForEach-Object { $ownerSid=(Invoke-CimMethod -InputObject $_ -MethodName GetOwnerSid -ErrorAction SilentlyContinue).Sid; [pscustomobject]@{ProcessId=$_.ProcessId;CommandLine=$_.CommandLine;OwnerSid=$ownerSid;CurrentSid=$currentSid} } | ConvertTo-Json -Compress", ], { encoding: "utf8", killSignal: "SIGKILL", timeout: LOCAL_TUI_PROCESS_PROBE_TIMEOUT_MS }, ); @@ -134,6 +134,8 @@ export function listLocalTuiProcesses( } const pidValue = Reflect.get(entry, "ProcessId"); const commandValue = Reflect.get(entry, "CommandLine"); + const ownerSidValue = Reflect.get(entry, "OwnerSid"); + const currentSidValue = Reflect.get(entry, "CurrentSid"); const pid = typeof pidValue === "number" ? pidValue : undefined; const command = typeof commandValue === "string" ? commandValue.trim() : undefined; const startTime = pid @@ -141,6 +143,8 @@ export function listLocalTuiProcesses( : null; return pid && pid !== (params.currentPid ?? process.pid) && + typeof ownerSidValue === "string" && + ownerSidValue === currentSidValue && command && isLocalTuiCommand(command, "win32") ? [{ pid, command, ...(startTime === null ? {} : { startTime: String(startTime) }) }] @@ -309,6 +313,15 @@ export async function quiesceLocalTuiProcessesBeforeUpdate( export async function waitForLocalTuiUpdate( acquireLock: typeof acquireFileLock = acquireFileLock, ): Promise { - const lock = await acquireLock(LOCAL_TUI_UPDATE_LOCK_PATH, LOCAL_TUI_UPDATE_LOCK_OPTIONS); - await lock.release(); + for (;;) { + try { + const lock = await acquireLock(LOCAL_TUI_UPDATE_LOCK_PATH, LOCAL_TUI_UPDATE_LOCK_OPTIONS); + await lock.release(); + return; + } catch (error) { + if (extractErrorCode(error) !== "file_lock_timeout") { + throw error; + } + } + } }