@hellolynk-ai/codex-plugin 0.2.102-macos.1 → 0.2.103
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/README.md +17 -17
- package/bin/hellolynk-client-setup.mjs +7 -2
- package/hellolynk-client-pack-manifest.json +36 -56
- package/hellolynk-client-pack-pin.json +1 -1
- package/package.json +4 -8
- package/plugins/hellolynk-codex/.codex-plugin/plugin.json +1 -1
- package/plugins/hellolynk-codex/.mcp.json +19 -20
- 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 -27
- 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 +2 -2
- 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
- package/plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin +0 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.swift +0 -149
- package/plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.bin +0 -0
- package/plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.swift +0 -149
|
@@ -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"]) {
|
|
@@ -6,17 +6,24 @@ import { withNeutralHostStoreLock } from "./hellolynk-neutral-host-installation-
|
|
|
6
6
|
|
|
7
7
|
const CATALOG = "https://hellolynk.com/downloads/clients.json";
|
|
8
8
|
export const CLIENT_UPDATE_CLIENTS = Object.freeze(["codex", "openclaw", "trae-work", "workbuddy", "dsh", "claude-code"]);
|
|
9
|
-
export const
|
|
9
|
+
export const MAC_CLIENT_VERSION = /^\d+\.\d+\.\d+-macos\.[1-9]\d*$/u;
|
|
10
10
|
const HOUR = 3_600_000;
|
|
11
11
|
const MAX_ARTIFACT = 64 * 1024 * 1024;
|
|
12
12
|
const HASH = /^[a-f0-9]{64}$/;
|
|
13
|
-
const CONTRACT = "hellolynk-client-update-
|
|
13
|
+
const CONTRACT = "hellolynk-client-update-v2";
|
|
14
|
+
export const CLIENT_UPDATE_RELEASES = "releases-v2";
|
|
15
|
+
// The release freeze pins only public Ed25519 keys here. An empty set refuses all updates.
|
|
16
|
+
export const CLIENT_UPDATE_CATALOG_KEYS = Object.freeze({
|
|
17
|
+
"ed25519-20261109-primary-d4fd7f46475dac48": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAucQohi+be+ksjQhRzYD8ZiyZMUz6Vi/DSphQ3iVOWDM=\n-----END PUBLIC KEY-----\n",
|
|
18
|
+
"ed25519-20261109-backup-5e3d08a775320304": "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEA2vxHpcgLZQc1cXqxq3dcmZRmE0fAFyeRC3umMVGAosA=\n-----END PUBLIC KEY-----\n"
|
|
19
|
+
});
|
|
20
|
+
export const CLIENT_UPDATE_CATALOG_LIMIT = 256 * 1024;
|
|
14
21
|
// A failed integrity or path check keeps a downloaded package from installing. Every other failure is transient.
|
|
15
|
-
const BLOCKING = /^client_update_(?:same_version_changed|version_rollback|artifact_integrity_invalid|archive_[a-z_]+|path_invalid|url_invalid|redirect_invalid)$/;
|
|
22
|
+
const BLOCKING = /^client_update_(?:catalog_signature_invalid|same_version_changed|version_rollback|artifact_integrity_invalid|archive_[a-z_]+|path_invalid|url_invalid|redirect_invalid)$/;
|
|
16
23
|
|
|
17
24
|
export function clientUpdateCatalogId(client, platform) {
|
|
18
25
|
if (platform === "win32") return client;
|
|
19
|
-
if (platform === "darwin" && client === "codex") return
|
|
26
|
+
if (platform === "darwin" && (client === "codex" || client === "claude-code")) return `${client}-macos`;
|
|
20
27
|
return null;
|
|
21
28
|
}
|
|
22
29
|
|
|
@@ -26,10 +33,12 @@ export function createClientUpdateManager(options = {}) {
|
|
|
26
33
|
const { client, currentVersion, platform = process.platform } = options;
|
|
27
34
|
if (!CLIENT_UPDATE_CLIENTS.includes(client) || !semver(currentVersion) || !path.isAbsolute(String(options.root || ""))) fail("configuration_invalid");
|
|
28
35
|
const catalogId = clientUpdateCatalogId(client, platform);
|
|
29
|
-
const mac =
|
|
30
|
-
if (mac && !
|
|
36
|
+
const mac = platform === "darwin";
|
|
37
|
+
if (mac && !MAC_CLIENT_VERSION.test(currentVersion)) fail("configuration_invalid");
|
|
31
38
|
const root = path.join(path.resolve(options.root), "client-updates", client);
|
|
32
|
-
|
|
39
|
+
// A still-running unsigned updater may read, rewrite and prune its own cache.
|
|
40
|
+
const stateFile = path.join(root, "state-v2.json");
|
|
41
|
+
const releases = path.join(root, CLIENT_UPDATE_RELEASES);
|
|
33
42
|
const fetcher = options.fetchImpl || globalThis.fetch;
|
|
34
43
|
const clock = options.clock || Date.now;
|
|
35
44
|
const timeoutMs = bounded(options.timeoutMs, 30_000, 50, 120_000);
|
|
@@ -70,7 +79,7 @@ export function createClientUpdateManager(options = {}) {
|
|
|
70
79
|
}
|
|
71
80
|
}
|
|
72
81
|
|
|
73
|
-
function releasePath(record) { return path.join(
|
|
82
|
+
function releasePath(record) { return path.join(releases, `${record.version}-${record.sha256}`); }
|
|
74
83
|
async function save(record) {
|
|
75
84
|
await safeDirectory(root);
|
|
76
85
|
const temporary = path.join(root, `.state-${crypto.randomUUID()}.tmp`);
|
|
@@ -92,10 +101,11 @@ export function createClientUpdateManager(options = {}) {
|
|
|
92
101
|
if (!force && old.checkedAt > 0 && now >= old.checkedAt && now - old.checkedAt < 23 * HOUR) return old;
|
|
93
102
|
if (!catalogId) return save({ ...old, stage: "unsupported", checkedAt: now });
|
|
94
103
|
try {
|
|
95
|
-
const metadata = await request(CATALOG, { limit:
|
|
104
|
+
const metadata = await request(CATALOG, { limit: CLIENT_UPDATE_CATALOG_LIMIT, timeout: timeoutMs, kind: "catalog" });
|
|
96
105
|
let catalog;
|
|
97
106
|
try { catalog = JSON.parse(metadata.toString("utf8")); } catch { fail("catalog_invalid"); }
|
|
98
|
-
const
|
|
107
|
+
const verified = verifyClientUpdateCatalog(catalog, options.catalogKeys);
|
|
108
|
+
const artifact = validateClientUpdateCatalogEntry(verified, catalogId, mac);
|
|
99
109
|
// The catalog has no build for this client: there is nothing to install.
|
|
100
110
|
if (!artifact) {
|
|
101
111
|
// A downloaded package keeps its stage and any integrity block; only an empty cache is "current".
|
|
@@ -144,7 +154,7 @@ export function createClientUpdateManager(options = {}) {
|
|
|
144
154
|
if (!bytes) fail("source_unavailable");
|
|
145
155
|
const release = releasePath(artifact);
|
|
146
156
|
await safeDirectory(path.dirname(release));
|
|
147
|
-
const stage = path.join(
|
|
157
|
+
const stage = path.join(releases, `.stage-${crypto.randomUUID()}`);
|
|
148
158
|
await fs.mkdir(stage, { mode: 0o700 });
|
|
149
159
|
try {
|
|
150
160
|
const archivePath = path.join(stage, artifact.filename);
|
|
@@ -157,7 +167,7 @@ export function createClientUpdateManager(options = {}) {
|
|
|
157
167
|
return;
|
|
158
168
|
} catch { /* Preserve damaged evidence without reusing it. */ }
|
|
159
169
|
await safeAncestors(release, true);
|
|
160
|
-
const quarantine = path.join(
|
|
170
|
+
const quarantine = path.join(releases, `.corrupt-${artifact.sha256}-${crypto.randomUUID()}`);
|
|
161
171
|
await fs.rename(release, quarantine);
|
|
162
172
|
try { await fs.rename(stage, release); }
|
|
163
173
|
catch (replacement) { await fs.rename(quarantine, release).catch(() => {}); throw replacement; }
|
|
@@ -182,7 +192,7 @@ export function createClientUpdateManager(options = {}) {
|
|
|
182
192
|
});
|
|
183
193
|
}
|
|
184
194
|
|
|
185
|
-
// After an install only the package state
|
|
195
|
+
// After an install only the package the signed state names stays cached; older
|
|
186
196
|
// versions and quarantined copies go. Holding the check lock means no
|
|
187
197
|
// download is in flight to be mistaken for leftovers.
|
|
188
198
|
async function prune() {
|
|
@@ -190,7 +200,7 @@ export function createClientUpdateManager(options = {}) {
|
|
|
190
200
|
return withNeutralHostStoreLock(root, "client-update", async () => {
|
|
191
201
|
const state = await readState();
|
|
192
202
|
if (!semver(state.version) || !HASH.test(String(state.sha256 || ""))) return [];
|
|
193
|
-
const keep = path.basename(releasePath(state)),
|
|
203
|
+
const keep = path.basename(releasePath(state)), removed = [];
|
|
194
204
|
await safeAncestors(releases, true);
|
|
195
205
|
let entries;
|
|
196
206
|
try { entries = await fs.readdir(releases, { withFileTypes: true }); }
|
|
@@ -277,13 +287,33 @@ export function createClientUpdateManager(options = {}) {
|
|
|
277
287
|
});
|
|
278
288
|
}
|
|
279
289
|
|
|
280
|
-
function
|
|
290
|
+
export function verifyClientUpdateCatalog(catalog, keys = CLIENT_UPDATE_CATALOG_KEYS) {
|
|
291
|
+
const signed = catalog?.signed;
|
|
292
|
+
if (!signed || typeof signed.keyId !== "string" || !/^[A-Za-z0-9_-]{1,64}$/.test(signed.keyId)
|
|
293
|
+
|| !Object.hasOwn(keys, signed.keyId) || typeof signed.payload !== "string" || !signed.payload
|
|
294
|
+
|| signed.payload.length > CLIENT_UPDATE_CATALOG_LIMIT || typeof signed.signature !== "string"
|
|
295
|
+
|| !/^[A-Za-z0-9_-]{86}$/.test(signed.signature)) fail("catalog_signature_invalid");
|
|
296
|
+
const payload = Buffer.from(signed.payload, "base64");
|
|
297
|
+
const signature = Buffer.from(signed.signature, "base64url");
|
|
298
|
+
if (payload.toString("base64") !== signed.payload || signature.toString("base64url") !== signed.signature) fail("catalog_signature_invalid");
|
|
299
|
+
let valid = false;
|
|
300
|
+
try {
|
|
301
|
+
const key = crypto.createPublicKey(keys[signed.keyId]);
|
|
302
|
+
valid = key.asymmetricKeyType === "ed25519" && crypto.verify(null, payload, key, signature);
|
|
303
|
+
} catch { /* Invalid pins and invalid signatures both fail closed. */ }
|
|
304
|
+
if (!valid) fail("catalog_signature_invalid");
|
|
305
|
+
const json = payload.toString("utf8");
|
|
306
|
+
if (!Buffer.from(json, "utf8").equals(payload)) fail("catalog_invalid");
|
|
307
|
+
try { return JSON.parse(json); } catch { fail("catalog_invalid"); }
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
export function validateClientUpdateCatalogEntry(catalog, client, mac) {
|
|
281
311
|
if (!catalog || !Array.isArray(catalog.clients) || catalog.clients.length > 32) fail("catalog_invalid");
|
|
282
312
|
const matches = catalog.clients.filter((item) => item?.client === client);
|
|
283
313
|
if (matches.length === 0) return null;
|
|
284
314
|
if (matches.length > 1) fail("catalog_invalid");
|
|
285
315
|
const item = matches[0];
|
|
286
|
-
if (!semver(item.version) || (mac && !
|
|
316
|
+
if (!semver(item.version) || (mac && !MAC_CLIENT_VERSION.test(item.version))
|
|
287
317
|
|| !HASH.test(String(item.sha256 || "")) || !safeFilename(item.filename)
|
|
288
318
|
|| !Number.isSafeInteger(item.bytes) || item.bytes < 4 || item.bytes > MAX_ARTIFACT
|
|
289
319
|
|| !Array.isArray(item.platforms) || !item.platforms.includes(mac ? "macOS" : "Windows")) fail("catalog_invalid");
|
|
@@ -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
|
});
|
|
@@ -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 =
|
|
37
|
+
export const HELLOLYNK_HOST_RELEASE_ORDINAL = 16;
|
|
38
38
|
|
|
39
39
|
const INSTALLATION_LOCK_CONTRACT = "hellolynk-neutral-host-installation-lock-v2";
|
|
40
40
|
const INSTALLATION_LOCK_MAX_BYTES = 4 * 1024;
|
|
@@ -95,9 +95,18 @@ export async function runningReleaseNames(releasesRoot) {
|
|
|
95
95
|
export async function entryProcessList(client, releasesRoot, pids = []) {
|
|
96
96
|
if (!ENTRY[client] && client !== 'codex') return [];
|
|
97
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'
|
|
99
|
-
await rootPath(releasesRoot);
|
|
100
|
-
|
|
98
|
+
if (process.platform === 'darwin') {
|
|
99
|
+
const root = await rootPath(releasesRoot);
|
|
100
|
+
const stdout = await darwinPs('pid=,ppid=,lstart=,command=');
|
|
101
|
+
if (client === 'codex') return parseDarwinCodexEntryProcesses(stdout, pids);
|
|
102
|
+
const entries = [...new Set([root, path.resolve(releasesRoot)])]
|
|
103
|
+
.map(value => path.join(path.dirname(value), ...ENTRY[client]).replace(/[.*+?^${}()|[\]\\]/g, '\\$&'));
|
|
104
|
+
const pattern = new RegExp(`(?:^|["\\s])(?:${entries.join('|')})(?=["\\s]|$)`, 'u');
|
|
105
|
+
const wanted = new Set(pids);
|
|
106
|
+
return [...parseDarwinProcesses(stdout).values()].flatMap(({ pid, startedAt, command }) => {
|
|
107
|
+
const entry = pattern.test(command);
|
|
108
|
+
return entry || wanted.has(pid) ? [{ pid, startedAt, entry }] : [];
|
|
109
|
+
});
|
|
101
110
|
}
|
|
102
111
|
if (process.platform !== 'win32') throw failure('release_retention_process_check_unavailable');
|
|
103
112
|
const root = await rootPath(releasesRoot);
|
|
@@ -130,7 +139,7 @@ async function darwinPs(fields) {
|
|
|
130
139
|
|
|
131
140
|
// Keep command lines inside the process probe. Only a PID, its start time and
|
|
132
141
|
// whether it might still be serving the Codex cache reach the retention logic.
|
|
133
|
-
|
|
142
|
+
function parseDarwinProcesses(stdout) {
|
|
134
143
|
const processes = new Map();
|
|
135
144
|
for (const line of stdout.trim().split('\n')) {
|
|
136
145
|
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);
|
|
@@ -139,6 +148,11 @@ export function parseDarwinCodexEntryProcesses(stdout, pids = []) {
|
|
|
139
148
|
const startedAt = Date.parse(`${started} UTC`);
|
|
140
149
|
processes.set(Number(pid), { pid: Number(pid), parent: Number(parent), startedAt: Number.isFinite(startedAt) ? startedAt : null, command });
|
|
141
150
|
}
|
|
151
|
+
return processes;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function parseDarwinCodexEntryProcesses(stdout, pids = []) {
|
|
155
|
+
const processes = parseDarwinProcesses(stdout);
|
|
142
156
|
const wanted = new Set(pids);
|
|
143
157
|
const proxy = /(?:^|["\s/])(?:\.\/)?server\/mcp-proxy\.mjs(?=["\s]|$)/u;
|
|
144
158
|
const codexApp = /(?:^|["/])(?:Codex|ChatGPT)\.app\/Contents\//iu;
|
|
@@ -198,19 +212,28 @@ async function entryReleases(releasesRoot, root, client, pattern, dependencies)
|
|
|
198
212
|
// live processes can delete its executable before the next client startup.
|
|
199
213
|
export async function configuredReleaseNames(releasesRoot) {
|
|
200
214
|
if (process.platform === 'darwin') {
|
|
201
|
-
const
|
|
215
|
+
const root = await rootPath(releasesRoot);
|
|
216
|
+
const pattern = darwinReleasePattern(root, releasesRoot);
|
|
202
217
|
const names = new Set();
|
|
218
|
+
const readAgent = async file => {
|
|
219
|
+
const { stdout } = await execute('/usr/bin/plutil', ['-convert', 'json', '-o', '-', file],
|
|
220
|
+
{ timeout: 15000, maxBuffer: 256 * 1024 });
|
|
221
|
+
const { Program, WorkingDirectory, ProgramArguments } = JSON.parse(stdout);
|
|
222
|
+
for (const value of [Program, WorkingDirectory, ...(ProgramArguments || [])]) {
|
|
223
|
+
if (typeof value === 'string') for (const name of darwinReleaseNames(value, pattern)) names.add(name);
|
|
224
|
+
}
|
|
225
|
+
};
|
|
226
|
+
// A receiver set to start on demand keeps its definition outside LaunchAgents.
|
|
227
|
+
const definition = path.join(path.dirname(root), 'host', 'claude-native-service.plist');
|
|
228
|
+
if (await fs.lstat(definition).catch(error => { if (error.code === 'ENOENT') return null; throw error; })) {
|
|
229
|
+
await readAgent(definition);
|
|
230
|
+
}
|
|
203
231
|
for (const directory of [path.join(homedir(), 'Library', 'LaunchAgents'), '/Library/LaunchAgents', '/Library/LaunchDaemons']) {
|
|
204
232
|
let entries;
|
|
205
233
|
try { entries = await fs.readdir(directory); }
|
|
206
234
|
catch (error) { if (error.code === 'ENOENT') continue; throw error; }
|
|
207
235
|
for (const entry of entries.filter(name => /hellolynk/i.test(name) && name.endsWith('.plist'))) {
|
|
208
|
-
|
|
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
|
-
}
|
|
236
|
+
await readAgent(path.join(directory, entry));
|
|
214
237
|
}
|
|
215
238
|
}
|
|
216
239
|
return [...names].sort();
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "release/package.json",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "f786baab2e9f35836813c6b77e1cbc0fb8c0a3c0a2055b2e6ea777f2428cd232"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"path": "neutral-host-bootstrap.mjs",
|
|
@@ -40,19 +40,19 @@
|
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"path": "core/hellolynk-client-update-entry.mjs",
|
|
43
|
-
"sha256": "
|
|
43
|
+
"sha256": "d9548eacda8632976e42aafc65f0cef2ec9c5233d2d53b31b02aec0a1702b5eb"
|
|
44
44
|
},
|
|
45
45
|
{
|
|
46
46
|
"path": "core/hellolynk-client-update-runtime.mjs",
|
|
47
|
-
"sha256": "
|
|
47
|
+
"sha256": "aa233e2cdbdfadb7da6aa489591bc12c4121101b94696f93f6e6cb5d3e7c7870"
|
|
48
48
|
},
|
|
49
49
|
{
|
|
50
50
|
"path": "core/hellolynk-client-update.mjs",
|
|
51
|
-
"sha256": "
|
|
51
|
+
"sha256": "e57f7cc5e98519a014cef7cef68a97bb7e2beb33eb381c38d1751a3967904aaa"
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"path": "core/hellolynk-release-retention.mjs",
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "0ee150af5a9bcc1f636e3ff980ee90ce7d073ab5ed4a991920cd3472bc65529c"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "core/hellolynk-contact-request-e2ee.mjs",
|
|
@@ -64,7 +64,7 @@
|
|
|
64
64
|
},
|
|
65
65
|
{
|
|
66
66
|
"path": "core/hellolynk-darwin-host.mjs",
|
|
67
|
-
"sha256": "
|
|
67
|
+
"sha256": "ded97c3bc3dd65ca75305f5199a1340dffb987a5f283454d60586df8b6b55c09"
|
|
68
68
|
},
|
|
69
69
|
{
|
|
70
70
|
"path": "core/hellolynk-darwin-vault.mjs",
|
|
@@ -196,7 +196,7 @@
|
|
|
196
196
|
},
|
|
197
197
|
{
|
|
198
198
|
"path": "core/hellolynk-neutral-host-process.mjs",
|
|
199
|
-
"sha256": "
|
|
199
|
+
"sha256": "7b237a1400c604da5decbb29ab3ce556fd1b744d0cfd7d28b5560b217da120b1"
|
|
200
200
|
},
|
|
201
201
|
{
|
|
202
202
|
"path": "core/hellolynk-neutral-host-store-adapters.mjs",
|
|
@@ -292,7 +292,7 @@
|
|
|
292
292
|
},
|
|
293
293
|
{
|
|
294
294
|
"path": "platform/windows/credential-store.ps1",
|
|
295
|
-
"sha256": "
|
|
295
|
+
"sha256": "8f288c91a1f6ecaf2651baab9bdfc55014c11ea9e165f203ca7a69f4d47d7917"
|
|
296
296
|
},
|
|
297
297
|
{
|
|
298
298
|
"path": "platform/windows/hellolynk-host-protected-store.ps1",
|
|
@@ -312,7 +312,7 @@
|
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"path": "bootstrap/core/hellolynk-darwin-host.mjs",
|
|
315
|
-
"sha256": "
|
|
315
|
+
"sha256": "ded97c3bc3dd65ca75305f5199a1340dffb987a5f283454d60586df8b6b55c09"
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
"path": "bootstrap/core/hellolynk-darwin-vault.mjs",
|
|
@@ -358,25 +358,9 @@
|
|
|
358
358
|
"path": "bootstrap/platform/windows/hellolynk-host-protected-store.ps1",
|
|
359
359
|
"sha256": "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"
|
|
360
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
|
-
},
|
|
377
361
|
{
|
|
378
362
|
"path": "release/server/codex-neutral-production.mjs",
|
|
379
|
-
"sha256": "
|
|
363
|
+
"sha256": "e1106264be25cd79fc12f1b774726089eead0f7e237aff231cd739f40e0c1f40"
|
|
380
364
|
},
|
|
381
365
|
{
|
|
382
366
|
"path": "release/server/neutral-host-adapter.mjs",
|
|
@@ -384,7 +368,7 @@
|
|
|
384
368
|
},
|
|
385
369
|
{
|
|
386
370
|
"path": "release/server/workbench-runtime.mjs",
|
|
387
|
-
"sha256": "
|
|
371
|
+
"sha256": "1136c7ea50cc73065fe04094c5b5c19cb676c40a0a189979015766a9c4e908d8"
|
|
388
372
|
}
|
|
389
373
|
]
|
|
390
374
|
}
|