@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.
- package/bin/hellolynk-client-setup.mjs +7 -2
- package/hellolynk-client-pack-manifest.json +30 -30
- package/hellolynk-client-pack-pin.json +1 -1
- package/package.json +1 -1
- package/plugins/hellolynk-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/hellolynk-codex/package.json +1 -1
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-darwin-host.mjs +45 -32
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +3 -3
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +47 -34
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +46 -16
- package/plugins/hellolynk-codex/runtime/core/hellolynk-darwin-host.mjs +45 -32
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +1 -1
- package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +34 -11
- package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +11 -11
- package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +1 -1
- package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +46 -10
- package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +1 -1
- package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -1193
- package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -33
- package/plugins/hellolynk-codex/server/workbench-runtime.mjs +42 -42
|
@@ -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
|
|
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.
|
|
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":
|
|
21
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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": "
|
|
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":
|
|
81
|
-
"sha256": "
|
|
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":
|
|
161
|
-
"sha256": "
|
|
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":
|
|
166
|
-
"sha256": "
|
|
165
|
+
"bytes": 41618,
|
|
166
|
+
"sha256": "aa233e2cdbdfadb7da6aa489591bc12c4121101b94696f93f6e6cb5d3e7c7870"
|
|
167
167
|
},
|
|
168
168
|
{
|
|
169
169
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs",
|
|
170
|
-
"bytes":
|
|
171
|
-
"sha256": "
|
|
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":
|
|
181
|
-
"sha256": "
|
|
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": "
|
|
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":
|
|
376
|
-
"sha256": "
|
|
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": "
|
|
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": "
|
|
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":
|
|
436
|
-
"sha256": "
|
|
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": "
|
|
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":
|
|
606
|
-
"sha256": "
|
|
605
|
+
"bytes": 60463,
|
|
606
|
+
"sha256": "d20e041d6ce63c52bbdf7faac7e7c2ed288444fca014f861f1a8eb15d8067497"
|
|
607
607
|
},
|
|
608
608
|
{
|
|
609
609
|
"path": "plugins/hellolynk-codex/server/native-conversation-directory.mjs",
|
|
610
|
-
"bytes":
|
|
611
|
-
"sha256": "
|
|
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":
|
|
636
|
-
"sha256": "
|
|
635
|
+
"bytes": 8902,
|
|
636
|
+
"sha256": "1136c7ea50cc73065fe04094c5b5c19cb676c40a0a189979015766a9c4e908d8"
|
|
637
637
|
},
|
|
638
638
|
{
|
|
639
639
|
"path": "plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hellolynk-ai/codex-plugin",
|
|
3
|
-
"version": "0.2.
|
|
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": {
|
|
@@ -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(
|
|
21
|
-
} catch { fail(
|
|
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
|
|
42
|
-
const
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
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 ? "
|
|
49
|
-
if (
|
|
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 (
|
|
54
|
-
|
|
55
|
-
if (
|
|
56
|
-
if (
|
|
57
|
-
|
|
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
|
-
|
|
61
|
-
|
|
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
|
-
|
|
66
|
-
|
|
67
|
-
if (!fieldsMatch(previous, next, ["contract","region","principalId","delegateId","hostInstallationId","profileId","adapterId","version","runtimeRoot"])) {
|
|
68
|
-
fail("
|
|
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("
|
|
79
|
+
if (!equal(b, previous)) fail(error("binding_invalid"));
|
|
71
80
|
const active = s.caps.filter(c => capMatches(c, next));
|
|
72
|
-
if (
|
|
73
|
-
if (!equal(previous.scopes, next.scopes)) fail("
|
|
74
|
-
if (active.length !== 1) fail("
|
|
75
|
-
s
|
|
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 (
|
|
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("
|
|
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("
|
|
91
|
+
if (other.length > 1) fail(error("capability_ambiguous"));
|
|
83
92
|
if (!other.length) addCapability(s, replacementRecord);
|
|
84
93
|
revokeCapability(s, old);
|
|
85
|
-
s
|
|
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
|
-
|
|
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,
|
|
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 =
|
|
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 ?
|
|
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,
|
|
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
|
|
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(
|
|
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(
|
|
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,
|
|
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.
|
|
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
|
-
|
|
415
|
-
|
|
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
|
-
|| (
|
|
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 (
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
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" &&
|
|
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"]) {
|