@hellolynk-ai/codex-plugin 0.2.101 → 0.2.102-macos.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +17 -17
  2. package/bin/hellolynk-client-setup.mjs +72 -11
  3. package/hellolynk-client-pack-manifest.json +109 -69
  4. package/hellolynk-client-pack-pin.json +1 -1
  5. package/package.json +8 -4
  6. package/plugins/hellolynk-codex/.codex-plugin/plugin.json +1 -1
  7. package/plugins/hellolynk-codex/.mcp.json +20 -19
  8. package/plugins/hellolynk-codex/package.json +1 -1
  9. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +1 -1
  10. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +25 -2
  11. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-darwin-host.mjs +256 -0
  12. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-darwin-vault.mjs +153 -0
  13. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +20 -8
  14. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +3 -7
  15. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +68 -14
  16. package/plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin +0 -0
  17. package/plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.swift +149 -0
  18. package/plugins/hellolynk-codex/runtime/connect-activate.mjs +5 -1
  19. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +1 -1
  20. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +25 -2
  21. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +7 -3
  22. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +24 -8
  23. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +23 -9
  24. package/plugins/hellolynk-codex/runtime/core/hellolynk-darwin-host.mjs +256 -0
  25. package/plugins/hellolynk-codex/runtime/core/hellolynk-darwin-vault.mjs +153 -0
  26. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +20 -8
  27. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +53 -6
  28. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +22 -7
  29. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +7 -5
  30. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +3 -7
  31. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +9 -3
  32. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +1 -1
  33. package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +68 -14
  34. package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +78 -2
  35. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +5 -3
  36. package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +1 -1
  37. package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +1 -1
  38. package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +58 -26
  39. package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +1 -1
  40. package/plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.bin +0 -0
  41. package/plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.swift +149 -0
  42. package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +15 -9
  43. package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -1193
  44. package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -33
  45. package/plugins/hellolynk-codex/server/workbench-runtime.mjs +42 -42
  46. package/tools/hellolynk-activate-neutral-host-clients.mjs +5 -1
@@ -1,3 +1,4 @@
1
+ import { DARWIN_HELPER, darwinHelperEnvironment } from "./hellolynk-darwin-vault.mjs";
1
2
  import crypto from "node:crypto";
2
3
  import { spawn } from "node:child_process";
3
4
  import { StringDecoder } from "node:string_decoder";
@@ -583,7 +584,7 @@ export function createWindowsNativeHumanConfirmationLauncher() {
583
584
  await context.deliver?.({ kind: "failure", code: "human_confirmation_timed_out" });
584
585
  return;
585
586
  }
586
- if (process.platform !== "win32") {
587
+ if (!["darwin","win32"].includes(process.platform)) {
587
588
  await context.deliver?.({ kind: "failure", code: "human_confirmation_platform_unsupported" });
588
589
  return;
589
590
  }
@@ -592,12 +593,12 @@ export function createWindowsNativeHumanConfirmationLauncher() {
592
593
  await context.deliver?.({ kind: "failure", code: "human_confirmation_request_too_large" });
593
594
  return;
594
595
  }
595
- await runPowerShellChild(serialized, context);
596
+ await runNativeDialogChild(serialized, context);
596
597
  },
597
598
  });
598
599
  }
599
600
 
600
- async function runPowerShellChild(serialized, context) {
601
+ async function runNativeDialogChild(serialized, context) {
601
602
  await new Promise((resolve) => {
602
603
  let delivered = false;
603
604
  let stdout = "";
@@ -605,13 +606,14 @@ async function runPowerShellChild(serialized, context) {
605
606
  let stdoutBytes = 0;
606
607
  let stderrBytes = 0;
607
608
  let forcedCode = null;
608
- const child = spawn("powershell.exe", [
609
+ const darwin = process.platform === "darwin";
610
+ const child = spawn(darwin ? DARWIN_HELPER : "powershell.exe", darwin ? ["confirm"] : [
609
611
  "-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass",
610
612
  "-STA", "-File", POWERSHELL_SCRIPT,
611
613
  ], {
612
614
  windowsHide: true,
613
615
  stdio: ["pipe", "pipe", "pipe"],
614
- env: sanitizeHelloLynkChildEnvironment(process.env),
616
+ env: darwin ? darwinHelperEnvironment() : sanitizeHelloLynkChildEnvironment(process.env),
615
617
  });
616
618
  const finish = async (result) => {
617
619
  if (delivered) return;
@@ -12,10 +12,9 @@ import {
12
12
  } from "node:fs/promises";
13
13
  import { basename, dirname, isAbsolute, join, parse, resolve } from "node:path";
14
14
  import { spawn } from "node:child_process";
15
- import { homedir } from "node:os";
16
15
  import { performance } from "node:perf_hooks";
17
16
  import { withLocalProcessLock } from "./hellolynk-local-process-lock.mjs";
18
- import { readClientProfile } from "./hellolynk-client-profile.mjs";
17
+ import { defaultHelloLynkClientRoot, readClientProfile } from "./hellolynk-client-profile.mjs";
19
18
  import { assertIdentifier } from "./hellolynk-host-auth.mjs";
20
19
  import runtimeSafety from "./hellolynk-runtime-safety.cjs";
21
20
 
@@ -46,10 +45,7 @@ export function defaultNeutralHostStoreRoot(platform = process.platform, env = p
46
45
  }
47
46
  return root;
48
47
  }
49
- const home = resolve(String(env.HOME || env.USERPROFILE || homedir()));
50
- if (platform === "win32") return resolve(String(env.LOCALAPPDATA || join(home, "AppData", "Local")), "HelloLynk", "host");
51
- if (platform === "darwin") return join(home, "Library", "Application Support", "HelloLynk", "host");
52
- return resolve(String(env.XDG_DATA_HOME || join(home, ".local", "share")), "hellolynk", "host");
48
+ return join(defaultHelloLynkClientRoot(platform, env), "host");
53
49
  }
54
50
 
55
51
  export function createNeutralHostInstallationStore(options = {}) {
@@ -344,7 +340,7 @@ async function assertReadOnlyPath(path, directory, verifyPath) {
344
340
  if (metadata.isSymbolicLink() || (directory ? !metadata.isDirectory() : !metadata.isFile())) {
345
341
  throw codeError(directory ? "host_store_root_invalid" : "host_registry_path_invalid");
346
342
  }
347
- if (process.platform === "win32") {
343
+ if (process.platform === "win32" || (process.platform === "darwin" && verifyPath)) {
348
344
  await verifyPath({ path, directory });
349
345
  }
350
346
  }
@@ -48,7 +48,7 @@ const SAFE_BOOTSTRAP_FAILURE_CODES = new Set([
48
48
  "neutral_trae_work_uncaught_exception",
49
49
  "powershell7_missing",
50
50
  ]);
51
- const SAFE_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:cjs|cs|css|html|js|json|md|mjs|ps1|vbs)$/;
51
+ const SAFE_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:bin|cjs|cs|css|html|js|json|md|mjs|ps1|swift|vbs)$/;
52
52
  const REQUIRED_RUNTIME_RECORDS = Object.freeze([
53
53
  "neutral-host-bootstrap.mjs",
54
54
  "neutral-host-release-loader.mjs",
@@ -63,8 +63,14 @@ const REQUIRED_RUNTIME_RECORDS = Object.freeze([
63
63
  "core/hellolynk-neutral-host-process.mjs",
64
64
  "core/hellolynk-neutral-host-workbench-read-model.mjs",
65
65
  "core/hellolynk-workbench-action-grants.mjs",
66
- "platform/windows/hellolynk-host-protected-store.ps1",
67
- "platform/windows/hellolynk-local-data-key.ps1",
66
+ ...(process.platform === "darwin" ? [
67
+ "core/hellolynk-darwin-vault.mjs",
68
+ "core/hellolynk-darwin-host.mjs",
69
+ "platform/darwin/hellolynk-keychain-helper.bin",
70
+ ] : [
71
+ "platform/windows/hellolynk-host-protected-store.ps1",
72
+ "platform/windows/hellolynk-local-data-key.ps1",
73
+ ]),
68
74
  "workbench/contract.mjs",
69
75
  ]);
70
76
 
@@ -34,7 +34,7 @@ export const HELLOLYNK_NEUTRAL_HOST_PROCESS_CONTRACT = "hellolynk-neutral-host-p
34
34
  export const HELLOLYNK_NEUTRAL_HOST_DISCOVERY_CONTRACT = "hellolynk-neutral-host-discovery-v1";
35
35
  // Raised with every release batch, together with the six client versions. A Host reports it
36
36
  // in its health; the higher release stays and the lower one makes room.
37
- export const HELLOLYNK_HOST_RELEASE_ORDINAL = 14;
37
+ export const HELLOLYNK_HOST_RELEASE_ORDINAL = 15;
38
38
 
39
39
  const INSTALLATION_LOCK_CONTRACT = "hellolynk-neutral-host-installation-lock-v2";
40
40
  const INSTALLATION_LOCK_MAX_BYTES = 4 * 1024;
@@ -1,3 +1,4 @@
1
+ import { checkDarwinPath, DARWIN_HELPER, invokeDarwinHelper, withDarwinVault } from "./hellolynk-darwin-vault.mjs";
1
2
  import { AsyncLocalStorage } from "node:async_hooks";
2
3
  import { spawn as nodeSpawn } from "node:child_process";
3
4
  import { randomBytes } from "node:crypto";
@@ -260,6 +261,14 @@ export async function runWindowsClientTrustCommand(command, options = {}) {
260
261
  }
261
262
 
262
263
  export async function prepareWindowsClientRoot(clientRoot, options = {}) {
264
+ if ((options.platform || process.platform) === "darwin") {
265
+ checkDarwinPath(clientRoot, { directory: true, create: true, privateMode: true });
266
+ for (const sub of ["profiles", "credentials", "e2ee-private-keys", "status", "host", "host/runtime", "releases"]) {
267
+ checkDarwinPath(join(clientRoot, sub), { directory: true, create: true, privateMode: true });
268
+ }
269
+ await invokeDarwinHelper(options.helperPath || DARWIN_HELPER, ["verify-acl", clientRoot]);
270
+ return "prepared";
271
+ }
263
272
  return runWindowsClientTrustCommand("PrepareRoot", {
264
273
  ...options,
265
274
  clientRoot,
@@ -272,6 +281,14 @@ export async function secureWindowsClientReleaseTree(clientRoot, releaseId, opti
272
281
  if (!/^(?!\.{1,2}$)[A-Za-z0-9.][A-Za-z0-9_.-]{2,191}$/.test(acceptedReleaseId)) {
273
282
  throw new HelloLynkCredentialStoreError("client_release_path_invalid");
274
283
  }
284
+ if ((options.platform || process.platform) === "darwin") {
285
+ checkDarwinPath(clientRoot, { directory: true, privateMode: true });
286
+ checkDarwinPath(join(clientRoot, "releases"), { directory: true, privateMode: true });
287
+ const release = join(clientRoot, "releases", acceptedReleaseId);
288
+ checkDarwinPath(release, { directory: true });
289
+ await invokeDarwinHelper(options.helperPath || DARWIN_HELPER, [options.verifyOnly ? "verify-tree" : "secure-tree", release]);
290
+ return options.verifyOnly ? "verified" : "secured";
291
+ }
275
292
  return runWindowsClientTrustCommand(options.verifyOnly === true ? "VerifyReleaseTree" : "SecureReleaseTree", {
276
293
  ...options,
277
294
  clientRoot,
@@ -460,24 +477,61 @@ export function createPlatformCredentialStore(options = {}) {
460
477
  }
461
478
 
462
479
  if (platform === "darwin") {
463
- const helper = options.helperPath || join(
464
- homedir(), "Library", "Application Support", "HelloLynk", "bin", "hellolynk-keychain-helper",
465
- );
466
- const invoke = (command, profileId, stdin) => run(
467
- helper,
468
- [command.toLowerCase(), profileId],
469
- { ...runnerOptions, stdin },
470
- );
480
+ const clientRoot = options.clientRoot || (options.storeRoot ? windowsClientRootForStore(options.storeRoot)
481
+ : join(homedir(), "Library", "Application Support", "HelloLynk"));
482
+ if (clientRoot !== resolve(clientRoot) || clientRoot === "/") throw new HelloLynkCredentialStoreError("client_root_path_invalid");
483
+ const credentials = join(clientRoot, "credentials");
484
+ const privateKeys = join(clientRoot, "e2ee-private-keys");
485
+ const root = options.storeRoot || credentials;
486
+ const e2eeRoot = options.e2eeStoreRoot || privateKeys;
487
+ if (root !== clientRoot && root !== credentials) throw new HelloLynkCredentialStoreError("credential_store_root_invalid");
488
+ if (e2eeRoot !== privateKeys) throw new HelloLynkCredentialStoreError("e2ee_private_key_store_root_invalid");
489
+ function entry(location, section, profile, operation, value) {
490
+ const slot = section + ":" + assertCredentialProfileId(profile);
491
+ return withDarwinVault({ root: location, helperPath: options.helperPath, readOnly: operation === "load", allowMissing:true }, data => {
492
+ if (operation === "save") data[slot] = value;
493
+ if (operation === "remove") delete data[slot];
494
+ return data[slot] ?? null;
495
+ });
496
+ }
471
497
  return {
472
498
  platform,
473
- async save(profile, credential) {
474
- await invoke("Store", assertCredentialProfileId(profile), assertCredential(credential));
475
- },
499
+ async save(profile, credential) { await entry(root, "device", profile, "save", assertCredential(credential)); },
476
500
  async load(profile) {
477
- return assertCredential(await invoke("Load", assertCredentialProfileId(profile), ""));
501
+ const value = await entry(root, "device", profile, "load");
502
+ if (value === null) throw new HelloLynkCredentialStoreError("device_credential_missing");
503
+ return assertCredential(value);
478
504
  },
479
- async remove(profile) {
480
- await invoke("Remove", assertCredentialProfileId(profile), "");
505
+ async remove(profile) { await entry(root, "device", profile, "remove"); },
506
+ runtimeSessions: {
507
+ async save(profile, session) { await entry(root, "session", profile, "save", assertRuntimeSession(session)); },
508
+ async load(profile) {
509
+ const value = await entry(root, "session", profile, "load");
510
+ return value === null ? null : assertRuntimeSession(value);
511
+ },
512
+ async remove(profile) { await entry(root, "session", profile, "remove"); },
513
+ async withLock(profile, operation) {
514
+ const id = assertCredentialProfileId(profile);
515
+ if (typeof operation !== "function") throw new HelloLynkCredentialStoreError("runtime_session_lock_operation_invalid");
516
+ checkDarwinPath(root, { directory:true, create:true, privateMode:true });
517
+ return withRuntimeSessionFileLock(root, id, operation, options.runtimeSessionLockDependencies, options.timeoutMs);
518
+ },
519
+ },
520
+ directTextPrivateKeys: {
521
+ async save(profile, material) { await entry(e2eeRoot, "keys", profile, "save", encodeDirectTextPrivateKeyMaterial(material)); },
522
+ async load(profile) {
523
+ const value = await entry(e2eeRoot, "keys", profile, "load");
524
+ return value === null ? null : decodeDirectTextPrivateKeyMaterial(value);
525
+ },
526
+ async remove(profile) { await entry(e2eeRoot, "keys", profile, "remove"); },
527
+ async preload(profiles) {
528
+ const ids = [...new Set((Array.isArray(profiles) ? profiles : []).map(assertCredentialProfileId))];
529
+ if (!ids.length) return;
530
+ if (ids.length > MAX_E2EE_BATCH_PROFILES) throw new HelloLynkCredentialStoreError("e2ee_private_key_batch_invalid");
531
+ await withDarwinVault({root:e2eeRoot, helperPath:options.helperPath, readOnly:true, allowMissing:true}, data => {
532
+ for (const id of ids) if (data["keys:" + id] !== undefined) decodeDirectTextPrivateKeyMaterial(data["keys:" + id]);
533
+ });
534
+ },
481
535
  },
482
536
  };
483
537
  }
@@ -2,6 +2,7 @@ import fs from 'node:fs/promises';
2
2
  import path from 'node:path';
3
3
  import crypto from 'node:crypto';
4
4
  import { execFile } from 'node:child_process';
5
+ import { homedir } from 'node:os';
5
6
  import { promisify } from 'node:util';
6
7
 
7
8
  const execute = promisify(execFile);
@@ -27,7 +28,7 @@ const equal = (a, b) => process.platform === 'win32' ? a.toLowerCase() === b.toL
27
28
  // recent successful binding, even when its command line names only the cache.
28
29
  // A checkout/uninstalled fixture has no immutable release to retain.
29
30
  export async function recordCodexReleaseProcess(packageRoot) {
30
- if (process.platform !== 'win32' || !path.isAbsolute(packageRoot || '')
31
+ if (!['win32', 'darwin'].includes(process.platform) || !path.isAbsolute(packageRoot || '')
31
32
  || path.basename(path.dirname(packageRoot)) !== 'releases' || !/^codex-[a-f0-9]{64}$/.test(path.basename(packageRoot))) return;
32
33
  try {
33
34
  const root = await rootPath(path.dirname(packageRoot));
@@ -70,6 +71,10 @@ async function regularTree(target) {
70
71
  // older Host may still import files after installation; deletion waits for the
71
72
  // next successful install instead of stopping any user process.
72
73
  export async function runningReleaseNames(releasesRoot) {
74
+ if (process.platform === 'darwin') {
75
+ const pattern = darwinReleasePattern(await rootPath(releasesRoot), releasesRoot);
76
+ return darwinReleaseNames(await darwinPs('command='), pattern);
77
+ }
73
78
  if (process.platform !== 'win32') throw failure('release_retention_process_check_unavailable');
74
79
  const root = await rootPath(releasesRoot);
75
80
  const literal = `${root}${path.sep}`.replaceAll("'", "''");
@@ -89,8 +94,12 @@ export async function runningReleaseNames(releasesRoot) {
89
94
  // with a positive pid are listed, and command lines never leave PowerShell.
90
95
  export async function entryProcessList(client, releasesRoot, pids = []) {
91
96
  if (!ENTRY[client] && client !== 'codex') return [];
92
- if (process.platform !== 'win32') throw failure('release_retention_process_check_unavailable');
93
97
  if (!Array.isArray(pids) || pids.some(pid => !Number.isSafeInteger(pid) || pid <= 0)) throw failure('release_retention_process_check_invalid');
98
+ if (process.platform === 'darwin' && client === 'codex') {
99
+ await rootPath(releasesRoot);
100
+ return parseDarwinCodexEntryProcesses(await darwinPs('pid=,ppid=,lstart=,command='), pids);
101
+ }
102
+ if (process.platform !== 'win32') throw failure('release_retention_process_check_unavailable');
94
103
  const root = await rootPath(releasesRoot);
95
104
  // This installation's entry only, in its given and canonical spellings:
96
105
  // any case, either slash, quoted or not.
@@ -112,6 +121,45 @@ export async function entryProcessList(client, releasesRoot, pids = []) {
112
121
  return list;
113
122
  }
114
123
 
124
+ async function darwinPs(fields) {
125
+ const { stdout } = await execute('/bin/ps', ['-ww', '-axo', fields], {
126
+ env: { LC_ALL: 'C', TZ: 'UTC0' }, timeout: 15000, maxBuffer: 4 * 1024 * 1024,
127
+ });
128
+ return stdout;
129
+ }
130
+
131
+ // Keep command lines inside the process probe. Only a PID, its start time and
132
+ // whether it might still be serving the Codex cache reach the retention logic.
133
+ export function parseDarwinCodexEntryProcesses(stdout, pids = []) {
134
+ const processes = new Map();
135
+ for (const line of stdout.trim().split('\n')) {
136
+ const fields = /^\s*(\d+)\s+(\d+)\s+(\w{3}\s+\w{3}\s+\d{1,2}\s+\d{2}:\d{2}:\d{2}\s+\d{4})\s+(.+)$/u.exec(line);
137
+ if (!fields) throw failure('release_retention_process_check_invalid');
138
+ const [, pid, parent, started, command] = fields;
139
+ const startedAt = Date.parse(`${started} UTC`);
140
+ processes.set(Number(pid), { pid: Number(pid), parent: Number(parent), startedAt: Number.isFinite(startedAt) ? startedAt : null, command });
141
+ }
142
+ const wanted = new Set(pids);
143
+ const proxy = /(?:^|["\s/])(?:\.\/)?server\/mcp-proxy\.mjs(?=["\s]|$)/u;
144
+ const codexApp = /(?:^|["/])(?:Codex|ChatGPT)\.app\/Contents\//iu;
145
+ const codexCli = /^(?:"?[^"\s]*\/)?codex(?=["\s]|$)/iu;
146
+ const result = [];
147
+ for (const { pid, parent, startedAt, command } of processes.values()) {
148
+ let entry = false;
149
+ if (proxy.test(command) && /(?:^|\/)node(?=["\s]|$)/u.test(command)) {
150
+ entry = /\/cua_node\/bin\/node(?=["\s]|$)/u.test(command)
151
+ || /\/hellolynk-codex\/server\/mcp-proxy\.mjs(?=["\s]|$)/u.test(command);
152
+ const seen = new Set([pid]);
153
+ for (let ancestor = processes.get(parent); !entry && ancestor && !seen.has(ancestor.pid); ancestor = processes.get(ancestor.parent)) {
154
+ entry = codexApp.test(ancestor.command) || codexCli.test(ancestor.command);
155
+ seen.add(ancestor.pid);
156
+ }
157
+ }
158
+ if (entry || wanted.has(pid)) result.push({ pid, startedAt, entry });
159
+ }
160
+ return result;
161
+ }
162
+
115
163
  // Releases named by live entry records, and whether a process started through
116
164
  // the stable entry has no matching record (one from before records existed).
117
165
  // Records of exited or reused pids are removed, so a release can go at the
@@ -149,6 +197,24 @@ async function entryReleases(releasesRoot, root, client, pattern, dependencies)
149
197
  // A stopped on-demand task is still a configuration reference. Keeping only
150
198
  // live processes can delete its executable before the next client startup.
151
199
  export async function configuredReleaseNames(releasesRoot) {
200
+ if (process.platform === 'darwin') {
201
+ const pattern = darwinReleasePattern(await rootPath(releasesRoot), releasesRoot);
202
+ const names = new Set();
203
+ for (const directory of [path.join(homedir(), 'Library', 'LaunchAgents'), '/Library/LaunchAgents', '/Library/LaunchDaemons']) {
204
+ let entries;
205
+ try { entries = await fs.readdir(directory); }
206
+ catch (error) { if (error.code === 'ENOENT') continue; throw error; }
207
+ for (const entry of entries.filter(name => /hellolynk/i.test(name) && name.endsWith('.plist'))) {
208
+ const { stdout } = await execute('/usr/bin/plutil', ['-convert', 'json', '-o', '-', path.join(directory, entry)],
209
+ { timeout: 15000, maxBuffer: 256 * 1024 });
210
+ const { Program, WorkingDirectory, ProgramArguments } = JSON.parse(stdout);
211
+ for (const value of [Program, WorkingDirectory, ...(ProgramArguments || [])]) {
212
+ if (typeof value === 'string') for (const name of darwinReleaseNames(value, pattern)) names.add(name);
213
+ }
214
+ }
215
+ }
216
+ return [...names].sort();
217
+ }
152
218
  if (process.platform !== 'win32') throw failure('release_retention_configuration_check_unavailable');
153
219
  const root = await rootPath(releasesRoot);
154
220
  const prefixes = [...new Set([root, path.resolve(releasesRoot)])].map(value => (value + path.sep).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
@@ -164,6 +230,16 @@ export async function configuredReleaseNames(releasesRoot) {
164
230
  return names;
165
231
  }
166
232
 
233
+ function darwinReleasePattern(root, logicalRoot) {
234
+ const prefixes = [...new Set([root, path.resolve(logicalRoot)])]
235
+ .map(value => (value + path.sep).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
236
+ return new RegExp('(?:' + prefixes.join('|') + ')((?:' + clients.join('|') + ')-[a-f0-9]{64})(?=[/"\\s]|$)', 'g');
237
+ }
238
+
239
+ function darwinReleaseNames(text, pattern) {
240
+ return [...new Set([...text.matchAll(pattern)].map(match => match[1]))].sort();
241
+ }
242
+
167
243
  // Call only after the caller's complete adoption transaction succeeds, while
168
244
  // holding its install/account lock. Never call from candidate staging or a
169
245
  // rollback. The receipt records adoption order, not directory timestamps.
@@ -60,7 +60,7 @@ export function validateWorkbenchConnectionInput(value) {
60
60
  export function createWorkbenchConnection(options) {
61
61
  if (!CLIENTS.has(options?.client) || !path.isAbsolute(options?.releaseRoot || "")) throw code("workbench_connect_configuration_invalid");
62
62
  const env = options.env || process.env;
63
- const storeRoot = options.storeRoot || env.HELLOLYNK_NEUTRAL_STORE_ROOT || defaultNeutralHostStoreRoot("win32", env);
63
+ const storeRoot = options.storeRoot || env.HELLOLYNK_NEUTRAL_STORE_ROOT || defaultNeutralHostStoreRoot(process.platform, env);
64
64
  if (!path.isAbsolute(storeRoot)) throw code("workbench_connect_configuration_invalid");
65
65
  let busy = false;
66
66
  const accounts = createClientAccountActions(options);
@@ -87,7 +87,7 @@ export function createWorkbenchAccountActions(options) {
87
87
  }
88
88
 
89
89
  async function connectAccount(client, sourceRoot, storeRoot, input) {
90
- if (process.platform !== "win32") throw code("workbench_connect_windows_only", 400);
90
+ if (!["win32", "darwin"].includes(process.platform)) throw code("workbench_connect_platform_unsupported", 400);
91
91
  if (!CLIENTS.has(client) || !path.isAbsolute(sourceRoot) || !path.isAbsolute(storeRoot)
92
92
  || path.basename(storeRoot).toLowerCase() !== "host") throw code("workbench_connect_configuration_invalid");
93
93
  const value = validateWorkbenchConnectionInput(input);
@@ -181,7 +181,9 @@ async function retainRelease(binding, currentRoot, client) {
181
181
  const destination = path.resolve(stage, relative);
182
182
  if (!destination.startsWith(`${stage}${path.sep}`)) throw code("workbench_connect_release_invalid");
183
183
  await fs.mkdir(path.dirname(destination), { recursive: true, mode: 0o700 });
184
- await fs.writeFile(destination, await fs.readFile(path.join(sourceRoot, relative)), { flag: "wx", mode: 0o600 });
184
+ await fs.writeFile(destination, await fs.readFile(path.join(sourceRoot, relative)), {
185
+ flag: "wx", mode: relative.endsWith(".bin") ? 0o700 : 0o600,
186
+ });
185
187
  }
186
188
  await verifyPinnedNeutralHostRuntime({ ...binding, releaseRoot: stage });
187
189
  await secureWindowsClientReleaseTree(currentRoot, stageId);
@@ -10,7 +10,7 @@ import { startHelloLynkNeutralHostAssembly } from "./core/hellolynk-neutral-host
10
10
 
11
11
  export async function createNeutralHostReleaseRuntime() {
12
12
  return Object.freeze({
13
- defaultStoreRoot: (env) => defaultNeutralHostStoreRoot("win32", env),
13
+ defaultStoreRoot: (env) => defaultNeutralHostStoreRoot(process.platform, env),
14
14
  createInstallationStore: (options) => createNeutralHostInstallationStore(options),
15
15
  createProtectedStore: (options) => createHostProtectedStore(options),
16
16
  run: async ({ root, binding }) => runProductionHost(root, binding),
@@ -6,7 +6,7 @@ import { fileURLToPath, pathToFileURL } from "node:url";
6
6
  const ROOT = path.dirname(fileURLToPath(import.meta.url));
7
7
  const PIN_PATH = path.join(ROOT, "neutral-host-release-pin.json");
8
8
  const MANIFEST_PATH = path.join(ROOT, "neutral-host-release-manifest.json");
9
- const SAFE_RUNTIME_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:cjs|mjs|ps1)$/;
9
+ const SAFE_RUNTIME_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:cjs|mjs|ps1|swift|bin)$/;
10
10
  const SAFE_RELEASE_RELATIVE = /^release\/(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:mjs|ps1|md|html|js|css|json)$/;
11
11
 
12
12
  export async function inspectNeutralHostRelease() {
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  {
6
6
  "path": "release/package.json",
7
- "sha256": "5875df8d4163c0a84b3dc30265b1a5e7b977a7c92250aae013d3b513b3640c40"
7
+ "sha256": "befbca064334e8ff7f9d192f095ac1bd786a3882385a450fc2c2d2a5c9b2b7dd"
8
8
  },
9
9
  {
10
10
  "path": "neutral-host-bootstrap.mjs",
@@ -12,15 +12,15 @@
12
12
  },
13
13
  {
14
14
  "path": "connect-activate.mjs",
15
- "sha256": "50ec8b0a58ac5b099a653722b0946acb23d0a29a054bfb3856dc963db6f84bee"
15
+ "sha256": "93c68c23ed2d18e87766ecd8dae6c3d8e757eca6ad7569477c8ec994402a8b0e"
16
16
  },
17
17
  {
18
18
  "path": "neutral-host-release-loader.mjs",
19
- "sha256": "15593e237fd5cbd765b65e60efe7a1c87e24a5390ad932c8a23d2c03b8da9770"
19
+ "sha256": "283a20c6a9b90481bc03c143ab51d5289abf7b7a86fbe51fba7ea2ae1e6d2c60"
20
20
  },
21
21
  {
22
22
  "path": "neutral-host-release-entry.mjs",
23
- "sha256": "23e88d39362fe556eb96f86b2ac9740c0390f33f01edee5c89d708c80c4f8fa6"
23
+ "sha256": "698d7e80d38353d56c7f1c337fbc676e8649d08afc726ffcb31215dc11fc8455"
24
24
  },
25
25
  {
26
26
  "path": "core/hellolynk-agent-configurator.mjs",
@@ -28,7 +28,7 @@
28
28
  },
29
29
  {
30
30
  "path": "core/hellolynk-client-account.mjs",
31
- "sha256": "ab7ec5dda92e510e7deb14f900fd2df4c6c3373fbeb4af4fb2b6c1a72aa41a99"
31
+ "sha256": "6e03a05127d4dc86be6391567b51a956d1a987acf4e23f5699ce0e00c3187b7e"
32
32
  },
33
33
  {
34
34
  "path": "core/hellolynk-client-core.mjs",
@@ -36,23 +36,23 @@
36
36
  },
37
37
  {
38
38
  "path": "core/hellolynk-client-profile.mjs",
39
- "sha256": "4b45a78b4d6c4794a1f494bd95777579c32c56a68321a1d3b484048942ae4742"
39
+ "sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
40
40
  },
41
41
  {
42
42
  "path": "core/hellolynk-client-update-entry.mjs",
43
- "sha256": "b2740406d79d0fa129c3bd1b5e6a07c3f52eed5e35a91ffea40b099ddc708936"
43
+ "sha256": "1e513a4a98437dc60b49f9f39fc3958808c3acfaeb6c5bb638efea2e61d4b1ee"
44
44
  },
45
45
  {
46
46
  "path": "core/hellolynk-client-update-runtime.mjs",
47
- "sha256": "3ed31f0cea704c3ad26481126a78a7d39c21df69a5c52d1074a096a2b7b3e32f"
47
+ "sha256": "96aa12ba21619a0cd78ef61c63be6f43fdb61f892201ce923ee35e0912ae2e52"
48
48
  },
49
49
  {
50
50
  "path": "core/hellolynk-client-update.mjs",
51
- "sha256": "8f0d6f54313c91bd2ffb4643859a91dd1e122bb5f1da9143fe2851a3370d7fbf"
51
+ "sha256": "93bf5f2ecc72731d70367306989fa18c82fa7bf9e4b5f785a1376f0d55239428"
52
52
  },
53
53
  {
54
54
  "path": "core/hellolynk-release-retention.mjs",
55
- "sha256": "9b02a42a510fa5ef4a251b6e675f9ccc60f20b718d17c1617027cd048c5634dd"
55
+ "sha256": "7cfd7e20c37ac1479f2472cc5fba8e830dcd1b83b91a86c340749cf145f0d661"
56
56
  },
57
57
  {
58
58
  "path": "core/hellolynk-contact-request-e2ee.mjs",
@@ -62,6 +62,14 @@
62
62
  "path": "core/hellolynk-device-enrollment.mjs",
63
63
  "sha256": "f3e99ff2bdfa76e5a830a518792a1b9e5e705d7a8a01eca725dd7c5c8391d4ef"
64
64
  },
65
+ {
66
+ "path": "core/hellolynk-darwin-host.mjs",
67
+ "sha256": "0772d7a23280b18d5bc59383a6a14fd2ecfd9de1ec53b351be0250770e3e2e70"
68
+ },
69
+ {
70
+ "path": "core/hellolynk-darwin-vault.mjs",
71
+ "sha256": "b6094d856af016e674e4a0183a4e474397bbe92e66638743fa62fe085b554d30"
72
+ },
65
73
  {
66
74
  "path": "core/hellolynk-direct-text-e2ee-client.mjs",
67
75
  "sha256": "4007bf5e6c91df00adf10384cc1f0d0d28491b8ded518365dddbac717f3ac5be"
@@ -76,7 +84,7 @@
76
84
  },
77
85
  {
78
86
  "path": "core/hellolynk-host-protected-store.mjs",
79
- "sha256": "89b6e6c4ce9c6ba2984f8c301de4251da6004bdd402cc44d578a4505c21b75b1"
87
+ "sha256": "10f81f4f6a5dd2fe9a742e847b2eb47dbf20f802f4414f46a05ccbc3505148e2"
80
88
  },
81
89
  {
82
90
  "path": "core/hellolynk-host-transport-client.mjs",
@@ -108,7 +116,7 @@
108
116
  },
109
117
  {
110
118
  "path": "core/hellolynk-human-confirmation-ledger.mjs",
111
- "sha256": "63bcbbb0870ad83fc031636d74ce77fc070d5f4d1d4c10297f8b3ec9ad85bbf6"
119
+ "sha256": "43b6b6baea422eb226c850f15966c907d834bc2f5aadbab6ee0c9e05445f182f"
112
120
  },
113
121
  {
114
122
  "path": "core/hellolynk-human-relationship-controller.mjs",
@@ -140,7 +148,7 @@
140
148
  },
141
149
  {
142
150
  "path": "core/hellolynk-local-data-protection.mjs",
143
- "sha256": "1bda879aa0bdf46700a63374e82cc7bc639385656b1463a75a49e8510971066c"
151
+ "sha256": "cea4f73ec5a31918c48bd8234e28d38ff1bb8028d2591f542b15ee12b2238e1f"
144
152
  },
145
153
  {
146
154
  "path": "core/hellolynk-local-identity-trust.mjs",
@@ -172,7 +180,7 @@
172
180
  },
173
181
  {
174
182
  "path": "core/hellolynk-native-human-confirmation-broker.mjs",
175
- "sha256": "2fb2d3b40869362be3e40a1ec55947021319af5315cbf8fdc5ed59df717cceb3"
183
+ "sha256": "f190c28af2703b58a685a57213b2789df7eda35d7699d718f9420cd1dddd1b65"
176
184
  },
177
185
  {
178
186
  "path": "core/hellolynk-neutral-host-assembly.mjs",
@@ -180,15 +188,15 @@
180
188
  },
181
189
  {
182
190
  "path": "core/hellolynk-neutral-host-installation-store.mjs",
183
- "sha256": "b5f6288732af6f9b9c0c750f6002eae657dde9513fc97674f6e59200f3cc9dc6"
191
+ "sha256": "42ae1e7deb876a8308b7bfed6a7b891d1834117b62504fc5befe3f91a0b4c740"
184
192
  },
185
193
  {
186
194
  "path": "core/hellolynk-neutral-host-launcher.mjs",
187
- "sha256": "789b2531cdbde6e6d8f91f2a40746372e903e9e48e8aa6846b9abf2d28150780"
195
+ "sha256": "d94b853a7ed1ebc9c7e9bd7c37e2405e39849fb6d4ddf3d697e17ba169eda6d2"
188
196
  },
189
197
  {
190
198
  "path": "core/hellolynk-neutral-host-process.mjs",
191
- "sha256": "af6cb6585baae477f87ee880bd044dffb4c7f71667bfa78bcbf0d4d20e7483af"
199
+ "sha256": "e744bacf65f7b86cbcf7d654189c0e07e09401083e2b6f9053b17c1c3bacd1b4"
192
200
  },
193
201
  {
194
202
  "path": "core/hellolynk-neutral-host-store-adapters.mjs",
@@ -200,7 +208,7 @@
200
208
  },
201
209
  {
202
210
  "path": "core/hellolynk-platform-credential-store.mjs",
203
- "sha256": "f27f5f377cc4d6d2b5664212cf8fcc4c98d910064c1677ff9c1f776df1a59dba"
211
+ "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
204
212
  },
205
213
  {
206
214
  "path": "core/hellolynk-relay-client-cli.mjs",
@@ -216,7 +224,7 @@
216
224
  },
217
225
  {
218
226
  "path": "core/hellolynk-workbench-connection.mjs",
219
- "sha256": "d3396350fd75af78e60772e021cf6dfb643d9eee41894c5461c959a56650cb60"
227
+ "sha256": "6ce8e75025d31ed81c39150df2766231fc871ef18c234de986dc910bab79d639"
220
228
  },
221
229
  {
222
230
  "path": "workbench/candidate-bootstrap.mjs",
@@ -302,9 +310,17 @@
302
310
  "path": "platform/windows/hellolynk-native-human-confirmation-broker.ps1",
303
311
  "sha256": "757a731a3e8761d5d473db19ecfc330783f2826663a0057c4d6896e679d7a388"
304
312
  },
313
+ {
314
+ "path": "bootstrap/core/hellolynk-darwin-host.mjs",
315
+ "sha256": "0772d7a23280b18d5bc59383a6a14fd2ecfd9de1ec53b351be0250770e3e2e70"
316
+ },
317
+ {
318
+ "path": "bootstrap/core/hellolynk-darwin-vault.mjs",
319
+ "sha256": "b6094d856af016e674e4a0183a4e474397bbe92e66638743fa62fe085b554d30"
320
+ },
305
321
  {
306
322
  "path": "bootstrap/core/hellolynk-client-account.mjs",
307
- "sha256": "ab7ec5dda92e510e7deb14f900fd2df4c6c3373fbeb4af4fb2b6c1a72aa41a99"
323
+ "sha256": "6e03a05127d4dc86be6391567b51a956d1a987acf4e23f5699ce0e00c3187b7e"
308
324
  },
309
325
  {
310
326
  "path": "bootstrap/core/hellolynk-client-core.mjs",
@@ -312,7 +328,7 @@
312
328
  },
313
329
  {
314
330
  "path": "bootstrap/core/hellolynk-client-profile.mjs",
315
- "sha256": "4b45a78b4d6c4794a1f494bd95777579c32c56a68321a1d3b484048942ae4742"
331
+ "sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
316
332
  },
317
333
  {
318
334
  "path": "bootstrap/core/hellolynk-host-auth.mjs",
@@ -320,7 +336,7 @@
320
336
  },
321
337
  {
322
338
  "path": "bootstrap/core/hellolynk-host-protected-store.mjs",
323
- "sha256": "89b6e6c4ce9c6ba2984f8c301de4251da6004bdd402cc44d578a4505c21b75b1"
339
+ "sha256": "10f81f4f6a5dd2fe9a742e847b2eb47dbf20f802f4414f46a05ccbc3505148e2"
324
340
  },
325
341
  {
326
342
  "path": "bootstrap/core/hellolynk-local-process-lock.mjs",
@@ -328,11 +344,11 @@
328
344
  },
329
345
  {
330
346
  "path": "bootstrap/core/hellolynk-neutral-host-installation-store.mjs",
331
- "sha256": "b5f6288732af6f9b9c0c750f6002eae657dde9513fc97674f6e59200f3cc9dc6"
347
+ "sha256": "42ae1e7deb876a8308b7bfed6a7b891d1834117b62504fc5befe3f91a0b4c740"
332
348
  },
333
349
  {
334
350
  "path": "bootstrap/core/hellolynk-platform-credential-store.mjs",
335
- "sha256": "f27f5f377cc4d6d2b5664212cf8fcc4c98d910064c1677ff9c1f776df1a59dba"
351
+ "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
336
352
  },
337
353
  {
338
354
  "path": "bootstrap/core/hellolynk-runtime-safety.cjs",
@@ -342,9 +358,25 @@
342
358
  "path": "bootstrap/platform/windows/hellolynk-host-protected-store.ps1",
343
359
  "sha256": "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"
344
360
  },
361
+ {
362
+ "path": "platform/darwin/hellolynk-keychain-helper.swift",
363
+ "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
364
+ },
365
+ {
366
+ "path": "platform/darwin/hellolynk-keychain-helper.bin",
367
+ "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
368
+ },
369
+ {
370
+ "path": "bootstrap/platform/darwin/hellolynk-keychain-helper.swift",
371
+ "sha256": "5d933589a1d739217bc164f73ab9afadc9e27cc512e1c00c0be4b38975fb6d5b"
372
+ },
373
+ {
374
+ "path": "bootstrap/platform/darwin/hellolynk-keychain-helper.bin",
375
+ "sha256": "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"
376
+ },
345
377
  {
346
378
  "path": "release/server/codex-neutral-production.mjs",
347
- "sha256": "ffb8e689df1b600bcca85159af8d59f0fad819827f48ba9a218a63dfd7a3daa2"
379
+ "sha256": "2e09f95b8c15ac307d3be0e5b29fecd00e5eae95fd816e885c51484990e703cb"
348
380
  },
349
381
  {
350
382
  "path": "release/server/neutral-host-adapter.mjs",
@@ -352,7 +384,7 @@
352
384
  },
353
385
  {
354
386
  "path": "release/server/workbench-runtime.mjs",
355
- "sha256": "1136c7ea50cc73065fe04094c5b5c19cb676c40a0a189979015766a9c4e908d8"
387
+ "sha256": "99e069a72a0be3e73a993b2092d55470fcb481eb381841a156b2756415a0a0a9"
356
388
  }
357
389
  ]
358
390
  }
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-neutral-host-release-pin-v1",
3
- "manifestSha256": "5b035cb2d82e7543b801ca9d3b35a36e3f54a7b9aff9afcdf9f479926c537f56"
3
+ "manifestSha256": "2cc4af5084d836c44b30aa3f747ab0562980991edf78aba149aaded974d32b3c"
4
4
  }