You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@maka.apache.org by wy...@apache.org on 2026/09/04 11:13:29 UTC

(maka) branch diagnose/windows-local-remote-access-ebusy updated (abdafac2fc -> 42a26f0011)

This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a change to branch diagnose/windows-local-remote-access-ebusy
in repository https://gitbox.apache.org/repos/asf/maka.git


 discard abdafac2fc fix(desktop): stabilize Windows remote access setup
     add c5d906559a test(desktop): sink the /compact menu rule to a tested predicate (#4762)
     add 4ed6255f29 fix(runtime): preserve Codex compaction chronology (#4754)
     add ba08a08f0a fix(desktop): persist edited context windows (#4763)
     add 4f72332ea3 fix(ui): show project names in session hover cards (#4743)
     add 3dc920f42d fix(desktop): align Permission Center metadata rows (#4725)
     add 14910a8ee4 fix(runtime): reserve State Root migrations for Host (#4770)
     new 42a26f0011 fix(desktop): stabilize Windows remote access setup

This update added new revisions after undoing existing revisions.
That is to say, some revisions that were in the old version of the
branch are not in the new version.  This situation occurs
when a user --force pushes a change and generates a repository
containing something like this:

 * -- * -- B -- O -- O -- O   (abdafac2fc)
            \
             N -- N -- N   refs/heads/diagnose/windows-local-remote-access-ebusy (42a26f0011)

You should already have received notification emails for all of the O
revisions, and so the following emails describe only the N revisions
from the common base, B.

Any revisions marked "omit" are not gone; other references still
refer to them.  Any revisions marked "discard" are gone forever.

The 1 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 apps/desktop/e2e/context-window-save.spec.ts       |  63 ++++++++++
 apps/desktop/e2e/fixtures.ts                       |  10 ++
 .../e2e/permission-center-metadata-layout.spec.ts  |  86 ++++++++++++++
 apps/desktop/e2e/sidebar-project-row.spec.ts       |   7 +-
 apps/desktop/renderer-architecture.json            |   6 +-
 .../main/__tests__/desktop-slash-command.test.ts   |  46 ++++++++
 .../main/__tests__/main-startup-lifetime.test.ts   |  25 ++++
 apps/desktop/src/main/runtime-host-boot.ts         |  17 +--
 apps/desktop/src/renderer/app-shell.tsx            |  14 ++-
 apps/desktop/src/renderer/desktop-slash-command.ts |  15 +++
 .../use-session-navigation-controller.ts           |  24 +++-
 .../ui/session-navigation-provider.tsx             |   2 +
 .../renderer/settings/permission-center-page.tsx   |   4 +-
 .../settings/provider-connection-detail.tsx        |  24 ++--
 .../src/renderer/styles/settings/permission.css    |   8 ++
 apps/desktop/src/renderer/styles/sidebar.css       |   4 +
 packages/runtime-host/src/protocol/index.ts        |   7 +-
 .../openai-codex-history-compactor.test.ts         | 129 +++++++++++++++++++++
 .../runtime/src/openai-codex-history-compactor.ts  |  15 +--
 .../src/__tests__/operational-state-store.test.ts  |  66 ++++++++++-
 packages/storage/src/operational-state-store.ts    |  29 ++++-
 packages/storage/src/session-copy-cleanup.ts       |  15 ++-
 packages/storage/src/work-board-store.ts           |  12 +-
 packages/ui/src/session-history-list.tsx           |  20 +++-
 packages/ui/src/session-rail-context.tsx           |   2 +
 packages/ui/stories/session-rail-harness.tsx       |   1 +
 26 files changed, 589 insertions(+), 62 deletions(-)
 create mode 100644 apps/desktop/e2e/context-window-save.spec.ts
 create mode 100644 apps/desktop/e2e/permission-center-metadata-layout.spec.ts
 create mode 100644 apps/desktop/src/main/__tests__/desktop-slash-command.test.ts


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org


(maka) 01/01: fix(desktop): stabilize Windows remote access setup

Posted by wy...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

M4n5ter pushed a commit to branch diagnose/windows-local-remote-access-ebusy
in repository https://gitbox.apache.org/repos/asf/maka.git

commit 42a26f001180540131621bae051122fa84ae3b0f
Author: M4n5ter <m4...@gmail.com>
AuthorDate: Fri Sep 4 18:35:58 2026 +0800

    fix(desktop): stabilize Windows remote access setup
---
 .../__tests__/runtime-host-local-operator.test.ts  | 69 ++++++++++++++++++++++
 .../src/main/runtime-host-local-operator.ts        | 19 +++++-
 .../cli/src/__tests__/runtime-host-setup.test.ts   | 69 ++++++++++++++++++++++
 .../cli/src/runtime-host-managed-deployment.ts     | 69 ++++++++++++++++++++--
 .../cli/src/runtime-host-package-deployment.ts     |  1 +
 .../runtime-host-windows-task-launcher-artifact.ts | 21 +++++++
 6 files changed, 243 insertions(+), 5 deletions(-)

diff --git a/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts b/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts
index 8b57c82549..5a08954585 100644
--- a/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts
+++ b/apps/desktop/src/main/__tests__/runtime-host-local-operator.test.ts
@@ -133,6 +133,75 @@ test('local setup forwards the exact development archive evidence', async (t) =>
   assert.equal(environment?.[RUNTIME_HOST_SETUP_SOURCE_PACKAGE_INTEGRITY_ENV], integrity);
 });
 
+test('local setup scratch cleanup cannot replace its framed outcome', async (t) => {
+  const frames = [
+    {
+      schemaVersion: 1 as const,
+      sequence: 0,
+      kind: 'complete' as const,
+      version: '0.2.0',
+      serviceId: 'b'.repeat(64),
+      deploymentId: '00000000-0000-4000-8000-000000000001',
+      operator: OPERATOR,
+      rootPath: '/tmp/maka/root',
+      rootId: 'a'.repeat(64),
+      endpoint: 'ws://127.0.0.1:7443/runtime-host',
+      credentialId: 'credential-1',
+      credential: 'secret-access-token',
+    },
+    {
+      schemaVersion: 1 as const,
+      sequence: 0,
+      kind: 'error' as const,
+      error: { code: 'setup_failed', message: 'primary setup failure' },
+    },
+  ];
+  let invocation = 0;
+  let cleanupCount = 0;
+  const spawnProcess = (() => {
+    const child = new EventEmitter() as ReturnType<typeof spawn>;
+    const stdout = new PassThrough();
+    const stderr = new PassThrough();
+    const frame = frames[invocation++];
+    Object.assign(child, { pid: 1234, stdout, stderr, kill: () => true });
+    process.nextTick(() => {
+      stdout.end(encodeRuntimeHostSetupFrame(frame));
+      stderr.end();
+      child.emit('close', frame.kind === 'complete' ? 0 : 1, null);
+    });
+    return child;
+  }) as typeof spawn;
+  const operator = createDesktopRuntimeHostLocalOperator({
+    environment: { PATH: process.env.PATH },
+    spawnProcess,
+    removeSetupWorkingDirectory: async (path) => {
+      cleanupCount += 1;
+      await rm(path, { recursive: true, force: true });
+      throw new Error('scratch cleanup failed');
+    },
+  });
+  t.after(() => operator.close());
+  const setup = {
+    setupPackage: { kind: 'npm' as const, specifier: 'maka-agent@0.2.0' },
+    clientDataRoot: '/tmp/maka/client',
+    rootPath: '/tmp/maka/root',
+    principalId: 'desktop-owner:pairing',
+    expectedTarget: {
+      serviceId: 'b'.repeat(64),
+      rootPath: '/tmp/maka/root',
+      rootId: 'a'.repeat(64),
+    },
+  };
+
+  const complete = await operator.runSetup(setup, () => undefined);
+  assert.equal(complete.kind, 'complete');
+  await assert.rejects(
+    operator.runSetup(setup, () => undefined),
+    /primary setup failure/u,
+  );
+  assert.equal(cleanupCount, 2);
+});
+
 test('Windows npm discovery cannot outlive setup cancellation', async (t) => {
   const originalPlatform = process.platform;
   const fixtureRoot = await mkdtemp(join(tmpdir(), 'maka-windows-npm-lookup-'));
diff --git a/apps/desktop/src/main/runtime-host-local-operator.ts b/apps/desktop/src/main/runtime-host-local-operator.ts
index 4b00e73885..1ade55cedc 100644
--- a/apps/desktop/src/main/runtime-host-local-operator.ts
+++ b/apps/desktop/src/main/runtime-host-local-operator.ts
@@ -62,6 +62,8 @@ import {
 const SETUP_TIMEOUT_MS = 10 * 60_000;
 const SETUP_FRAME_PENDING_MAX = 20 * 1024;
 const STDERR_MAX_BYTES = 64 * 1024;
+const SETUP_CLEANUP_MAX_RETRIES = 10;
+const SETUP_CLEANUP_RETRY_DELAY_MS = 100;
 const WINDOWS_NPM_RESOLUTION_SCRIPT = String.raw`
 const { statSync } = require('node:fs');
 const path = require('node:path').win32;
@@ -199,6 +201,7 @@ export function createDesktopRuntimeHostLocalOperator(input: {
   readonly spawnProcess?: typeof spawn;
   readonly setupTimeoutMs?: number;
   readonly terminateProcess?: typeof terminateChildProcessTree;
+  readonly removeSetupWorkingDirectory?: (path: string) => Promise<void>;
 } = {}): {
   runSetup(
     setup: DesktopRuntimeHostLocalSetupInput,
@@ -267,6 +270,15 @@ export function createDesktopRuntimeHostLocalOperator(input: {
   let closed = false;
   const closing = new AbortController();
   const terminate = input.terminateProcess ?? terminateChildProcessTree;
+  const removeSetupWorkingDirectory =
+    input.removeSetupWorkingDirectory ??
+    ((path: string) =>
+      rm(path, {
+        recursive: true,
+        force: true,
+        maxRetries: SETUP_CLEANUP_MAX_RETRIES,
+        retryDelay: SETUP_CLEANUP_RETRY_DELAY_MS,
+      }));
 
   return {
     async runSetup(setup, onProgress) {
@@ -302,7 +314,12 @@ export function createDesktopRuntimeHostLocalOperator(input: {
           active,
         });
       } finally {
-        await rm(workingDirectory, { recursive: true, force: true });
+        try {
+          await removeSetupWorkingDirectory(workingDirectory);
+        } catch {
+          // The private scratch directory is not part of the setup transaction.
+          // Its cleanup must not replace the operator's framed result or error.
+        }
       }
     },
     runPeer(command) {
diff --git a/packages/cli/src/__tests__/runtime-host-setup.test.ts b/packages/cli/src/__tests__/runtime-host-setup.test.ts
index a8818d2783..041b4dd80c 100644
--- a/packages/cli/src/__tests__/runtime-host-setup.test.ts
+++ b/packages/cli/src/__tests__/runtime-host-setup.test.ts
@@ -54,12 +54,19 @@ import {
   acknowledgeRuntimeHostManagedDeploymentCleanup,
   assertRuntimeHostManagedOperatorDeployment,
   convergeRuntimeHostManagedOperator,
+  convergeRuntimeHostManagedWindowsTaskLauncher,
   prepareRuntimeHostManagedPackageDeployment,
   pruneRuntimeHostManagedPackages,
   readRuntimeHostManagedDeploymentCleanupReceipt,
   resolveRuntimeHostManagedControlRoot,
   resolveRuntimeHostManagedDeploymentRoot,
+  verifyRuntimeHostManagedWindowsTaskLauncher,
 } from '../runtime-host-managed-deployment.js';
+import {
+  resolvePackagedRuntimeHostWindowsTaskLauncherPath,
+  resolveRuntimeHostWindowsTaskLauncherPath,
+  runtimeHostManagedWindowsTaskLauncherPath,
+} from '../runtime-host-windows-task-launcher-artifact.js';
 import { runRuntimeHostSetupCli } from '../runtime-host-setup-command.js';
 import { RuntimeHostAccessUnavailableError } from '../runtime-host-access-command.js';
 import { replaceRuntimeHostLifecycle } from '../runtime-host-lifecycle-transaction.js';
@@ -977,6 +984,68 @@ test('managed operator binds its Client Data Root and routes deployment cleanup'
   assert.deepEqual(signalExit, { code: null, signal: 'SIGTERM' });
 });
 
+test('managed Windows task launcher is projected to a stable deployment path', async (t) => {
+  const base = await mkdtemp(join(tmpdir(), 'maka-runtime-host-windows-launcher-'));
+  t.after(() => rm(base, { recursive: true, force: true }));
+  const sourcePackageRoot = await createReleasePackage(base, '0.2.0');
+  const sourceLauncher = join(
+    sourcePackageRoot,
+    'native',
+    'runtime-host-windows-task-launcher',
+    'prebuilds',
+    'win32-x64',
+    'maka-runtime-host-task-launcher.exe',
+  );
+  await mkdir(dirname(sourceLauncher), { recursive: true });
+  await writeFile(sourceLauncher, 'launcher-v1');
+  const serviceId = 'a'.repeat(64);
+  const deployment = await prepareRuntimeHostManagedPackageDeployment(
+    {
+      serviceId,
+      clientDataRoot: join(base, 'client'),
+      sourcePackageRoot,
+      version: '0.2.0',
+      packageIntegrity: PACKAGE_INTEGRITY,
+    },
+    {
+      env: { XDG_DATA_HOME: join(base, 'data') },
+      homeDir: join(base, 'home'),
+      platform: 'linux',
+    },
+  );
+  const config: RuntimeHostManagedDeploymentConfig = {
+    schemaVersion: 1,
+    state: 'active',
+    deploymentId: '00000000-0000-4000-8000-000000000001',
+    configRevision: 1,
+    deploymentRoot: deployment.root,
+    root: { id: serviceId, path: join(base, 'state') },
+    projectDirectoryRoots: [],
+    launch: {
+      kind: 'exact_package',
+      nodePath: process.execPath,
+      package: { kind: 'npm_registry', version: '0.2.0', integrity: PACKAGE_INTEGRITY },
+    },
+    listeners: { localIpc: true },
+    lifecycle: { mode: 'supervised', provider: 'windows_task', availability: 'session' },
+    reconciliation: { trigger: 'scheduled', provider: 'windows_task_timer' },
+  };
+
+  await convergeRuntimeHostManagedWindowsTaskLauncher(config);
+  const projected = runtimeHostManagedWindowsTaskLauncherPath(deployment.root);
+  assert.equal(await readFile(projected, 'utf8'), 'launcher-v1');
+  assert.equal(await resolveRuntimeHostWindowsTaskLauncherPath(deployment.cliPath), projected);
+
+  const packaged = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(deployment.cliPath);
+  await writeFile(packaged, 'launcher-v2');
+  await assert.rejects(
+    verifyRuntimeHostManagedWindowsTaskLauncher(config),
+    /does not match its deployment/u,
+  );
+  await convergeRuntimeHostManagedWindowsTaskLauncher(config);
+  await verifyRuntimeHostManagedWindowsTaskLauncher(config);
+});
+
 async function createReleasePackage(base: string, version: string): Promise<string> {
   const root = join(base, `source-package-${version}`);
   await mkdir(join(root, 'dist'), { recursive: true });
diff --git a/packages/cli/src/runtime-host-managed-deployment.ts b/packages/cli/src/runtime-host-managed-deployment.ts
index 30e46b997a..95ce468e9d 100644
--- a/packages/cli/src/runtime-host-managed-deployment.ts
+++ b/packages/cli/src/runtime-host-managed-deployment.ts
@@ -41,6 +41,10 @@ import {
   type RuntimeHostManagedDeploymentAuthorityOptions,
   type RuntimeHostManagedDeploymentConfig,
 } from '@maka/runtime-host/operator';
+import {
+  resolvePackagedRuntimeHostWindowsTaskLauncherPath,
+  runtimeHostManagedWindowsTaskLauncherPath,
+} from './runtime-host-windows-task-launcher-artifact.js';
 
 export { RuntimeHostPackageDeploymentError as RuntimeHostManagedDeploymentError } from './runtime-host-package-deployment.js';
 
@@ -63,6 +67,7 @@ interface RuntimeHostManagedDeploymentCleanupReceipt {
 }
 
 const CLEANUP_RECEIPT_FILE = 'cleanup-approved.json';
+const WINDOWS_TASK_LAUNCHER_MAX_BYTES = 1024 * 1024;
 
 export function resolveRuntimeHostManagedPackageCliPath(
   deploymentRoot: string,
@@ -641,15 +646,15 @@ async function writeOperatorLauncher(
     managedRootId,
     deploymentId,
   );
-  await writeStableOperator(path, contents);
+  await writeStableArtifact(path, contents);
 }
 
-async function writeStableOperator(path: string, contents: string): Promise<void> {
+async function writeStableArtifact(path: string, contents: string | Uint8Array): Promise<void> {
   const temporaryPath = `${path}.${randomUUID()}.tmp`;
   try {
     const file = await open(temporaryPath, 'wx', 0o700);
     try {
-      await file.writeFile(contents, 'utf8');
+      await file.writeFile(contents);
       await file.sync();
     } finally {
       await file.close();
@@ -735,6 +740,9 @@ export async function convergeRuntimeHostManagedOperator(
     desired.root.id,
     desired.deploymentId,
   );
+  if (process.platform === 'win32') {
+    await convergeRuntimeHostManagedWindowsTaskLauncher(desired);
+  }
   await forwardLegacyOperatorIfPresent(
     deployment.deploymentRoot,
     desired.launch.nodePath,
@@ -761,7 +769,7 @@ async function forwardLegacyOperatorIfPresent(
     },
   );
   if (exists) {
-    await writeStableOperator(path, legacyOperatorLauncherContents(nodePath, modulePath));
+    await writeStableArtifact(path, legacyOperatorLauncherContents(nodePath, modulePath));
   }
 }
 
@@ -811,6 +819,9 @@ export async function verifyRuntimeHostManagedOperator(
       cause: error,
     });
   });
+  if (process.platform === 'win32') {
+    await verifyRuntimeHostManagedWindowsTaskLauncher(config, { allowAbsent: true });
+  }
   const legacyOperatorPath = join(config.deploymentRoot, 'operator');
   const legacyExpected = legacyOperatorLauncherContents(config.launch.nodePath, operatorPath);
   const legacyExists = await access(legacyOperatorPath, constants.F_OK).then(
@@ -839,6 +850,56 @@ export async function verifyRuntimeHostManagedOperator(
   }
 }
 
+export async function convergeRuntimeHostManagedWindowsTaskLauncher(
+  config: RuntimeHostManagedDeploymentConfig,
+): Promise<void> {
+  const layout = resolveRuntimeHostNpmDeploymentLayout(
+    config.deploymentRoot,
+    config.launch.package.integrity,
+  );
+  const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath);
+  await writeStableArtifact(
+    runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot),
+    await readWindowsTaskLauncher(sourcePath),
+  );
+}
+
+export async function verifyRuntimeHostManagedWindowsTaskLauncher(
+  config: RuntimeHostManagedDeploymentConfig,
+  options: { readonly allowAbsent?: boolean } = {},
+): Promise<void> {
+  const projectedPath = runtimeHostManagedWindowsTaskLauncherPath(config.deploymentRoot);
+  const projectedExists = await access(projectedPath, constants.F_OK).then(
+    () => true,
+    (error: unknown) => {
+      if (isNodeError(error, 'ENOENT')) return false;
+      throw error;
+    },
+  );
+  if (!projectedExists && options.allowAbsent) return;
+
+  const layout = resolveRuntimeHostNpmDeploymentLayout(
+    config.deploymentRoot,
+    config.launch.package.integrity,
+  );
+  const sourcePath = await resolvePackagedRuntimeHostWindowsTaskLauncherPath(layout.cliPath);
+  const [expected, observed] = await Promise.all([
+    readWindowsTaskLauncher(sourcePath),
+    readWindowsTaskLauncher(projectedPath),
+  ]);
+  if (!observed.equals(expected)) {
+    throw new Error('The managed Runtime Host Windows task launcher does not match its deployment');
+  }
+}
+
+function readWindowsTaskLauncher(path: string): Promise<Buffer> {
+  return readStableBoundedFile({
+    path,
+    maxBytes: WINDOWS_TASK_LAUNCHER_MAX_BYTES,
+    invalidFile: () => new Error('The managed Runtime Host Windows task launcher is invalid'),
+  });
+}
+
 function isRecord(value: unknown): value is Record<string, unknown> {
   return value !== null && typeof value === 'object' && !Array.isArray(value);
 }
diff --git a/packages/cli/src/runtime-host-package-deployment.ts b/packages/cli/src/runtime-host-package-deployment.ts
index 18d1968b41..ff2f7a7cf8 100644
--- a/packages/cli/src/runtime-host-package-deployment.ts
+++ b/packages/cli/src/runtime-host-package-deployment.ts
@@ -369,6 +369,7 @@ const wait = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
   await rm(path, { recursive: true, force: true, maxRetries: 100, retryDelay: 100 });
 })().catch(() => { process.exitCode = 1; });`;
   const cleanup = spawn(process.execPath, ['-e', script, path, String(process.pid)], {
+    cwd: dirname(process.execPath),
     detached: true,
     stdio: 'ignore',
     windowsHide: true,
diff --git a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts
index 5045017dd3..84ecb76ea3 100644
--- a/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts
+++ b/packages/cli/src/runtime-host-windows-task-launcher-artifact.ts
@@ -23,6 +23,16 @@ import { basename, dirname, join } from 'node:path';
 const LAUNCHER_FILE = 'maka-runtime-host-task-launcher.exe';
 
 export async function resolveRuntimeHostWindowsTaskLauncherPath(cliPath: string): Promise<string> {
+  const packageRoot = dirname(dirname(await realpath(cliPath)));
+  const projected = projectedLauncherPath(packageRoot);
+  if (projected && (await isReadable(projected))) return realpath(projected);
+
+  return resolvePackagedRuntimeHostWindowsTaskLauncherPath(cliPath);
+}
+
+export async function resolvePackagedRuntimeHostWindowsTaskLauncherPath(
+  cliPath: string,
+): Promise<string> {
   const packageRoot = dirname(dirname(await realpath(cliPath)));
   const packaged = join(
     packageRoot,
@@ -51,6 +61,17 @@ export async function resolveRuntimeHostWindowsTaskLauncherPath(cliPath: string)
   throw new Error('Maka does not include the Windows Runtime Host task launcher');
 }
 
+export function runtimeHostManagedWindowsTaskLauncherPath(deploymentRoot: string): string {
+  return join(deploymentRoot, LAUNCHER_FILE);
+}
+
+function projectedLauncherPath(packageRoot: string): string | undefined {
+  const versionsRoot = dirname(packageRoot);
+  return basename(versionsRoot) === 'versions'
+    ? runtimeHostManagedWindowsTaskLauncherPath(dirname(versionsRoot))
+    : undefined;
+}
+
 async function isReadable(path: string): Promise<boolean> {
   try {
     await access(path);


---------------------------------------------------------------------
To unsubscribe, e-mail: commits-unsubscribe@maka.apache.org
For additional commands, e-mail: commits-help@maka.apache.org