@hellolynk-ai/codex-plugin 0.2.104-macos.1 → 0.2.105-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.
@@ -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.104-macos.1",
5
+ "version": "0.2.105-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",
@@ -33,7 +33,7 @@
33
33
  {
34
34
  "path": "package.json",
35
35
  "bytes": 1776,
36
- "sha256": "9485c043ba6f164976d9256d03da20c8db9a5c688d10359fc006a5b17ffeb917"
36
+ "sha256": "359e72f8e2c6b348b590151d5c95f8dd9bf08e7812e55baea52c074286072124"
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": "5cbee29306a0ff88cbe11d76bcaefdbfd5b56292cf8e5a4de21577ca0b61674e"
46
+ "sha256": "dc33e34ece594590424d3ec1508409e07a02741db5d93d368824e46f12c095a2"
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": "6f378efda0ab9a8f5555e88978dfaccdf7db448b7219da4e86b21fe0b59f1f75"
61
+ "sha256": "be5fc524bab2f659f8f1e851bd1c59c6edf2a5fd4394c1a917a8d5ee9321190e"
62
62
  },
63
63
  {
64
64
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs",
@@ -67,8 +67,8 @@
67
67
  },
68
68
  {
69
69
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs",
70
- "bytes": 33550,
71
- "sha256": "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"
70
+ "bytes": 35808,
71
+ "sha256": "dbab0f067c1a7f02c43b2696e1f5972288acd4f93a18684bf3cc120edff5cf94"
72
72
  },
73
73
  {
74
74
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs",
@@ -107,8 +107,8 @@
107
107
  },
108
108
  {
109
109
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs",
110
- "bytes": 38559,
111
- "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
110
+ "bytes": 40123,
111
+ "sha256": "4b4b27cd86f4816e5e6ec4914816a335cdea5c4b4ffb2091775c061a9676a69c"
112
112
  },
113
113
  {
114
114
  "path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs",
@@ -147,8 +147,8 @@
147
147
  },
148
148
  {
149
149
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs",
150
- "bytes": 33550,
151
- "sha256": "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"
150
+ "bytes": 35808,
151
+ "sha256": "dbab0f067c1a7f02c43b2696e1f5972288acd4f93a18684bf3cc120edff5cf94"
152
152
  },
153
153
  {
154
154
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs",
@@ -332,8 +332,8 @@
332
332
  },
333
333
  {
334
334
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs",
335
- "bytes": 46842,
336
- "sha256": "d0ac9c697bf8a85263d99a0739da557c62f52c72f4afb105eb97b3aac2ae1f3b"
335
+ "bytes": 46294,
336
+ "sha256": "287ad6e077c8da05799b970ffa6607d8c7a0ab54a4837de51516e6af0ef6695b"
337
337
  },
338
338
  {
339
339
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.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": "7b237a1400c604da5decbb29ab3ce556fd1b744d0cfd7d28b5560b217da120b1"
351
+ "sha256": "ad6809633f49f7316acf6ce5b8f71a13d7ba20d3b62a2700664421ba671b5369"
352
352
  },
353
353
  {
354
354
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs",
@@ -362,8 +362,8 @@
362
362
  },
363
363
  {
364
364
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs",
365
- "bytes": 38559,
366
- "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
365
+ "bytes": 40123,
366
+ "sha256": "4b4b27cd86f4816e5e6ec4914816a335cdea5c4b4ffb2091775c061a9676a69c"
367
367
  },
368
368
  {
369
369
  "path": "plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs",
@@ -408,12 +408,12 @@
408
408
  {
409
409
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json",
410
410
  "bytes": 14698,
411
- "sha256": "96b171b53d91da097e2c36c36dbe9e6e6b0e2ca0fb34c1741b0df2cb5ac38e9b"
411
+ "sha256": "f4b81255068b2a48cdf759b06af6f11409e5e89b8be1de75799c9db053a0a1ec"
412
412
  },
413
413
  {
414
414
  "path": "plugins/hellolynk-codex/runtime/neutral-host-release-pin.json",
415
415
  "bytes": 146,
416
- "sha256": "f68a4fe31eba6adc1c31aa1c2ccf399e584e02ea9556371f2b8363cf5633c7f3"
416
+ "sha256": "86b4c52c5fa4988c3337698b838fc6f36d468d6342bc7f0075c3e054b29908ee"
417
417
  },
418
418
  {
419
419
  "path": "plugins/hellolynk-codex/runtime/platform/darwin/hellolynk-keychain-helper.bin",
@@ -593,7 +593,7 @@
593
593
  {
594
594
  "path": "plugins/hellolynk-codex/server/codex-neutral-production.mjs",
595
595
  "bytes": 27551,
596
- "sha256": "2c2f209339385cc14f27fe985f4deb75f4bd48e689035311847cc3b08e36ac52"
596
+ "sha256": "a307100c0b5835f5a6cba5151db367515b7f6cedd09d16af914f28e163df107a"
597
597
  },
598
598
  {
599
599
  "path": "plugins/hellolynk-codex/server/mcp-proxy.mjs",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-client-npm-pack-pin-v1",
3
- "manifestSha256": "bb5217cd79d40f6b12e7361bc4f655ec7a1918b6f17d3138cfdcd0d6d7c25644"
3
+ "manifestSha256": "f51a0c64b7f2492259f270d2d320601020fa2b02600e3e27279c8f87f8a7b67a"
4
4
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hellolynk-ai/codex-plugin",
3
- "version": "0.2.104-macos.1",
3
+ "version": "0.2.105-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.104",
3
+ "version": "0.2.105",
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.104",
3
+ "version": "0.2.105",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "description": "Project-local HelloLynk Messaging, unread notification, and secure messaging plugin for Codex.",
@@ -1,9 +1,11 @@
1
+ import { createHash } from "node:crypto";
1
2
  import runtimeSafety from "./hellolynk-runtime-safety.cjs";
2
3
  const DEFAULT_TIMEOUT_MS = 10_000;
3
4
  const MAX_JSON_RESPONSE_BYTES = 256 * 1024;
4
5
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
5
6
  const DEVICE_RUNTIME_TTL_MS = 20 * 60 * 1000;
6
7
  const DEVICE_RUNTIME_RECOVERY_WINDOW_MS = 120_000;
8
+ const DEVICE_CREDENTIAL_RECHECK_MS = 60 * 60_000;
7
9
  const DEVICE_RUNTIME_SCOPES = Object.freeze([
8
10
  "relay.text.receive",
9
11
  "relay.text.send",
@@ -567,6 +569,7 @@ export function createDeviceRuntimeSessionManager(options = {}) {
567
569
  const issue = options.issue || issueDeviceRuntimeSession;
568
570
  const now = options.now || (() => Date.now());
569
571
  const sessionStore = options.sessionStore || null;
572
+ const credentialRejections = sessionStore?.credentialRejections;
570
573
  const sessionKey = options.sessionKey == null ? null : String(options.sessionKey);
571
574
  if (sessionStore && (
572
575
  !sessionKey ||
@@ -580,6 +583,8 @@ export function createDeviceRuntimeSessionManager(options = {}) {
580
583
  5 * 60 * 1000,
581
584
  );
582
585
  let current = null;
586
+ let rejectedCredential = null;
587
+ let localIssueTail = Promise.resolve();
583
588
 
584
589
  const routeMatches = (session, input) => {
585
590
  const expectedValues = [
@@ -652,22 +657,67 @@ export function createDeviceRuntimeSessionManager(options = {}) {
652
657
  return proof;
653
658
  };
654
659
 
660
+ const issueSession = async (input, previousSession) => {
661
+ const request = { ...input, previousSession };
662
+ if (options.loadDeviceCredential) request.deviceCredential = await options.loadDeviceCredential();
663
+ const fingerprint = request.deviceCredential && request.baseUrl
664
+ ? createHash("sha256").update(JSON.stringify([
665
+ normalizeHelloLynkBaseUrl(request.baseUrl, { allowLoopbackHttp: request.allowLoopbackHttp === true }),
666
+ request.deviceCredential,
667
+ ])).digest("hex")
668
+ : null;
669
+ // The production caller holds the existing session lock, including across
670
+ // Host processes. An authority denial may drain the current Host.
671
+ if (fingerprint) {
672
+ const isCoolingDown = (rejection) => {
673
+ if (rejection?.fingerprint !== fingerprint) return false;
674
+ const age = now() - rejection.rejectedAtMs;
675
+ return age >= 0 && age < DEVICE_CREDENTIAL_RECHECK_MS;
676
+ };
677
+ // A generic denial may clear after a server-side repair, without a new
678
+ // credential. The lock permits one recheck once the cooldown ends.
679
+ if (isCoolingDown(rejectedCredential)
680
+ || isCoolingDown(await credentialRejections?.load(sessionKey).catch(() => null))) {
681
+ throw new HelloLynkClientError("device_credential_invalid", { httpStatus: 401 });
682
+ }
683
+ }
684
+ let session;
685
+ try {
686
+ session = reusable(await issue(request), input);
687
+ } catch (error) {
688
+ if (fingerprint && error?.httpStatus === 401 && error.code === "device_credential_invalid") {
689
+ rejectedCredential = { fingerprint, rejectedAtMs: now() };
690
+ // Preserve the authority denial even if local storage is unavailable.
691
+ await credentialRejections?.save(sessionKey, rejectedCredential).catch(() => {});
692
+ }
693
+ throw error;
694
+ }
695
+ if (session && fingerprint) {
696
+ rejectedCredential = null;
697
+ await credentialRejections?.remove(sessionKey).catch(() => {});
698
+ }
699
+ return session;
700
+ };
701
+
655
702
  return {
656
703
  async get(input) {
657
704
  if (!sessionStore) {
658
- if (
659
- current && current.expiresAtMs - now() > minimumRemainingMs
660
- && routeMatches(current, input)
661
- && scopesMatch(current, input)
662
- && (input?.expectedAgentId == null || input.expectedAgentId === current.agentId)
663
- && (input?.expectedPrincipalId == null || input.expectedPrincipalId === current.principalId)
664
- ) return current;
665
- const reusableCurrent = reusable(current, input);
666
- if (reusableCurrent) return reusableCurrent;
667
- const previousSession = recoveryProof(current, input);
668
- current = reusable(await issue({ ...input, previousSession }), input);
669
- if (!current) throw new HelloLynkClientError("invalid_runtime_session_handle");
670
- return current;
705
+ const result = localIssueTail.then(async () => {
706
+ if (
707
+ current && current.expiresAtMs - now() > minimumRemainingMs
708
+ && routeMatches(current, input)
709
+ && scopesMatch(current, input)
710
+ && (input?.expectedAgentId == null || input.expectedAgentId === current.agentId)
711
+ && (input?.expectedPrincipalId == null || input.expectedPrincipalId === current.principalId)
712
+ ) return current;
713
+ const reusableCurrent = reusable(current, input);
714
+ if (reusableCurrent) return reusableCurrent;
715
+ current = await issueSession(input, recoveryProof(current, input));
716
+ if (!current) throw new HelloLynkClientError("invalid_runtime_session_handle");
717
+ return current;
718
+ });
719
+ localIssueTail = result.catch(() => {});
720
+ return result;
671
721
  }
672
722
  const inMemory = reusable(current, input);
673
723
  if (inMemory) return inMemory;
@@ -678,7 +728,7 @@ export function createDeviceRuntimeSessionManager(options = {}) {
678
728
  current = persisted;
679
729
  return current;
680
730
  }
681
- const issued = reusable(await issue({ ...input, previousSession: persisted }), input);
731
+ const issued = await issueSession(input, persisted);
682
732
  if (!issued) throw new HelloLynkClientError("invalid_runtime_session_handle");
683
733
  await sessionStore.save(sessionKey, issued);
684
734
  current = issued;
@@ -297,6 +297,36 @@ export async function secureWindowsClientReleaseTree(clientRoot, releaseId, opti
297
297
  });
298
298
  }
299
299
 
300
+ // Accessed under the same cross-process lock as the corresponding session.
301
+ function createCredentialRejectionStore(root) {
302
+ const fileFor = (profile) => join(root, `runtime-session-rejection-${assertCredentialProfileId(profile)}.json`);
303
+ const isRejection = (value) => typeof value?.fingerprint === "string" && /^[a-f0-9]{64}$/.test(value.fingerprint)
304
+ && Number.isSafeInteger(value.rejectedAtMs) && value.rejectedAtMs >= 0;
305
+ return {
306
+ async load(profile) {
307
+ let value;
308
+ try { value = JSON.parse(await readFile(fileFor(profile), "utf8")); }
309
+ catch (error) { if (error?.code === "ENOENT" || error instanceof SyntaxError) return null; throw error; }
310
+ return isRejection(value) ? value : null;
311
+ },
312
+ async save(profile, rejection) {
313
+ if (!isRejection(rejection)) throw new HelloLynkCredentialStoreError("invalid_credential_rejection");
314
+ const { fingerprint, rejectedAtMs } = rejection;
315
+ const file = fileFor(profile);
316
+ const temporary = `${file}.${randomBytes(12).toString("hex")}.tmp`;
317
+ try {
318
+ await writeFile(temporary, `${JSON.stringify({ fingerprint, rejectedAtMs })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
319
+ await rename(temporary, file);
320
+ } finally {
321
+ await rm(temporary, { force: true }).catch(() => {});
322
+ }
323
+ },
324
+ async remove(profile) {
325
+ await rm(fileFor(profile), { force: true });
326
+ },
327
+ };
328
+ }
329
+
300
330
  export function createPlatformCredentialStore(options = {}) {
301
331
  const platform = options.platform || process.platform;
302
332
  const run = options.runHelper || runCredentialHelper;
@@ -370,6 +400,7 @@ export function createPlatformCredentialStore(options = {}) {
370
400
  await invoke("Remove", assertCredentialProfileId(profile), "");
371
401
  },
372
402
  runtimeSessions: {
403
+ credentialRejections: createCredentialRejectionStore(runtimeStoreRoot),
373
404
  async save(profile, session) {
374
405
  const body = JSON.stringify({ schemaVersion: 1, ...assertRuntimeSession(session) });
375
406
  await invoke("StoreRuntimeSession", assertCredentialProfileId(profile), body);
@@ -504,6 +535,7 @@ export function createPlatformCredentialStore(options = {}) {
504
535
  },
505
536
  async remove(profile) { await entry(root, "device", profile, "remove"); },
506
537
  runtimeSessions: {
538
+ credentialRejections: createCredentialRejectionStore(root),
507
539
  async save(profile, session) { await entry(root, "session", profile, "save", assertRuntimeSession(session)); },
508
540
  async load(profile) {
509
541
  const value = await entry(root, "session", profile, "load");
@@ -1,9 +1,11 @@
1
+ import { createHash } from "node:crypto";
1
2
  import runtimeSafety from "./hellolynk-runtime-safety.cjs";
2
3
  const DEFAULT_TIMEOUT_MS = 10_000;
3
4
  const MAX_JSON_RESPONSE_BYTES = 256 * 1024;
4
5
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "[::1]"]);
5
6
  const DEVICE_RUNTIME_TTL_MS = 20 * 60 * 1000;
6
7
  const DEVICE_RUNTIME_RECOVERY_WINDOW_MS = 120_000;
8
+ const DEVICE_CREDENTIAL_RECHECK_MS = 60 * 60_000;
7
9
  const DEVICE_RUNTIME_SCOPES = Object.freeze([
8
10
  "relay.text.receive",
9
11
  "relay.text.send",
@@ -567,6 +569,7 @@ export function createDeviceRuntimeSessionManager(options = {}) {
567
569
  const issue = options.issue || issueDeviceRuntimeSession;
568
570
  const now = options.now || (() => Date.now());
569
571
  const sessionStore = options.sessionStore || null;
572
+ const credentialRejections = sessionStore?.credentialRejections;
570
573
  const sessionKey = options.sessionKey == null ? null : String(options.sessionKey);
571
574
  if (sessionStore && (
572
575
  !sessionKey ||
@@ -580,6 +583,8 @@ export function createDeviceRuntimeSessionManager(options = {}) {
580
583
  5 * 60 * 1000,
581
584
  );
582
585
  let current = null;
586
+ let rejectedCredential = null;
587
+ let localIssueTail = Promise.resolve();
583
588
 
584
589
  const routeMatches = (session, input) => {
585
590
  const expectedValues = [
@@ -652,22 +657,67 @@ export function createDeviceRuntimeSessionManager(options = {}) {
652
657
  return proof;
653
658
  };
654
659
 
660
+ const issueSession = async (input, previousSession) => {
661
+ const request = { ...input, previousSession };
662
+ if (options.loadDeviceCredential) request.deviceCredential = await options.loadDeviceCredential();
663
+ const fingerprint = request.deviceCredential && request.baseUrl
664
+ ? createHash("sha256").update(JSON.stringify([
665
+ normalizeHelloLynkBaseUrl(request.baseUrl, { allowLoopbackHttp: request.allowLoopbackHttp === true }),
666
+ request.deviceCredential,
667
+ ])).digest("hex")
668
+ : null;
669
+ // The production caller holds the existing session lock, including across
670
+ // Host processes. An authority denial may drain the current Host.
671
+ if (fingerprint) {
672
+ const isCoolingDown = (rejection) => {
673
+ if (rejection?.fingerprint !== fingerprint) return false;
674
+ const age = now() - rejection.rejectedAtMs;
675
+ return age >= 0 && age < DEVICE_CREDENTIAL_RECHECK_MS;
676
+ };
677
+ // A generic denial may clear after a server-side repair, without a new
678
+ // credential. The lock permits one recheck once the cooldown ends.
679
+ if (isCoolingDown(rejectedCredential)
680
+ || isCoolingDown(await credentialRejections?.load(sessionKey).catch(() => null))) {
681
+ throw new HelloLynkClientError("device_credential_invalid", { httpStatus: 401 });
682
+ }
683
+ }
684
+ let session;
685
+ try {
686
+ session = reusable(await issue(request), input);
687
+ } catch (error) {
688
+ if (fingerprint && error?.httpStatus === 401 && error.code === "device_credential_invalid") {
689
+ rejectedCredential = { fingerprint, rejectedAtMs: now() };
690
+ // Preserve the authority denial even if local storage is unavailable.
691
+ await credentialRejections?.save(sessionKey, rejectedCredential).catch(() => {});
692
+ }
693
+ throw error;
694
+ }
695
+ if (session && fingerprint) {
696
+ rejectedCredential = null;
697
+ await credentialRejections?.remove(sessionKey).catch(() => {});
698
+ }
699
+ return session;
700
+ };
701
+
655
702
  return {
656
703
  async get(input) {
657
704
  if (!sessionStore) {
658
- if (
659
- current && current.expiresAtMs - now() > minimumRemainingMs
660
- && routeMatches(current, input)
661
- && scopesMatch(current, input)
662
- && (input?.expectedAgentId == null || input.expectedAgentId === current.agentId)
663
- && (input?.expectedPrincipalId == null || input.expectedPrincipalId === current.principalId)
664
- ) return current;
665
- const reusableCurrent = reusable(current, input);
666
- if (reusableCurrent) return reusableCurrent;
667
- const previousSession = recoveryProof(current, input);
668
- current = reusable(await issue({ ...input, previousSession }), input);
669
- if (!current) throw new HelloLynkClientError("invalid_runtime_session_handle");
670
- return current;
705
+ const result = localIssueTail.then(async () => {
706
+ if (
707
+ current && current.expiresAtMs - now() > minimumRemainingMs
708
+ && routeMatches(current, input)
709
+ && scopesMatch(current, input)
710
+ && (input?.expectedAgentId == null || input.expectedAgentId === current.agentId)
711
+ && (input?.expectedPrincipalId == null || input.expectedPrincipalId === current.principalId)
712
+ ) return current;
713
+ const reusableCurrent = reusable(current, input);
714
+ if (reusableCurrent) return reusableCurrent;
715
+ current = await issueSession(input, recoveryProof(current, input));
716
+ if (!current) throw new HelloLynkClientError("invalid_runtime_session_handle");
717
+ return current;
718
+ });
719
+ localIssueTail = result.catch(() => {});
720
+ return result;
671
721
  }
672
722
  const inMemory = reusable(current, input);
673
723
  if (inMemory) return inMemory;
@@ -678,7 +728,7 @@ export function createDeviceRuntimeSessionManager(options = {}) {
678
728
  current = persisted;
679
729
  return current;
680
730
  }
681
- const issued = reusable(await issue({ ...input, previousSession: persisted }), input);
731
+ const issued = await issueSession(input, persisted);
682
732
  if (!issued) throw new HelloLynkClientError("invalid_runtime_session_handle");
683
733
  await sessionStore.save(sessionKey, issued);
684
734
  current = issued;
@@ -7,7 +7,6 @@ import path from "node:path";
7
7
 
8
8
  import {
9
9
  createDeviceRuntimeSessionManager,
10
- issueDeviceRuntimeSession,
11
10
  normalizeHelloLynkBaseUrl,
12
11
  } from "./hellolynk-client-core.mjs";
13
12
  import { readClientProfile } from "./hellolynk-client-profile.mjs";
@@ -243,31 +242,19 @@ export async function startHelloLynkNeutralHostAssembly(options = {}, dependenci
243
242
  const manager = (dependencies.createRuntimeSessionManager || createDeviceRuntimeSessionManager)({
244
243
  sessionStore: credentialStore.runtimeSessions,
245
244
  sessionKey,
246
- issue: async ({ previousSession } = {}) => {
247
- let deviceCredential = await credentialStore.load(profile.profileId);
248
- try {
249
- return await (dependencies.issueRuntimeSession || issueDeviceRuntimeSession)({
250
- baseUrl: profile.baseUrl,
251
- allowLoopbackHttp: profile.allowLoopbackHttp,
252
- deviceCredential,
253
- expectedPrincipalId: profile.principalId,
254
- expectedAgentId: profile.agentId,
255
- expectedDelegateId: profile.delegateId,
256
- expectedHostId: profile.hostId,
257
- expectedAuthorizationVersion: profile.authorizationVersion,
258
- previousSession,
259
- fetchImpl: dependencies.fetchImpl,
260
- });
261
- } finally { deviceCredential = null; }
262
- },
245
+ loadDeviceCredential: () => credentialStore.load(profile.profileId),
246
+ issue: dependencies.issueRuntimeSession,
263
247
  });
264
248
  const sessionProvider = Object.freeze({
265
249
  get: () => manager.get({
250
+ baseUrl: profile.baseUrl,
251
+ allowLoopbackHttp: profile.allowLoopbackHttp,
266
252
  expectedPrincipalId: profile.principalId,
267
253
  expectedAgentId: profile.agentId,
268
254
  expectedDelegateId: profile.delegateId,
269
255
  expectedHostId: profile.hostId,
270
256
  expectedAuthorizationVersion: profile.authorizationVersion,
257
+ fetchImpl: dependencies.fetchImpl,
271
258
  }),
272
259
  clear: (error) => manager.clear(error),
273
260
  status: () => manager.status(),
@@ -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 = 16;
37
+ export const HELLOLYNK_HOST_RELEASE_ORDINAL = 17;
38
38
 
39
39
  const INSTALLATION_LOCK_CONTRACT = "hellolynk-neutral-host-installation-lock-v2";
40
40
  const INSTALLATION_LOCK_MAX_BYTES = 4 * 1024;
@@ -297,6 +297,36 @@ export async function secureWindowsClientReleaseTree(clientRoot, releaseId, opti
297
297
  });
298
298
  }
299
299
 
300
+ // Accessed under the same cross-process lock as the corresponding session.
301
+ function createCredentialRejectionStore(root) {
302
+ const fileFor = (profile) => join(root, `runtime-session-rejection-${assertCredentialProfileId(profile)}.json`);
303
+ const isRejection = (value) => typeof value?.fingerprint === "string" && /^[a-f0-9]{64}$/.test(value.fingerprint)
304
+ && Number.isSafeInteger(value.rejectedAtMs) && value.rejectedAtMs >= 0;
305
+ return {
306
+ async load(profile) {
307
+ let value;
308
+ try { value = JSON.parse(await readFile(fileFor(profile), "utf8")); }
309
+ catch (error) { if (error?.code === "ENOENT" || error instanceof SyntaxError) return null; throw error; }
310
+ return isRejection(value) ? value : null;
311
+ },
312
+ async save(profile, rejection) {
313
+ if (!isRejection(rejection)) throw new HelloLynkCredentialStoreError("invalid_credential_rejection");
314
+ const { fingerprint, rejectedAtMs } = rejection;
315
+ const file = fileFor(profile);
316
+ const temporary = `${file}.${randomBytes(12).toString("hex")}.tmp`;
317
+ try {
318
+ await writeFile(temporary, `${JSON.stringify({ fingerprint, rejectedAtMs })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
319
+ await rename(temporary, file);
320
+ } finally {
321
+ await rm(temporary, { force: true }).catch(() => {});
322
+ }
323
+ },
324
+ async remove(profile) {
325
+ await rm(fileFor(profile), { force: true });
326
+ },
327
+ };
328
+ }
329
+
300
330
  export function createPlatformCredentialStore(options = {}) {
301
331
  const platform = options.platform || process.platform;
302
332
  const run = options.runHelper || runCredentialHelper;
@@ -370,6 +400,7 @@ export function createPlatformCredentialStore(options = {}) {
370
400
  await invoke("Remove", assertCredentialProfileId(profile), "");
371
401
  },
372
402
  runtimeSessions: {
403
+ credentialRejections: createCredentialRejectionStore(runtimeStoreRoot),
373
404
  async save(profile, session) {
374
405
  const body = JSON.stringify({ schemaVersion: 1, ...assertRuntimeSession(session) });
375
406
  await invoke("StoreRuntimeSession", assertCredentialProfileId(profile), body);
@@ -504,6 +535,7 @@ export function createPlatformCredentialStore(options = {}) {
504
535
  },
505
536
  async remove(profile) { await entry(root, "device", profile, "remove"); },
506
537
  runtimeSessions: {
538
+ credentialRejections: createCredentialRejectionStore(root),
507
539
  async save(profile, session) { await entry(root, "session", profile, "save", assertRuntimeSession(session)); },
508
540
  async load(profile) {
509
541
  const value = await entry(root, "session", profile, "load");
@@ -4,7 +4,7 @@
4
4
  "files": [
5
5
  {
6
6
  "path": "release/package.json",
7
- "sha256": "6f378efda0ab9a8f5555e88978dfaccdf7db448b7219da4e86b21fe0b59f1f75"
7
+ "sha256": "be5fc524bab2f659f8f1e851bd1c59c6edf2a5fd4394c1a917a8d5ee9321190e"
8
8
  },
9
9
  {
10
10
  "path": "neutral-host-bootstrap.mjs",
@@ -32,7 +32,7 @@
32
32
  },
33
33
  {
34
34
  "path": "core/hellolynk-client-core.mjs",
35
- "sha256": "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"
35
+ "sha256": "dbab0f067c1a7f02c43b2696e1f5972288acd4f93a18684bf3cc120edff5cf94"
36
36
  },
37
37
  {
38
38
  "path": "core/hellolynk-client-profile.mjs",
@@ -184,7 +184,7 @@
184
184
  },
185
185
  {
186
186
  "path": "core/hellolynk-neutral-host-assembly.mjs",
187
- "sha256": "d0ac9c697bf8a85263d99a0739da557c62f52c72f4afb105eb97b3aac2ae1f3b"
187
+ "sha256": "287ad6e077c8da05799b970ffa6607d8c7a0ab54a4837de51516e6af0ef6695b"
188
188
  },
189
189
  {
190
190
  "path": "core/hellolynk-neutral-host-installation-store.mjs",
@@ -196,7 +196,7 @@
196
196
  },
197
197
  {
198
198
  "path": "core/hellolynk-neutral-host-process.mjs",
199
- "sha256": "7b237a1400c604da5decbb29ab3ce556fd1b744d0cfd7d28b5560b217da120b1"
199
+ "sha256": "ad6809633f49f7316acf6ce5b8f71a13d7ba20d3b62a2700664421ba671b5369"
200
200
  },
201
201
  {
202
202
  "path": "core/hellolynk-neutral-host-store-adapters.mjs",
@@ -208,7 +208,7 @@
208
208
  },
209
209
  {
210
210
  "path": "core/hellolynk-platform-credential-store.mjs",
211
- "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
211
+ "sha256": "4b4b27cd86f4816e5e6ec4914816a335cdea5c4b4ffb2091775c061a9676a69c"
212
212
  },
213
213
  {
214
214
  "path": "core/hellolynk-relay-client-cli.mjs",
@@ -324,7 +324,7 @@
324
324
  },
325
325
  {
326
326
  "path": "bootstrap/core/hellolynk-client-core.mjs",
327
- "sha256": "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"
327
+ "sha256": "dbab0f067c1a7f02c43b2696e1f5972288acd4f93a18684bf3cc120edff5cf94"
328
328
  },
329
329
  {
330
330
  "path": "bootstrap/core/hellolynk-client-profile.mjs",
@@ -348,7 +348,7 @@
348
348
  },
349
349
  {
350
350
  "path": "bootstrap/core/hellolynk-platform-credential-store.mjs",
351
- "sha256": "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"
351
+ "sha256": "4b4b27cd86f4816e5e6ec4914816a335cdea5c4b4ffb2091775c061a9676a69c"
352
352
  },
353
353
  {
354
354
  "path": "bootstrap/core/hellolynk-runtime-safety.cjs",
@@ -376,7 +376,7 @@
376
376
  },
377
377
  {
378
378
  "path": "release/server/codex-neutral-production.mjs",
379
- "sha256": "2c2f209339385cc14f27fe985f4deb75f4bd48e689035311847cc3b08e36ac52"
379
+ "sha256": "a307100c0b5835f5a6cba5151db367515b7f6cedd09d16af914f28e163df107a"
380
380
  },
381
381
  {
382
382
  "path": "release/server/neutral-host-adapter.mjs",
@@ -1,4 +1,4 @@
1
1
  {
2
2
  "contract": "hellolynk-neutral-host-release-pin-v1",
3
- "manifestSha256": "96b171b53d91da097e2c36c36dbe9e6e6b0e2ca0fb34c1741b0df2cb5ac38e9b"
3
+ "manifestSha256": "f4b81255068b2a48cdf759b06af6f11409e5e89b8be1de75799c9db053a0a1ec"
4
4
  }
@@ -30,8 +30,8 @@ const TRUSTED_BOOTSTRAP_FILES = Object.freeze(new Map([
30
30
  ["runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs", "42ae1e7deb876a8308b7bfed6a7b891d1834117b62504fc5befe3f91a0b4c740"],
31
31
  ["runtime/bootstrap/core/hellolynk-host-auth.mjs", "db1ec56f4e60e6450fba50188eecf89748f39f5d7a75c2456c30a6053233555d"],
32
32
  ["runtime/bootstrap/core/hellolynk-client-profile.mjs", "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"],
33
- ["runtime/bootstrap/core/hellolynk-platform-credential-store.mjs", "3969708bafefc3b267bb34a9dcc115804c874ad14f4a036426d727c02ff70d42"],
34
- ["runtime/bootstrap/core/hellolynk-client-core.mjs", "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"],
33
+ ["runtime/bootstrap/core/hellolynk-platform-credential-store.mjs", "4b4b27cd86f4816e5e6ec4914816a335cdea5c4b4ffb2091775c061a9676a69c"],
34
+ ["runtime/bootstrap/core/hellolynk-client-core.mjs", "dbab0f067c1a7f02c43b2696e1f5972288acd4f93a18684bf3cc120edff5cf94"],
35
35
  ["runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1", "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"],
36
36
  ...(process.platform === "darwin" ? [
37
37
  ["runtime/bootstrap/platform/darwin/hellolynk-keychain-helper.bin", "f1a782c08229ebc0e8d1704b6390e91c5695e084b4df0a1a6dc043f6b23700ab"],