@hellolynk-ai/codex-plugin 0.2.102-macos.1 → 0.2.103-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.
@@ -16,7 +16,8 @@ const PACK_PIN = "hellolynk-client-pack-pin.json";
16
16
 
17
17
  // A person at a terminal reads sentences in the system language; the updater, tests and other
18
18
  // programs read one JSON line.
19
- const jsonOutput = process.argv.includes("--json") || process.stdout.isTTY !== true;
19
+ const explicitJson = process.argv.includes("--json");
20
+ const jsonOutput = explicitJson || process.stdout.isTTY !== true;
20
21
  const CHINESE = /^zh\b/i.test(Intl.DateTimeFormat().resolvedOptions().locale);
21
22
  const CLIENT_NAMES = Object.freeze({ codex: "Codex", openclaw: "OpenClaw", "trae-work": "TRAE Work",
22
23
  workbuddy: "WorkBuddy", cursor: "Cursor", dsh: "DSH" });
@@ -258,6 +259,10 @@ try {
258
259
  await verifyExactBinding({ ...installed, locations, activation });
259
260
  await verifyClientConfiguration({ ...installed, locations });
260
261
  }
262
+ // A running unsigned updater asks the stable package for implicit JSON. A
263
+ // staged package may still prove its own first install after lost stdout.
264
+ const legacyUpdateProof = installed && !existingDoctor && !explicitJson && jsonOutput
265
+ && process.env.HELLOLYNK_NEUTRAL_STORE_ROOT && samePath(PACKAGE_ROOT, installed.packageRoot);
261
266
  print({
262
267
  ok: true,
263
268
  client: metadata.client,
@@ -268,7 +273,7 @@ try {
268
273
  activated: activation.ready,
269
274
  activationState: activation.state,
270
275
  stableReleaseVerified: installed != null,
271
- exactBindingVerified: installed != null,
276
+ ...(legacyUpdateProof ? {} : { exactBindingVerified: installed != null }),
272
277
  clientConfigurationVerified: installed != null,
273
278
  installsNode: false,
274
279
  installsGit: false,
@@ -2,7 +2,7 @@
2
2
  "contract": "hellolynk-client-npm-pack-manifest-v1",
3
3
  "client": "codex",
4
4
  "packageName": "@hellolynk-ai/codex-plugin",
5
- "version": "0.2.102-macos.1",
5
+ "version": "0.2.103-macos.1",
6
6
  "workbench": {
7
7
  "path": "plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json",
8
8
  "contract": "hellolynk-shared-workbench-build-v1",
@@ -17,8 +17,8 @@
17
17
  },
18
18
  {
19
19
  "path": "bin/hellolynk-client-setup.mjs",
20
- "bytes": 145198,
21
- "sha256": "d7ab5068d7c88f4cdcd056db0074431ea33811a538c2ede4a420d1ee15f4452c"
20
+ "bytes": 145615,
21
+ "sha256": "044321ea6c5757a985b92a826ffc1fb3ffa4fb8355b732abecc64210d4ac3399"
22
22
  },
23
23
  {
24
24
  "path": "bin/hellolynk-mcp-config-shape.mjs",
@@ -33,7 +33,7 @@
33
33
  {
34
34
  "path": "package.json",
35
35
  "bytes": 1776,
36
- "sha256": "8b20a1dc094f4e846029478dfb1d4f11b827c0e6edd1d5646eea02459b6480c0"
36
+ "sha256": "4115862c2d4169f3876fe2c35a3a96c8c1fb50b6a3a73af80456e26702ad3105"
37
37
  },
38
38
  {
39
39
  "path": "plugins/hellolynk-codex/.app.json",
@@ -43,7 +43,7 @@
43
43
  {
44
44
  "path": "plugins/hellolynk-codex/.codex-plugin/plugin.json",
45
45
  "bytes": 1669,
46
- "sha256": "38d2a5204b66ec57368494e69ed71de655f798f9d37ab2af966b59c3ab07d0f2"
46
+ "sha256": "1de6bb823a304b733c0c75418389b1e32819191adea03c575f5c602f2e00631c"
47
47
  },
48
48
  {
49
49
  "path": "plugins/hellolynk-codex/.mcp.json",
@@ -58,7 +58,7 @@
58
58
  {
59
59
  "path": "plugins/hellolynk-codex/package.json",
60
60
  "bytes": 1030,
61
- "sha256": "befbca064334e8ff7f9d192f095ac1bd786a3882385a450fc2c2d2a5c9b2b7dd"
61
+ "sha256": "f786baab2e9f35836813c6b77e1cbc0fb8c0a3c0a2055b2e6ea777f2428cd232"
62
62
  },
63
63
  {
64
64
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs",
@@ -77,8 +77,8 @@
77
77
  },
78
78
  {
79
79
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-darwin-host.mjs",
80
- "bytes": 14704,
81
- "sha256": "0772d7a23280b18d5bc59383a6a14fd2ecfd9de1ec53b351be0250770e3e2e70"
80
+ "bytes": 15242,
81
+ "sha256": "ded97c3bc3dd65ca75305f5199a1340dffb987a5f283454d60586df8b6b55c09"
82
82
  },
83
83
  {
84
84
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-darwin-vault.mjs",
@@ -157,18 +157,18 @@
157
157
  },
158
158
  {
159
159
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs",
160
- "bytes": 1935,
161
- "sha256": "1e513a4a98437dc60b49f9f39fc3958808c3acfaeb6c5bb638efea2e61d4b1ee"
160
+ "bytes": 1939,
161
+ "sha256": "d9548eacda8632976e42aafc65f0cef2ec9c5233d2d53b31b02aec0a1702b5eb"
162
162
  },
163
163
  {
164
164
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs",
165
- "bytes": 40918,
166
- "sha256": "96aa12ba21619a0cd78ef61c63be6f43fdb61f892201ce923ee35e0912ae2e52"
165
+ "bytes": 41618,
166
+ "sha256": "aa233e2cdbdfadb7da6aa489591bc12c4121101b94696f93f6e6cb5d3e7c7870"
167
167
  },
168
168
  {
169
169
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs",
170
- "bytes": 28747,
171
- "sha256": "93bf5f2ecc72731d70367306989fa18c82fa7bf9e4b5f785a1376f0d55239428"
170
+ "bytes": 30894,
171
+ "sha256": "e57f7cc5e98519a014cef7cef68a97bb7e2beb33eb381c38d1751a3967904aaa"
172
172
  },
173
173
  {
174
174
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs",
@@ -177,8 +177,8 @@
177
177
  },
178
178
  {
179
179
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-darwin-host.mjs",
180
- "bytes": 14704,
181
- "sha256": "0772d7a23280b18d5bc59383a6a14fd2ecfd9de1ec53b351be0250770e3e2e70"
180
+ "bytes": 15242,
181
+ "sha256": "ded97c3bc3dd65ca75305f5199a1340dffb987a5f283454d60586df8b6b55c09"
182
182
  },
183
183
  {
184
184
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-darwin-vault.mjs",
@@ -348,7 +348,7 @@
348
348
  {
349
349
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs",
350
350
  "bytes": 225161,
351
- "sha256": "e744bacf65f7b86cbcf7d654189c0e07e09401083e2b6f9053b17c1c3bacd1b4"
351
+ "sha256": "7b237a1400c604da5decbb29ab3ce556fd1b744d0cfd7d28b5560b217da120b1"
352
352
  },
353
353
  {
354
354
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs",
@@ -372,8 +372,8 @@
372
372
  },
373
373
  {
374
374
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs",
375
- "bytes": 24519,
376
- "sha256": "7cfd7e20c37ac1479f2472cc5fba8e830dcd1b83b91a86c340749cf145f0d661"
375
+ "bytes": 25616,
376
+ "sha256": "0ee150af5a9bcc1f636e3ff980ee90ce7d073ab5ed4a991920cd3472bc65529c"
377
377
  },
378
378
  {
379
379
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs",
@@ -408,12 +408,12 @@
408
408
  {
409
409
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json",
410
410
  "bytes": 14698,
411
- "sha256": "2cc4af5084d836c44b30aa3f747ab0562980991edf78aba149aaded974d32b3c"
411
+ "sha256": "f054f0e73accc788fb24c437eea03a6e4cd5872f39dc4d65cbc7ff0e17e6bcae"
412
412
  },
413
413
  {
414
414
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-pin.json",
415
415
  "bytes": 146,
416
- "sha256": "ea352aec41635ec6e187bc3cfb76a0c35d0100a5628293b40c25ef049aa50650"
416
+ "sha256": "530ed943cd227874b5096c7bb3657ec0f7a624c2b049680003f6237574d9d73a"
417
417
  },
418
418
  {
419
419
  "path": "plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.bin",
@@ -432,8 +432,8 @@
432
432
  },
433
433
  {
434
434
  "path": "plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1",
435
- "bytes": 38434,
436
- "sha256": "d00dae4af08205fffa52c4956d1c87d5c3423e8f20982ccfc1b76e3424fe442e"
435
+ "bytes": 40188,
436
+ "sha256": "8f288c91a1f6ecaf2651baab9bdfc55014c11ea9e165f203ca7a69f4d47d7917"
437
437
  },
438
438
  {
439
439
  "path": "plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1",
@@ -593,7 +593,7 @@
593
593
  {
594
594
  "path": "plugins/hellolynk-codex/server/codex-neutral-production.mjs",
595
595
  "bytes": 27551,
596
- "sha256": "2e09f95b8c15ac307d3be0e5b29fecd00e5eae95fd816e885c51484990e703cb"
596
+ "sha256": "2c2f209339385cc14f27fe985f4deb75f4bd48e689035311847cc3b08e36ac52"
597
597
  },
598
598
  {
599
599
  "path": "plugins/hellolynk-codex/server/mcp-proxy.mjs",
@@ -602,13 +602,13 @@
602
602
  },
603
603
  {
604
604
  "path": "plugins/hellolynk-codex/server/mcp-server.mjs",
605
- "bytes": 59270,
606
- "sha256": "2333f154c55b629a4a789b640be7099bbb8a84e90a17f84b77830210b527387a"
605
+ "bytes": 60463,
606
+ "sha256": "d20e041d6ce63c52bbdf7faac7e7c2ed288444fca014f861f1a8eb15d8067497"
607
607
  },
608
608
  {
609
609
  "path": "plugins/hellolynk-codex/server/native-conversation-directory.mjs",
610
- "bytes": 2319,
611
- "sha256": "382ca445887ef518d9fd58c7fdf18c55e8865623102396822d73df5f987b1b3d"
610
+ "bytes": 2352,
611
+ "sha256": "d6e3c1bd6e338bb13518760e0d2a350d3c9a28ef0a493bcbd53b44c610bfd56d"
612
612
  },
613
613
  {
614
614
  "path": "plugins/hellolynk-codex/server/native-reply-routes.mjs",
@@ -632,8 +632,8 @@
632
632
  },
633
633
  {
634
634
  "path": "plugins/hellolynk-codex/server/workbench-runtime.mjs",
635
- "bytes": 8860,
636
- "sha256": "99e069a72a0be3e73a993b2092d55470fcb481eb381841a156b2756415a0a0a9"
635
+ "bytes": 8902,
636
+ "sha256": "1136c7ea50cc73065fe04094c5b5c19cb676c40a0a189979015766a9c4e908d8"
637
637
  },
638
638
  {
639
639
  "path": "plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-client-npm-pack-pin-v1",
3
- "manifestSha256": "20c169b5149efb01cd3ac7d550b61f2959a491fe9e5c5d44f6106643aefdf65d"
3
+ "manifestSha256": "23e0c7dae786144392b74ba3ee2c6b4b5221d65410ddb563c207ab2022902f87"
4
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellolynk-ai/codex-plugin",
3
- "version": "0.2.102-macos.1",
3
+ "version": "0.2.103-macos.1",
4
4
  "description": "HelloLynk Messaging for Codex: open conversations and let your AI message other people's AI assistants.",
5
5
  "homepage": "https://hellolynk.com",
6
6
  "repository": {
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hellolynk-codex",
3
- "version": "0.2.102",
3
+ "version": "0.2.103",
4
4
  "description": "HelloLynk 通讯、本地未读提醒与安全文字收发的 Codex 插件。",
5
5
  "author": {
6
6
  "name": "HelloLynk local project"
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellolynk-ai/codex-plugin",
3
- "version": "0.2.102",
3
+ "version": "0.2.103",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Project-local HelloLynk Messaging, unread notification, and secure messaging plugin for Codex.",
@@ -12,13 +12,13 @@ function unique(records, missing, ambiguous) {
12
12
  if (records.length !== 1) fail(records.length ? ambiguous : missing);
13
13
  return records[0];
14
14
  }
15
- function checkBindingPaths(b) {
15
+ function checkBindingPaths(b, code) {
16
16
  try {
17
17
  checkDarwinPath(b.runtimeRoot, { directory: true });
18
18
  checkDarwinPath(b.releaseRoot, { directory: true });
19
19
  if (checkDarwinPath(b.profileManifestPath).size > 65536 ||
20
- checkDarwinPath(join(b.releaseRoot, b.runnerRelativePath)).size > 1048576) fail("neutral_codex_launch_manifest_invalid");
21
- } catch { fail("neutral_codex_launch_manifest_invalid"); }
20
+ checkDarwinPath(join(b.releaseRoot, b.runnerRelativePath)).size > 1048576) fail(code);
21
+ } catch { fail(code); }
22
22
  }
23
23
  function capMatches(cap, b) {
24
24
  return cap.state === "active" && fieldsMatch(cap, b,
@@ -38,51 +38,60 @@ function revokeCapability(s, r) {
38
38
  }
39
39
  return { capabilityId: r.capabilityId, revokedAt: r.revokedAt, state: "revoked" };
40
40
  }
41
- function codexBinding(s, command, input, oldId) {
42
- const b = s.codex;
43
- if (command === "ResolveCodexNeutralBinding" || command === "VerifyCodexNeutralBinding") {
44
- if (!b) fail("neutral_codex_binding_configuration_required");
45
- checkBindingPaths(b);
46
- const active = s.caps.filter(c => c.adapterId === "codex" && c.state === "active");
41
+ function neutralBinding(s, adapter, action, input, oldId) {
42
+ const codex = adapter === "codex";
43
+ const key = codex ? "codex" : "claudeCode";
44
+ const error = suffix => `neutral_${adapter.replace("-", "_")}_${suffix}`;
45
+ const check = binding => checkBindingPaths(binding, error("launch_manifest_invalid"));
46
+ const b = s[key];
47
+ if (action === "Resolve" || action === "Verify") {
48
+ if (!b) fail(error("binding_configuration_required"));
49
+ // Claude retains its identity across uninstall; Verify can inspect a
50
+ // retained binding whose old release is gone. Resolve always needs it.
51
+ if (codex || action === "Resolve") check(b);
52
+ const active = s.caps.filter(c => c.adapterId === adapter && c.state === "active");
47
53
  const cap = unique(active.filter(c => capMatches(c, b)),
48
- active.length ? "neutral_codex_capability_binding_mismatch" : "neutral_codex_capability_missing", "neutral_codex_capability_ambiguous");
49
- if (command.startsWith("Resolve")) return { binding: b, record: cap };
54
+ error(active.length ? "capability_binding_mismatch" : "capability_missing"), error("capability_ambiguous"));
55
+ if (action === "Resolve") return { binding: b, record: cap };
50
56
  const {secret, ...record} = cap;
51
57
  return { binding: b, record };
52
58
  }
53
- if (command === "SaveCodexNeutralBinding" || command === "RemoveCodexNeutralBinding") {
54
- checkBindingPaths(input);
55
- if (command.startsWith("Remove")) {
56
- if (!equal(b, input)) fail("neutral_codex_binding_invalid");
57
- s.codex = null;
59
+ if (action === "Save" || action === "Remove") {
60
+ if (input.adapterId !== adapter) fail(error("binding_invalid"));
61
+ if (action === "Remove") {
62
+ if (codex) check(input);
63
+ if (!equal(b, input)) fail(error("binding_invalid"));
64
+ if (codex) s.codex = null;
65
+ else delete s.claudeCode;
58
66
  return { removed: true };
59
67
  }
60
- if (b && !equal(b, input)) fail("neutral_codex_binding_invalid");
61
- s.codex = input;
68
+ check(input);
69
+ if (b && !equal(b, input)) fail(error("binding_invalid"));
70
+ s[key] = input;
62
71
  return { binding: input };
63
72
  }
64
73
  const { previousBinding: previous, replacementBinding: next, replacementRecord } = input;
65
- checkBindingPaths(previous);
66
- checkBindingPaths(next);
67
- if (!fieldsMatch(previous, next, ["contract","region","principalId","delegateId","hostInstallationId","profileId","adapterId","version","runtimeRoot"])) {
68
- fail("neutral_codex_capability_binding_mismatch");
74
+ if (codex) check(previous);
75
+ check(next);
76
+ if (next.adapterId !== adapter || !fieldsMatch(previous, next, ["contract","region","principalId","delegateId","hostInstallationId","profileId","adapterId","version","runtimeRoot"])) {
77
+ fail(error("capability_binding_mismatch"));
69
78
  }
70
- if (!equal(b, previous)) fail("neutral_codex_binding_invalid");
79
+ if (!equal(b, previous)) fail(error("binding_invalid"));
71
80
  const active = s.caps.filter(c => capMatches(c, next));
72
- if (command === "ReplaceCodexNeutralBinding") {
73
- if (!equal(previous.scopes, next.scopes)) fail("neutral_codex_capability_binding_mismatch");
74
- if (active.length !== 1) fail("neutral_codex_capability_ambiguous");
75
- s.codex = next;
81
+ if (codex && action === "Replace") {
82
+ if (!equal(previous.scopes, next.scopes)) fail(error("capability_binding_mismatch"));
83
+ if (active.length !== 1) fail(error("capability_ambiguous"));
84
+ s[key] = next;
76
85
  return { binding: next };
77
86
  }
78
- if (command !== "MigrateCodexNeutralBinding") fail("host_protected_store_command_invalid");
87
+ if (action !== "Migrate") fail("host_protected_store_command_invalid");
79
88
  const old = s.caps.find(c => c.capabilityId === oldId);
80
- if (!old || !capMatches(old, previous) || !capMatches(replacementRecord, next)) fail("neutral_codex_capability_binding_mismatch");
89
+ if (!old || !capMatches(old, previous) || !capMatches(replacementRecord, next)) fail(error("capability_binding_mismatch"));
81
90
  const other = active.filter(c => c.capabilityId !== oldId);
82
- if (other.length > 1) fail("neutral_codex_capability_ambiguous");
91
+ if (other.length > 1) fail(error("capability_ambiguous"));
83
92
  if (!other.length) addCapability(s, replacementRecord);
84
93
  revokeCapability(s, old);
85
- s.codex = next;
94
+ s[key] = next;
86
95
  return { binding: next, record: other[0] || replacementRecord, replacedCapabilityId: oldId };
87
96
  }
88
97
 
@@ -247,9 +256,13 @@ export function invokeDarwinHost(q) {
247
256
  case "DisconnectLocalClient":
248
257
  for (const cap of s.caps.filter(c => c.adapterId === input.client && c.state === "active")) revokeCapability(s,cap);
249
258
  if (input.client === "codex") s.codex = null;
259
+ if (input.client === "claude-code") delete s.claudeCode;
250
260
  return {disconnected:true,client:input.client};
251
261
  }
252
- if (q.command.endsWith("CodexNeutralBinding")) return codexBinding(s,q.command,input,q.capabilityId);
262
+ const bindingCommand = /^(Resolve|Verify|Save|Remove|Replace|Migrate)(Codex|ClaudeCode)NeutralBinding$/.exec(q.command);
263
+ if (bindingCommand) {
264
+ return neutralBinding(s, bindingCommand[2] === "Codex" ? "codex" : "claude-code", bindingCommand[1], input, q.capabilityId);
265
+ }
253
266
  if (q.command.includes("Transport")) return transport(s,q,input);
254
267
  fail("host_protected_store_command_unsupported");
255
268
  });
@@ -2,7 +2,7 @@ import fs from "node:fs/promises";
2
2
  import path from "node:path";
3
3
  import { clientAccountBaseRoot } from "./hellolynk-client-account.mjs";
4
4
  import { startClientUpdateRuntime } from "./hellolynk-client-update-runtime.mjs";
5
- import { clientUpdateCatalogId, MAC_CODEX_VERSION } from "./hellolynk-client-update.mjs";
5
+ import { clientUpdateCatalogId, MAC_CLIENT_VERSION } from "./hellolynk-client-update.mjs";
6
6
 
7
7
  const running = new Map();
8
8
 
@@ -11,7 +11,7 @@ const running = new Map();
11
11
  export function startInstalledClientUpdate(options = {}) {
12
12
  const { client, packageRoot, storeRoot } = options;
13
13
  const catalogId = clientUpdateCatalogId(client, process.platform);
14
- const mac = catalogId === "codex-macos";
14
+ const mac = process.platform === "darwin";
15
15
  if (!catalogId || (mac && process.arch !== "arm64") || !path.isAbsolute(String(packageRoot || ""))) return Promise.resolve(null);
16
16
  let baseRoot;
17
17
  try { baseRoot = clientAccountBaseRoot(storeRoot); } catch { return Promise.resolve(null); }
@@ -24,7 +24,7 @@ export function startInstalledClientUpdate(options = {}) {
24
24
  const metadata = await fs.lstat(packageFile);
25
25
  if (!metadata.isFile() || metadata.isSymbolicLink() || metadata.nlink !== 1 || metadata.size > 64 * 1024) return null;
26
26
  const manifest = JSON.parse(await fs.readFile(packageFile, "utf8"));
27
- const version = mac ? MAC_CODEX_VERSION : /^\d+\.\d+\.\d+$/u;
27
+ const version = mac ? MAC_CLIENT_VERSION : /^\d+\.\d+\.\d+$/u;
28
28
  if (manifest.hellolynkClient?.client !== client || !version.test(manifest.version || "")
29
29
  || (mac && (manifest.os?.[0] !== "darwin" || manifest.cpu?.[0] !== "arm64"))) return null;
30
30
  return startClientUpdateRuntime({
@@ -5,7 +5,7 @@ import path from "node:path";
5
5
  import { spawn } from "node:child_process";
6
6
  import { fileURLToPath } from "node:url";
7
7
  import { createClientUpdateManager, extractClientUpdateArchive, compareClientUpdateVersions, clientUpdateCatalogId,
8
- CLIENT_UPDATE_CLIENTS, MAC_CODEX_VERSION } from "./hellolynk-client-update.mjs";
8
+ CLIENT_UPDATE_CLIENTS, CLIENT_UPDATE_RELEASES, MAC_CLIENT_VERSION } from "./hellolynk-client-update.mjs";
9
9
  import { withNeutralHostStoreLock } from "./hellolynk-neutral-host-installation-store.mjs";
10
10
  import { readClientAccount } from "./hellolynk-client-account.mjs";
11
11
  import runtimeSafety from "./hellolynk-runtime-safety.cjs";
@@ -95,7 +95,7 @@ export async function runClientUpdateWorker(options = {}) {
95
95
  function createRuntime(config, options) {
96
96
  const { root, client, currentVersion, installContext } = config;
97
97
  const directory = path.join(root, "client-updates", client);
98
- const statePath = path.join(directory, "runtime.json"), leasePath = path.join(directory, "leases.json"), installedPath = path.join(directory, "installed.json");
98
+ const statePath = path.join(directory, "runtime-v2.json"), leasePath = path.join(directory, "leases.json"), installedPath = path.join(directory, "installed.json");
99
99
  const manager = createClientUpdateManager({ ...options, root, client, currentVersion, platform: config.platform });
100
100
  const alive = options.isProcessAlive || processAlive;
101
101
  async function gate(operation) {
@@ -201,7 +201,8 @@ function createRuntime(config, options) {
201
201
  const attempted = runtime.sha256 === downloaded.sha256 && !(runtime.stage === "applying" || retryable);
202
202
  if (downloaded.stage !== "downloaded" || downloaded.error || attempted) return;
203
203
  // A damaged worker record means no known worker.
204
- const worker = await readJson(path.join(directory, "worker.json"), null).catch((error) => { if (!damaged(error)) throw error; return null; });
204
+ const workerPath = path.join(directory, "worker-v2.json");
205
+ const worker = await readJson(workerPath, null).catch((error) => { if (!damaged(error)) throw error; return null; });
205
206
  if (worker?.client === client && Number.isSafeInteger(worker.pid) && alive(worker.pid)) return;
206
207
  const launch = options.spawnWorker || launchWorker;
207
208
  let pid;
@@ -211,13 +212,13 @@ function createRuntime(config, options) {
211
212
  } catch (error) {
212
213
  // Retain only this rejected launch attempt, not raw errors or proof of
213
214
  // worker absence. Keep an earlier PID's conservative liveness check.
214
- await atomicJson(path.join(directory, "worker.json"), { client,
215
+ await atomicJson(workerPath, { client,
215
216
  ...(worker?.client === client && Number.isSafeInteger(worker.pid) ? { pid: worker.pid } : {}),
216
217
  sha256: downloaded.sha256, error: "client_update_worker_launch_failed", failedAt: Date.now(),
217
218
  }).catch(() => {});
218
219
  throw error;
219
220
  }
220
- await atomicJson(path.join(directory, "worker.json"), { client, pid });
221
+ await atomicJson(workerPath, { client, pid });
221
222
  });
222
223
  }
223
224
  async function applyPending() {
@@ -237,7 +238,7 @@ function createRuntime(config, options) {
237
238
  if (sha(await safeFile(path.join(proof.packageRoot, "hellolynk-client-pack-manifest.json")))
238
239
  !== sha(await safeFile(path.join(installContext.packageRoot, "hellolynk-client-pack-manifest.json")))) throw failure("adoption_invalid");
239
240
  const doctor = await (options.runInstaller || runInstaller)({ executable: installContext.nodePath || process.execPath,
240
- args: [path.join(installContext.packageRoot, "bin", "hellolynk-client-setup.mjs"), "doctor"], cwd: installContext.packageRoot, root, timeoutMs: 180_000 });
241
+ args: [path.join(installContext.packageRoot, "bin", "hellolynk-client-setup.mjs"), "doctor", "--json"], cwd: installContext.packageRoot, root, timeoutMs: 180_000 });
241
242
  if (!doctorProves(doctor, client, currentVersion)) throw failure("adoption_invalid");
242
243
  const adoptedRoot = path.join(path.dirname(root), "releases", `${client}-${sha(await safeFile(path.join(proof.packageRoot, "hellolynk-client-pack-manifest.json")))}`);
243
244
  if (!options.runInstaller) await verifyPackage(adoptedRoot, client, currentVersion);
@@ -275,7 +276,7 @@ function createRuntime(config, options) {
275
276
  try {
276
277
  await verifyPackage(packageRoot, client, version);
277
278
  const inspected = await run({ executable: installContext.nodePath || process.execPath,
278
- args: [path.join(packageRoot, "bin", "hellolynk-client-setup.mjs"), "doctor"], cwd: packageRoot, root, timeoutMs: 180_000 });
279
+ args: [path.join(packageRoot, "bin", "hellolynk-client-setup.mjs"), "doctor", "--json"], cwd: packageRoot, root, timeoutMs: 180_000 });
279
280
  return doctorProves(inspected, client, version);
280
281
  } catch { return false; }
281
282
  };
@@ -398,7 +399,7 @@ async function prepareCandidate(config, record) {
398
399
  const directory = path.join(config.root, "client-updates", config.client);
399
400
  if (!HASH.test(record.sha256) || typeof record.version !== "string" || !/^[0-9A-Za-z.+-]{1,100}$/.test(record.version)
400
401
  || typeof record.filename !== "string" || !/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}\.zip$/.test(record.filename)) throw failure("candidate_invalid");
401
- const artifactPath = path.join(directory, "releases", `${record.version}-${record.sha256}`, record.filename);
402
+ const artifactPath = path.join(directory, CLIENT_UPDATE_RELEASES, `${record.version}-${record.sha256}`, record.filename);
402
403
  if (artifactPath !== record.artifactPath) throw failure("candidate_path_invalid");
403
404
  const archive = await safeFile(artifactPath, 64 * 1024 * 1024);
404
405
  if (archive.length !== record.bytes || sha(archive) !== record.sha256) throw failure("artifact_integrity_invalid");
@@ -407,51 +408,63 @@ async function prepareCandidate(config, record) {
407
408
  const extractedRoot = path.join(temporaryRoot, "extracted");
408
409
  try {
409
410
  // Re-extract the hash-verified archive; never execute a mutable earlier
410
- // extractedRoot or an executable path supplied by state.json.
411
+ // extractedRoot or an executable path supplied by update state.
411
412
  const sealed = path.join(temporaryRoot, record.filename);
412
413
  await fs.writeFile(sealed, archive, { flag: "wx", mode: 0o600 });
413
414
  await extractClientUpdateArchive({ archivePath: sealed, destination: extractedRoot, platform: config.platform });
414
- let packageRoot;
415
- if (config.client === "dsh") {
416
- const descriptor = await readJson(path.join(extractedRoot, "trusted-release.json"));
417
- const manifestBytes = await safeFile(path.join(extractedRoot, "dist", "package", "hellolynk-dsh-nav-build.json"));
418
- if (descriptor?.contract !== "hellolynk-dsh-nav-release-v1" || descriptor.manifestSha256 !== sha(manifestBytes)) throw failure("package_pin_invalid");
419
- const manifest = JSON.parse(manifestBytes.toString("utf8"));
420
- if (manifest.contract !== "hellolynk-dsh-nav-build-v3") throw failure("package_pin_invalid");
421
- await verifyRecords(path.join(extractedRoot, "dist", "package"), manifest.packageFiles, ["hellolynk-dsh-nav-build.json"]);
422
- packageRoot = path.join(extractedRoot, "dist", "package", "runtime", "connect");
423
- await safeFile(path.join(extractedRoot, "scripts", "install.mjs"));
424
- } else {
425
- const id = clientUpdateCatalogId(config.client, config.platform) || config.client;
426
- const candidates = [extractedRoot, path.join(extractedRoot, "package"), path.join(extractedRoot, `hellolynk-${id}-${record.version}`, "package")];
427
- const matches = [];
428
- for (const candidate of candidates) if (await exists(path.join(candidate, "package.json"))) matches.push(candidate);
429
- if (matches.length !== 1) throw failure("package_identity_invalid");
430
- packageRoot = matches[0];
431
- }
432
- await verifyPackage(packageRoot, config.client, record.version);
415
+ const { packageRoot } = await verifyExtractedClientUpdatePackage(extractedRoot,
416
+ { client: config.client, platform: config.platform, version: record.version });
433
417
  return { temporaryRoot, extractedRoot, artifactPath: sealed, packageRoot, setupPath: path.join(packageRoot, "bin", "hellolynk-client-setup.mjs") };
434
418
  } catch (error) { await fs.rm(temporaryRoot, { recursive: true, force: true }); throw error; }
435
419
  }
436
420
 
421
+ export async function verifyExtractedClientUpdatePackage(extractedRoot, { client, platform, version }) {
422
+ if (!CLIENT_UPDATE_CLIENTS.includes(client) || typeof version !== "string" || !VERSION.test(version)) throw failure("package_identity_invalid");
423
+ let packageRoot;
424
+ if (client === "dsh") {
425
+ const descriptor = await readJson(path.join(extractedRoot, "trusted-release.json"));
426
+ const manifestBytes = await safeFile(path.join(extractedRoot, "dist", "package", "hellolynk-dsh-nav-build.json"));
427
+ if (descriptor?.contract !== "hellolynk-dsh-nav-release-v1" || descriptor.manifestSha256 !== sha(manifestBytes)) throw failure("package_pin_invalid");
428
+ const manifest = JSON.parse(manifestBytes.toString("utf8"));
429
+ if (manifest.contract !== "hellolynk-dsh-nav-build-v3") throw failure("package_pin_invalid");
430
+ await verifyRecords(path.join(extractedRoot, "dist", "package"), manifest.packageFiles, ["hellolynk-dsh-nav-build.json"]);
431
+ packageRoot = path.join(extractedRoot, "dist", "package", "runtime", "connect");
432
+ await safeFile(path.join(extractedRoot, "scripts", "install.mjs"));
433
+ } else {
434
+ const id = clientUpdateCatalogId(client, platform) || client;
435
+ const candidates = [extractedRoot, path.join(extractedRoot, "package"), path.join(extractedRoot, `hellolynk-${id}-${version}`, "package")];
436
+ const matches = [];
437
+ for (const candidate of candidates) if (await exists(path.join(candidate, "package.json"))) matches.push(candidate);
438
+ if (matches.length !== 1) throw failure("package_identity_invalid");
439
+ packageRoot = matches[0];
440
+ }
441
+ const checked = await verifyPackage(packageRoot, client, version);
442
+ return { packageRoot, ...checked };
443
+ }
444
+
437
445
  async function verifyPackage(root, client, version) {
438
446
  const pkg = await readJson(path.join(root, "package.json"));
439
447
  const manifestBytes = await safeFile(path.join(root, "hellolynk-client-pack-manifest.json"));
440
448
  const pin = await readJson(path.join(root, "hellolynk-client-pack-pin.json"));
441
449
  const manifest = JSON.parse(manifestBytes.toString("utf8"));
442
450
  if (pkg.version !== version || pkg.hellolynkClient?.client !== client || pkg.name !== NAMES[client]
443
- || (MAC_CODEX_VERSION.test(version) && (client !== "codex" || pkg.os?.[0] !== "darwin" || pkg.cpu?.[0] !== "arm64"))
451
+ || (MAC_CLIENT_VERSION.test(version) && (!clientUpdateCatalogId(client, "darwin") || pkg.os?.[0] !== "darwin" || pkg.cpu?.[0] !== "arm64"))
444
452
  || manifest.contract !== "hellolynk-client-npm-pack-manifest-v1" || manifest.client !== client
445
453
  || manifest.packageName !== pkg.name || manifest.version !== version
446
454
  || pin.contract !== "hellolynk-client-npm-pack-pin-v1" || pin.manifestSha256 !== sha(manifestBytes)) throw failure("package_identity_invalid");
447
455
  await verifyRecords(root, manifest.files, ["hellolynk-client-pack-manifest.json", "hellolynk-client-pack-pin.json"]);
448
456
  if (!manifest.files.some((file) => file.path === "bin/hellolynk-client-setup.mjs")) throw failure("package_pin_invalid");
449
- if (process.platform !== "win32" && MAC_CODEX_VERSION.test(version)) {
450
- for (const relative of ["platform/darwin", "bootstrap/platform/darwin"]) {
451
- const helper = path.join(root, "plugins/hellolynk-codex/runtime", relative, "hellolynk-keychain-helper.bin");
452
- if (!((await fs.lstat(helper)).mode & 0o100)) throw failure("package_pin_invalid");
457
+ if (MAC_CLIENT_VERSION.test(version)) {
458
+ const helpers = client === "codex"
459
+ ? ["plugins/hellolynk-codex/runtime/platform/darwin", "plugins/hellolynk-codex/runtime/bootstrap/platform/darwin"]
460
+ : ["runtime/platform/darwin"];
461
+ for (const relative of helpers) {
462
+ const file = `${relative}/hellolynk-keychain-helper.bin`;
463
+ if (!manifest.files.some(record => record.path === file)
464
+ || (process.platform !== "win32" && !((await fs.lstat(path.join(root, file))).mode & 0o100))) throw failure("package_pin_invalid");
453
465
  }
454
466
  }
467
+ return { coreRoot: pkg.hellolynkClient.coreRoot, files: manifest.files };
455
468
  }
456
469
  async function verifyRecords(root, records, allowedExtra) {
457
470
  if (!Array.isArray(records) || records.length < 1 || records.length > 5000) throw failure("package_pin_invalid");
@@ -482,7 +495,7 @@ async function verifyInstalledContext(config) {
482
495
  function normalize(value) {
483
496
  const platform = value.platform || process.platform;
484
497
  if (!CLIENT_UPDATE_CLIENTS.includes(value.client) || typeof value.currentVersion !== "string" || !VERSION.test(value.currentVersion)
485
- || (platform === "darwin" && (value.client !== "codex" || !MAC_CODEX_VERSION.test(value.currentVersion)))
498
+ || (platform === "darwin" && !MAC_CLIENT_VERSION.test(value.currentVersion))
486
499
  || !path.isAbsolute(String(value.root || "")) || !path.isAbsolute(String(value.installContext?.packageRoot || ""))) throw failure("configuration_invalid");
487
500
  const context = { packageRoot: path.resolve(value.installContext.packageRoot) };
488
501
  for (const name of ["nodePath", "dshProfile", "dshRoot"]) {