mirror of
https://github.com/nexu-io/open-design.git
synced 2026-10-27 09:51:12 +01:00
* fix(packaged): let a macOS headless runtime become the desktop in place
When Codex starts Open Design headlessly through MCP, macOS treats that
windowless process as the running app. Opening the app from the Dock,
Finder, Launchpad or `open -a` then only reactivates it, so no launcher
runs and no window ever appears (OPEND-3426).
On macOS the headless runtime now runs the desktop path with its window
deferred, and restores the window in the same process when the user
opens the app: through `activate`/`open-url` paired with the app becoming
active, or through SHOW from a newer launcher that sees a `restorable`
owner. The daemon, its port and connected MCP clients stay. A new outer
hands headless launches to the active payload and marks the launch as
managed; payloads started by older outers keep today's behavior.
Managed MCP registrations carry absolute sidecar endpoints for every mode
of the namespace, so the MCP process finds the daemon whichever mode owns
it, waits for a running owner instead of reopening it, and cold-starts
only when nothing runs. The daemon refreshes an existing Codex
registration on start and never installs one. A write interrupted by a
service restart now says it may or may not have been applied.
* fix(daemon): scope Codex refresh to its managed install
Require the existing registration's managed discovery marker to match before refreshing it, so local, stable, and prerelease installs cannot take over the global name from one another. Cover the unrelated-owner path without invoking a config mutation.\n\nGenerated-By: looper 0.11.2 (runner=fixer, agent=codex)
* fix(daemon): prove Codex registration ownership by data root and app
Tighten the ownership check from d2d47a5f8e. Matching the discovery
endpoints alone still let a copy of the app that reuses the installed
namespace take over the registration (the endpoints depend only on
channel, namespace and source), and it could never refresh a
registration written before the managed contract, which has no
discovery value and still names a payload version that cleanup removes.
Read the existing registration and rewrite it only when it serves the
same daemon data root and bootstraps the same installed app; the managed
marker is ignored so a pre-managed registration of this install still
counts. Anything else, including a registration that cannot be read, is
left untouched.
---------
Co-authored-by: Looper <looper@noreply.github.com>
37 lines
1.4 KiB
TypeScript
37 lines
1.4 KiB
TypeScript
import { isAbsolute } from "node:path";
|
|
import { afterEach, describe, expect, it } from "vitest";
|
|
|
|
import { resolveSidecarClientEndpoint, type SidecarStamp } from "../src/index.js";
|
|
|
|
const stamp: SidecarStamp = {
|
|
app: "daemon",
|
|
channel: "prerelease",
|
|
mode: "headless",
|
|
namespace: "release-prerelease",
|
|
source: "packaged",
|
|
};
|
|
|
|
describe("resolveSidecarClientEndpoint", () => {
|
|
const originalTmpdir = process.env.TMPDIR;
|
|
afterEach(() => {
|
|
if (originalTmpdir == null) delete process.env.TMPDIR;
|
|
else process.env.TMPDIR = originalTmpdir;
|
|
});
|
|
|
|
it("distinguishes modes of the same namespace", () => {
|
|
expect(resolveSidecarClientEndpoint(stamp)).not.toBe(resolveSidecarClientEndpoint({ ...stamp, mode: "runtime" }));
|
|
});
|
|
|
|
it.skipIf(process.platform === "win32")("resolves an absolute path under the caller's temporary directory", () => {
|
|
process.env.TMPDIR = "/private/var/folders/od-endpoint-test/T";
|
|
const endpoint = resolveSidecarClientEndpoint(stamp);
|
|
expect(isAbsolute(endpoint)).toBe(true);
|
|
expect(endpoint.startsWith("/private/var/folders/od-endpoint-test/T/")).toBe(true);
|
|
|
|
// A receiver with a different TMPDIR would compute a different path, which
|
|
// is why registrations must carry this value rather than the stamp.
|
|
process.env.TMPDIR = "/tmp";
|
|
expect(resolveSidecarClientEndpoint(stamp)).not.toBe(endpoint);
|
|
});
|
|
});
|