@hellolynk-ai/codex-plugin 0.2.100 → 0.2.102
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 +16 -3
- package/hellolynk-client-pack-manifest.json +23 -23
- 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-client-profile.mjs +25 -2
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +2 -6
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +25 -2
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +1 -1
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +2 -6
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +1 -1
- package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +39 -18
- package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +9 -9
- package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +1 -1
- package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +4 -2
|
@@ -61,8 +61,8 @@ try {
|
|
|
61
61
|
"--expected-manifest-sha256", sourceRelease.outerManifestSha256,
|
|
62
62
|
]);
|
|
63
63
|
} else {
|
|
64
|
-
const originalLocations = resolveLocations();
|
|
65
64
|
const accountModule = await importStableCore(PACKAGE_ROOT, "hellolynk-client-account.mjs", "hellolynk_npm_account_runtime_invalid");
|
|
65
|
+
const originalLocations = resolveLocations(accountModule.clientAccountBaseRoot);
|
|
66
66
|
const baseRoot = accountModule.clientAccountBaseRoot(originalLocations.hostRoot);
|
|
67
67
|
const packageLocations = { ...originalLocations, hostRoot: baseRoot, finalCurrentRoot: path.dirname(baseRoot) };
|
|
68
68
|
const accounts = accountModule.createClientAccountActions({ client: metadata.client, storeRoot: baseRoot });
|
|
@@ -84,6 +84,13 @@ try {
|
|
|
84
84
|
if (mutating) {
|
|
85
85
|
await prepareFinalClientRoot(locations);
|
|
86
86
|
installed = await installStablePackage({ packageClosure, sourceRelease, locations: packageLocations });
|
|
87
|
+
if (metadata.client === "codex") {
|
|
88
|
+
// The invoking updater can still be B255 code. Stop its old cleanup
|
|
89
|
+
// before switching the binding/cache to a release with process records.
|
|
90
|
+
const cleanup = await importStableCore(installed.packageRoot, "hellolynk-release-retention.mjs", "hellolynk_release_retention_unavailable");
|
|
91
|
+
await cleanup.protectRetentionReceipt({ releasesRoot: path.dirname(installed.packageRoot),
|
|
92
|
+
client: "codex", currentRelease: installed.packageRoot });
|
|
93
|
+
}
|
|
87
94
|
}
|
|
88
95
|
const activation = await inspectActivation(locations.hostRoot, metadata.profileId);
|
|
89
96
|
// Both public install commands use the actual account state. Native recovery
|
|
@@ -831,7 +838,7 @@ async function verifyRelease(releaseRoot, packageRoot, expectedMetadata = metada
|
|
|
831
838
|
return Object.freeze({ manifestSha256, outerManifestSha256 });
|
|
832
839
|
}
|
|
833
840
|
|
|
834
|
-
function resolveLocations() {
|
|
841
|
+
function resolveLocations(getDefaultHostRoot) {
|
|
835
842
|
if (process.platform !== "win32") fail("hellolynk_npm_windows_only");
|
|
836
843
|
const home = path.resolve(process.env.USERPROFILE || os.homedir());
|
|
837
844
|
const localAppData = path.resolve(process.env.LOCALAPPDATA || path.join(home, "AppData", "Local"));
|
|
@@ -841,7 +848,7 @@ function resolveLocations() {
|
|
|
841
848
|
const codexHome = path.resolve(configuredCodexHome || path.join(home, ".codex"));
|
|
842
849
|
const configuredHostRoot = String(process.env.HELLOLYNK_NEUTRAL_STORE_ROOT || "").trim();
|
|
843
850
|
if (configuredHostRoot && !path.isAbsolute(configuredHostRoot)) fail("hellolynk_npm_store_root_invalid");
|
|
844
|
-
const hostRoot = path.resolve(configuredHostRoot ||
|
|
851
|
+
const hostRoot = path.resolve(configuredHostRoot || getDefaultHostRoot());
|
|
845
852
|
if (configuredHostRoot && path.basename(hostRoot).toLowerCase() !== "host") {
|
|
846
853
|
fail("hellolynk_npm_store_root_invalid");
|
|
847
854
|
}
|
|
@@ -997,6 +1004,12 @@ async function startHostWhenAbsent(runtime, binding) {
|
|
|
997
1004
|
}
|
|
998
1005
|
|
|
999
1006
|
async function secureStableReleaseTree(locations, releaseId, options = {}) {
|
|
1007
|
+
const releasePath = path.resolve(locations.finalCurrentRoot, "releases", releaseId);
|
|
1008
|
+
// An existing AppData root can be shared while a new release is redirected
|
|
1009
|
+
// into one desktop app's private cache. Other clients cannot use that release.
|
|
1010
|
+
if (process.platform === "win32" && realpathSync.native(releasePath).toLowerCase() !== releasePath.toLowerCase()) {
|
|
1011
|
+
fail("client_root_alias_rejected");
|
|
1012
|
+
}
|
|
1000
1013
|
const module = await importCore(
|
|
1001
1014
|
"hellolynk-platform-credential-store.mjs",
|
|
1002
1015
|
"hellolynk_npm_client_root_security_unavailable",
|
|
@@ -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.102",
|
|
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": 142207,
|
|
21
|
+
"sha256": "95386fcd9092f31abb3b0441033b3b385b3c355506613da24b4557d5f9cac6ef"
|
|
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": 1713,
|
|
36
|
-
"sha256": "
|
|
36
|
+
"sha256": "a1dffd73cef661cd0f019f5a532f949693fccfa72222d346bd867470977cdc99"
|
|
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": "38d2a5204b66ec57368494e69ed71de655f798f9d37ab2af966b59c3ab07d0f2"
|
|
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": "befbca064334e8ff7f9d192f095ac1bd786a3882385a450fc2c2d2a5c9b2b7dd"
|
|
62
62
|
},
|
|
63
63
|
{
|
|
64
64
|
"path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs",
|
|
@@ -72,8 +72,8 @@
|
|
|
72
72
|
},
|
|
73
73
|
{
|
|
74
74
|
"path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs",
|
|
75
|
-
"bytes":
|
|
76
|
-
"sha256": "
|
|
75
|
+
"bytes": 9359,
|
|
76
|
+
"sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
|
|
77
77
|
},
|
|
78
78
|
{
|
|
79
79
|
"path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs",
|
|
@@ -92,8 +92,8 @@
|
|
|
92
92
|
},
|
|
93
93
|
{
|
|
94
94
|
"path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs",
|
|
95
|
-
"bytes":
|
|
96
|
-
"sha256": "
|
|
95
|
+
"bytes": 33183,
|
|
96
|
+
"sha256": "dfb6bba4c627082cd6fca8cb38dc786f905e38f3e83ccf91dbfe57e55639e8a2"
|
|
97
97
|
},
|
|
98
98
|
{
|
|
99
99
|
"path": "plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs",
|
|
@@ -132,8 +132,8 @@
|
|
|
132
132
|
},
|
|
133
133
|
{
|
|
134
134
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs",
|
|
135
|
-
"bytes":
|
|
136
|
-
"sha256": "
|
|
135
|
+
"bytes": 9359,
|
|
136
|
+
"sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
|
|
137
137
|
},
|
|
138
138
|
{
|
|
139
139
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs",
|
|
@@ -267,8 +267,8 @@
|
|
|
267
267
|
},
|
|
268
268
|
{
|
|
269
269
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs",
|
|
270
|
-
"bytes":
|
|
271
|
-
"sha256": "
|
|
270
|
+
"bytes": 19443,
|
|
271
|
+
"sha256": "f4bd81b3d03277e28955a62409383a0c4072c6212567f71425df4e18ece75d63"
|
|
272
272
|
},
|
|
273
273
|
{
|
|
274
274
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs",
|
|
@@ -307,8 +307,8 @@
|
|
|
307
307
|
},
|
|
308
308
|
{
|
|
309
309
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs",
|
|
310
|
-
"bytes":
|
|
311
|
-
"sha256": "
|
|
310
|
+
"bytes": 33183,
|
|
311
|
+
"sha256": "dfb6bba4c627082cd6fca8cb38dc786f905e38f3e83ccf91dbfe57e55639e8a2"
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs",
|
|
@@ -318,7 +318,7 @@
|
|
|
318
318
|
{
|
|
319
319
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs",
|
|
320
320
|
"bytes": 225161,
|
|
321
|
-
"sha256": "
|
|
321
|
+
"sha256": "e744bacf65f7b86cbcf7d654189c0e07e09401083e2b6f9053b17c1c3bacd1b4"
|
|
322
322
|
},
|
|
323
323
|
{
|
|
324
324
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs",
|
|
@@ -342,8 +342,8 @@
|
|
|
342
342
|
},
|
|
343
343
|
{
|
|
344
344
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs",
|
|
345
|
-
"bytes":
|
|
346
|
-
"sha256": "
|
|
345
|
+
"bytes": 20624,
|
|
346
|
+
"sha256": "9b02a42a510fa5ef4a251b6e675f9ccc60f20b718d17c1617027cd048c5634dd"
|
|
347
347
|
},
|
|
348
348
|
{
|
|
349
349
|
"path": "plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs",
|
|
@@ -378,12 +378,12 @@
|
|
|
378
378
|
{
|
|
379
379
|
"path": "plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json",
|
|
380
380
|
"bytes": 13440,
|
|
381
|
-
"sha256": "
|
|
381
|
+
"sha256": "3f8ad933949136ed1ebf5172c9e49ead0b0e2829650bc960eef1bdddd27c6a8f"
|
|
382
382
|
},
|
|
383
383
|
{
|
|
384
384
|
"path": "plugins/hellolynk-codex/runtime/neutral-host-release-pin.json",
|
|
385
385
|
"bytes": 146,
|
|
386
|
-
"sha256": "
|
|
386
|
+
"sha256": "6c9832284a05105014d72fe52f15e7d36c3c3fbbcb20aaf24f84a10196a7fad8"
|
|
387
387
|
},
|
|
388
388
|
{
|
|
389
389
|
"path": "plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1",
|
|
@@ -552,8 +552,8 @@
|
|
|
552
552
|
},
|
|
553
553
|
{
|
|
554
554
|
"path": "plugins/hellolynk-codex/server/codex-neutral-production.mjs",
|
|
555
|
-
"bytes":
|
|
556
|
-
"sha256": "
|
|
555
|
+
"bytes": 26960,
|
|
556
|
+
"sha256": "7b3249f1b35c331cf2b00ac2873a54d12fc6f61cf341bb82cec98e49ce221b92"
|
|
557
557
|
},
|
|
558
558
|
{
|
|
559
559
|
"path": "plugins/hellolynk-codex/server/mcp-proxy.mjs",
|
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.102",
|
|
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,4 +1,5 @@
|
|
|
1
1
|
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -19,12 +20,34 @@ const SAFE_SCOPE = /^[a-z][a-z0-9_.:-]{0,127}$/;
|
|
|
19
20
|
const MAX_PROFILE_BYTES = 64 * 1024;
|
|
20
21
|
|
|
21
22
|
export function defaultHelloLynkClientRoot(platform = process.platform, env = process.env) {
|
|
22
|
-
const home = resolve(String(env.HOME || env.USERPROFILE || homedir()));
|
|
23
|
-
if (platform === "win32")
|
|
23
|
+
const home = resolve(String((platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME || env.USERPROFILE) || homedir()));
|
|
24
|
+
if (platform === "win32") {
|
|
25
|
+
const current = join(home, ".hellolynk", "hellolynk-v2");
|
|
26
|
+
// Once selected, another client's older installer must not move us back.
|
|
27
|
+
if (hasWindowsClientInstallation(current)) return current;
|
|
28
|
+
const existing = resolve(String(env.LOCALAPPDATA || join(home, "AppData", "Local")), "HelloLynk");
|
|
29
|
+
// AppData created from a packaged desktop app can be private to that app.
|
|
30
|
+
// Keep an existing installation; an empty failed setup is not an account.
|
|
31
|
+
if (hasWindowsClientInstallation(existing)) return existing;
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
24
34
|
if (platform === "darwin") return join(home, "Library", "Application Support", "HelloLynk");
|
|
25
35
|
return resolve(String(env.XDG_DATA_HOME || join(home, ".local", "share")), "hellolynk");
|
|
26
36
|
}
|
|
27
37
|
|
|
38
|
+
function hasWindowsClientInstallation(root) {
|
|
39
|
+
for (const directory of ["profiles", "releases", "host", "credentials", "e2ee-private-keys"]) {
|
|
40
|
+
try {
|
|
41
|
+
const entries = readdirSync(join(root, directory), { withFileTypes: true });
|
|
42
|
+
if (entries.some((entry) => directory !== "host" || entry.name !== "runtime")) return true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// An unreadable existing location must not silently become a fresh account.
|
|
45
|
+
if (error.code !== "ENOENT") return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
function safeId(value, code) {
|
|
29
52
|
const normalized = String(value || "").trim();
|
|
30
53
|
if (!SAFE_ID.test(normalized)) throw new Error(code);
|
package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs
CHANGED
|
@@ -12,10 +12,9 @@ import {
|
|
|
12
12
|
} from "node:fs/promises";
|
|
13
13
|
import { basename, dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
15
|
import { performance } from "node:perf_hooks";
|
|
17
16
|
import { withLocalProcessLock } from "./hellolynk-local-process-lock.mjs";
|
|
18
|
-
import { readClientProfile } from "./hellolynk-client-profile.mjs";
|
|
17
|
+
import { defaultHelloLynkClientRoot, readClientProfile } from "./hellolynk-client-profile.mjs";
|
|
19
18
|
import { assertIdentifier } from "./hellolynk-host-auth.mjs";
|
|
20
19
|
import runtimeSafety from "./hellolynk-runtime-safety.cjs";
|
|
21
20
|
|
|
@@ -46,10 +45,7 @@ export function defaultNeutralHostStoreRoot(platform = process.platform, env = p
|
|
|
46
45
|
}
|
|
47
46
|
return root;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
if (platform === "win32") return resolve(String(env.LOCALAPPDATA || join(home, "AppData", "Local")), "HelloLynk", "host");
|
|
51
|
-
if (platform === "darwin") return join(home, "Library", "Application Support", "HelloLynk", "host");
|
|
52
|
-
return resolve(String(env.XDG_DATA_HOME || join(home, ".local", "share")), "hellolynk", "host");
|
|
48
|
+
return join(defaultHelloLynkClientRoot(platform, env), "host");
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
export function createNeutralHostInstallationStore(options = {}) {
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { lstat, mkdir, open, readFile, rename, rm, stat } from "node:fs/promises";
|
|
2
|
+
import { readdirSync } from "node:fs";
|
|
2
3
|
import { homedir } from "node:os";
|
|
3
4
|
import { basename, dirname, isAbsolute, join, resolve } from "node:path";
|
|
4
5
|
import { randomUUID } from "node:crypto";
|
|
@@ -19,12 +20,34 @@ const SAFE_SCOPE = /^[a-z][a-z0-9_.:-]{0,127}$/;
|
|
|
19
20
|
const MAX_PROFILE_BYTES = 64 * 1024;
|
|
20
21
|
|
|
21
22
|
export function defaultHelloLynkClientRoot(platform = process.platform, env = process.env) {
|
|
22
|
-
const home = resolve(String(env.HOME || env.USERPROFILE || homedir()));
|
|
23
|
-
if (platform === "win32")
|
|
23
|
+
const home = resolve(String((platform === "win32" ? env.USERPROFILE || env.HOME : env.HOME || env.USERPROFILE) || homedir()));
|
|
24
|
+
if (platform === "win32") {
|
|
25
|
+
const current = join(home, ".hellolynk", "hellolynk-v2");
|
|
26
|
+
// Once selected, another client's older installer must not move us back.
|
|
27
|
+
if (hasWindowsClientInstallation(current)) return current;
|
|
28
|
+
const existing = resolve(String(env.LOCALAPPDATA || join(home, "AppData", "Local")), "HelloLynk");
|
|
29
|
+
// AppData created from a packaged desktop app can be private to that app.
|
|
30
|
+
// Keep an existing installation; an empty failed setup is not an account.
|
|
31
|
+
if (hasWindowsClientInstallation(existing)) return existing;
|
|
32
|
+
return current;
|
|
33
|
+
}
|
|
24
34
|
if (platform === "darwin") return join(home, "Library", "Application Support", "HelloLynk");
|
|
25
35
|
return resolve(String(env.XDG_DATA_HOME || join(home, ".local", "share")), "hellolynk");
|
|
26
36
|
}
|
|
27
37
|
|
|
38
|
+
function hasWindowsClientInstallation(root) {
|
|
39
|
+
for (const directory of ["profiles", "releases", "host", "credentials", "e2ee-private-keys"]) {
|
|
40
|
+
try {
|
|
41
|
+
const entries = readdirSync(join(root, directory), { withFileTypes: true });
|
|
42
|
+
if (entries.some((entry) => directory !== "host" || entry.name !== "runtime")) return true;
|
|
43
|
+
} catch (error) {
|
|
44
|
+
// An unreadable existing location must not silently become a fresh account.
|
|
45
|
+
if (error.code !== "ENOENT") return true;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return false;
|
|
49
|
+
}
|
|
50
|
+
|
|
28
51
|
function safeId(value, code) {
|
|
29
52
|
const normalized = String(value || "").trim();
|
|
30
53
|
if (!SAFE_ID.test(normalized)) throw new Error(code);
|
|
@@ -18,7 +18,7 @@ const conversationGuidance = "HelloLynk 是本人授权的 AI 通讯。接着已
|
|
|
18
18
|
// What every client shows for the shared tools; nativeTool applies it.
|
|
19
19
|
const toolText = Object.freeze({
|
|
20
20
|
hellolynk_reply_authorized_message: { title: "回复来信", description: "接着当前对话的一封来信发送;不接手其他对话。" },
|
|
21
|
-
hellolynk_send_authorized_message: { title: "发起通信", description: "向本人授权的联系人发起新通信。contact 填对方名字;本人点名了对话时,targetConversationTitle
|
|
21
|
+
hellolynk_send_authorized_message: { title: "发起通信", description: "向本人授权的联系人发起新通信。contact 填对方名字;本人点名了对话时,targetConversationTitle 填完整标题。未指定对话的新通信默认送到对方的 HelloLynk 独立会话;要继续已有来往,用 hellolynk_reply_authorized_message 回复原信。" },
|
|
22
22
|
hellolynk_list_authorized_contacts: { title: "查找联系人", description: queryDescription },
|
|
23
23
|
hellolynk_claim_message: { title: "读取来信", description: "按自动提醒里的 batchId 一次读取整批来信(合计不超过 2 万字);手动读单条传 localMessageId 和 conversationKey。" },
|
|
24
24
|
hellolynk_list_pending_messages: { title: "待处理来信", description: "列出当前对话等待 AI 处理的来信,不含正文。" },
|
|
@@ -12,10 +12,9 @@ import {
|
|
|
12
12
|
} from "node:fs/promises";
|
|
13
13
|
import { basename, dirname, isAbsolute, join, parse, resolve } from "node:path";
|
|
14
14
|
import { spawn } from "node:child_process";
|
|
15
|
-
import { homedir } from "node:os";
|
|
16
15
|
import { performance } from "node:perf_hooks";
|
|
17
16
|
import { withLocalProcessLock } from "./hellolynk-local-process-lock.mjs";
|
|
18
|
-
import { readClientProfile } from "./hellolynk-client-profile.mjs";
|
|
17
|
+
import { defaultHelloLynkClientRoot, readClientProfile } from "./hellolynk-client-profile.mjs";
|
|
19
18
|
import { assertIdentifier } from "./hellolynk-host-auth.mjs";
|
|
20
19
|
import runtimeSafety from "./hellolynk-runtime-safety.cjs";
|
|
21
20
|
|
|
@@ -46,10 +45,7 @@ export function defaultNeutralHostStoreRoot(platform = process.platform, env = p
|
|
|
46
45
|
}
|
|
47
46
|
return root;
|
|
48
47
|
}
|
|
49
|
-
|
|
50
|
-
if (platform === "win32") return resolve(String(env.LOCALAPPDATA || join(home, "AppData", "Local")), "HelloLynk", "host");
|
|
51
|
-
if (platform === "darwin") return join(home, "Library", "Application Support", "HelloLynk", "host");
|
|
52
|
-
return resolve(String(env.XDG_DATA_HOME || join(home, ".local", "share")), "hellolynk", "host");
|
|
48
|
+
return join(defaultHelloLynkClientRoot(platform, env), "host");
|
|
53
49
|
}
|
|
54
50
|
|
|
55
51
|
export function createNeutralHostInstallationStore(options = {}) {
|
|
@@ -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 = 15;
|
|
38
38
|
|
|
39
39
|
const INSTALLATION_LOCK_CONTRACT = "hellolynk-neutral-host-installation-lock-v2";
|
|
40
40
|
const INSTALLATION_LOCK_MAX_BYTES = 4 * 1024;
|
|
@@ -15,12 +15,29 @@ const contract = 'hellolynk-release-retention-v1';
|
|
|
15
15
|
// receipt: an older worker that cleans up after an update deletes nothing.
|
|
16
16
|
const ENTRY = { 'claude-code': ['claude-code', 'hellolynk-claude-code.mjs'] };
|
|
17
17
|
const contractWithEntry = 'hellolynk-release-retention-v2';
|
|
18
|
+
// B255 workers understand v2 but do not read Codex process records.
|
|
19
|
+
const codexContract = 'hellolynk-release-retention-v3';
|
|
18
20
|
const PROCESS_CONTRACT = 'hellolynk-release-process-v1';
|
|
19
21
|
const START_TOLERANCE_MS = 15000;
|
|
20
22
|
export const releaseProcessDirectory = (releasesRoot, client) => path.join(releasesRoot, `.${client}-processes`);
|
|
21
23
|
const failure = code => Object.assign(new Error(code), { code });
|
|
22
24
|
const equal = (a, b) => process.platform === 'win32' ? a.toLowerCase() === b.toLowerCase() : a === b;
|
|
23
25
|
|
|
26
|
+
// A cache MCP keeps an installed prepare function in memory. Record its most
|
|
27
|
+
// recent successful binding, even when its command line names only the cache.
|
|
28
|
+
// A checkout/uninstalled fixture has no immutable release to retain.
|
|
29
|
+
export async function recordCodexReleaseProcess(packageRoot) {
|
|
30
|
+
if (process.platform !== 'win32' || !path.isAbsolute(packageRoot || '')
|
|
31
|
+
|| path.basename(path.dirname(packageRoot)) !== 'releases' || !/^codex-[a-f0-9]{64}$/.test(path.basename(packageRoot))) return;
|
|
32
|
+
try {
|
|
33
|
+
const root = await rootPath(path.dirname(packageRoot));
|
|
34
|
+
const folder = releaseProcessDirectory(root, 'codex');
|
|
35
|
+
await fs.mkdir(folder, { recursive: true });
|
|
36
|
+
await writeReceipt(path.join(folder, `${process.pid}.json`), { contract: PROCESS_CONTRACT, pid: process.pid,
|
|
37
|
+
startedAt: Math.round(performance.timeOrigin), release: path.basename(packageRoot) });
|
|
38
|
+
} catch { /* A missing record defers cleanup; it must not stop communication. */ }
|
|
39
|
+
}
|
|
40
|
+
|
|
24
41
|
async function directory(target) {
|
|
25
42
|
const stat = await fs.lstat(target);
|
|
26
43
|
if (!stat.isDirectory() || stat.isSymbolicLink()) throw failure('release_retention_path_invalid');
|
|
@@ -67,20 +84,25 @@ export async function runningReleaseNames(releasesRoot) {
|
|
|
67
84
|
return names;
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
// The given pids
|
|
71
|
-
//
|
|
87
|
+
// The given pids, this installation's stable entry, and Codex cache children:
|
|
88
|
+
// pid, start time and whether it runs the entry. Only processes
|
|
72
89
|
// with a positive pid are listed, and command lines never leave PowerShell.
|
|
73
90
|
export async function entryProcessList(client, releasesRoot, pids = []) {
|
|
74
|
-
if (!ENTRY[client]) return [];
|
|
91
|
+
if (!ENTRY[client] && client !== 'codex') return [];
|
|
75
92
|
if (process.platform !== 'win32') throw failure('release_retention_process_check_unavailable');
|
|
76
93
|
if (!Array.isArray(pids) || pids.some(pid => !Number.isSafeInteger(pid) || pid <= 0)) throw failure('release_retention_process_check_invalid');
|
|
77
94
|
const root = await rootPath(releasesRoot);
|
|
78
95
|
// This installation's entry only, in its given and canonical spellings:
|
|
79
96
|
// any case, either slash, quoted or not.
|
|
80
97
|
const literal = value => value.split(/[\\/]+/).map(part => part.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')).join('[\\\\/]+');
|
|
81
|
-
const entries = [...new Set([root, path.resolve(releasesRoot)])].map(value => literal(path.join(path.dirname(value), ...ENTRY[client])));
|
|
82
|
-
const pattern =
|
|
83
|
-
|
|
98
|
+
const entries = ENTRY[client] ? [...new Set([root, path.resolve(releasesRoot)])].map(value => literal(path.join(path.dirname(value), ...ENTRY[client]))) : [];
|
|
99
|
+
const pattern = (client === 'codex' ? '(?:^|["\\s\\\\/])(?:\\.[\\\\/])?server[\\\\/]mcp-proxy\\.mjs(?=["\\s]|$)'
|
|
100
|
+
: `(?<=^|["\\s])(?:${entries.join('|')})(?=["\\s]|$)`).replaceAll("'", "''");
|
|
101
|
+
// Relative cache entries do not reveal their installation. An older Codex
|
|
102
|
+
// child without a record conservatively delays this client's cleanup until
|
|
103
|
+
// it reconnects through a recording release or exits. Other Node apps do not.
|
|
104
|
+
const parentCheck = client === 'codex' ? "$_.Name -ieq 'node.exe' -and ($codexParents -contains $_.ParentProcessId) -and " : '';
|
|
105
|
+
const script = `$ErrorActionPreference='Stop'; $wanted=@(${pids.join(',')}); $epoch=[datetime]::new(1970,1,1,0,0,0,[DateTimeKind]::Utc); $all=@(Get-CimInstance Win32_Process); $codexParents=@($all | Where-Object { $_.Name -ieq 'Codex.exe' } | ForEach-Object { $_.ProcessId }); $list=@($all | Where-Object { $_.ProcessId -gt 0 } | ForEach-Object { $entry=[bool](${parentCheck}$_.CommandLine -and [regex]::IsMatch($_.CommandLine,'${pattern}','IgnoreCase')); if ($entry -or ($wanted -contains [int64]$_.ProcessId)) { [pscustomobject]@{ pid=[int64]$_.ProcessId; startedAt=$(if ($_.CreationDate) { [int64](($_.CreationDate.ToUniversalTime() - $epoch).TotalMilliseconds) } else { $null }); entry=$entry } } }); ConvertTo-Json -Compress -InputObject $list`;
|
|
84
106
|
const shell = path.join(process.env.SystemRoot || process.env.SYSTEMROOT || 'C:\\Windows', 'System32/WindowsPowerShell/v1.0/powershell.exe');
|
|
85
107
|
const { stdout } = await execute(shell, ['-NoLogo', '-NoProfile', '-NonInteractive', '-Command', script],
|
|
86
108
|
{ windowsHide: true, timeout: 15000, maxBuffer: 1024 * 1024 });
|
|
@@ -95,7 +117,7 @@ export async function entryProcessList(client, releasesRoot, pids = []) {
|
|
|
95
117
|
// Records of exited or reused pids are removed, so a release can go at the
|
|
96
118
|
// next install once its last process is gone.
|
|
97
119
|
async function entryReleases(releasesRoot, root, client, pattern, dependencies) {
|
|
98
|
-
if (!ENTRY[client]) return { names: [], unregistered: false };
|
|
120
|
+
if (!ENTRY[client] && client !== 'codex') return { names: [], unregistered: false };
|
|
99
121
|
const folder = releaseProcessDirectory(root, client);
|
|
100
122
|
let items = [];
|
|
101
123
|
try {
|
|
@@ -179,21 +201,20 @@ export async function pruneInstalledReleases({ releasesRoot, client, currentRele
|
|
|
179
201
|
return result;
|
|
180
202
|
}
|
|
181
203
|
|
|
182
|
-
//
|
|
183
|
-
//
|
|
184
|
-
//
|
|
185
|
-
//
|
|
186
|
-
// v2, keeping the adoption it records, with whether it wrote. Throws when that
|
|
204
|
+
// Call before switching the binding/entry. An earlier updater cleans up after
|
|
205
|
+
// setup with its own code. Claude needs v2 to stop pre-B255 workers; Codex needs
|
|
206
|
+
// v3 to stop B255 workers that know only Claude records. Preserve the recorded
|
|
207
|
+
// adoption order and resolve with whether the receipt changed. Throw when that
|
|
187
208
|
// cannot be confirmed, even for a cause that passes before the worker reads the
|
|
188
209
|
// receipt (a file another process holds open): the caller then switches nothing.
|
|
189
210
|
// currentRelease, the release the install starts now, is recorded only when
|
|
190
211
|
// there is no receipt yet.
|
|
191
212
|
export async function protectRetentionReceipt({ releasesRoot, client, currentRelease }) {
|
|
192
|
-
if (!ENTRY[client]) throw failure('release_retention_client_invalid');
|
|
213
|
+
if (!ENTRY[client] && client !== 'codex') throw failure('release_retention_client_invalid');
|
|
193
214
|
const root = await rootPath(releasesRoot), pattern = new RegExp(`^${client}-[a-f0-9]{64}$`);
|
|
194
215
|
const receiptPath = path.join(root, `.${client}-retention.json`);
|
|
195
216
|
const old = await readReceipt(receiptPath, client, pattern);
|
|
196
|
-
if (old?.contract ===
|
|
217
|
+
if (old?.contract === receiptContract(client)) return false;
|
|
197
218
|
let current = old?.current;
|
|
198
219
|
if (!current) {
|
|
199
220
|
if (typeof currentRelease !== 'string' || !path.isAbsolute(currentRelease) || !pattern.test(path.basename(currentRelease))
|
|
@@ -201,11 +222,11 @@ export async function protectRetentionReceipt({ releasesRoot, client, currentRel
|
|
|
201
222
|
current = path.basename(currentRelease);
|
|
202
223
|
await directory(path.join(root, current));
|
|
203
224
|
}
|
|
204
|
-
await writeReceipt(receiptPath, { contract:
|
|
225
|
+
await writeReceipt(receiptPath, { contract: receiptContract(client), client, current, previous: old ? old.previous : null });
|
|
205
226
|
return true;
|
|
206
227
|
}
|
|
207
228
|
|
|
208
|
-
const receiptContract = client => ENTRY[client] ? contractWithEntry : contract;
|
|
229
|
+
const receiptContract = client => client === 'codex' ? codexContract : ENTRY[client] ? contractWithEntry : contract;
|
|
209
230
|
|
|
210
231
|
async function readReceipt(receiptPath, client, pattern) {
|
|
211
232
|
let old = null;
|
|
@@ -213,7 +234,7 @@ async function readReceipt(receiptPath, client, pattern) {
|
|
|
213
234
|
const stat = await fs.lstat(receiptPath);
|
|
214
235
|
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1 || stat.size > 4096) throw failure('release_retention_receipt_invalid');
|
|
215
236
|
old = JSON.parse(await fs.readFile(receiptPath, 'utf8'));
|
|
216
|
-
if (![contract, contractWithEntry].includes(old?.contract) || old.client !== client || !pattern.test(old.current)
|
|
237
|
+
if (![contract, contractWithEntry, ...(client === 'codex' ? [codexContract] : [])].includes(old?.contract) || old.client !== client || !pattern.test(old.current)
|
|
217
238
|
|| old.previous !== null && !pattern.test(old.previous) || old.current === old.previous) throw failure('release_retention_receipt_invalid');
|
|
218
239
|
} catch (error) {
|
|
219
240
|
if (error.code === 'ENOENT') return null;
|
|
@@ -245,7 +266,7 @@ export async function removeInstalledReleases({ releasesRoot, client }, dependen
|
|
|
245
266
|
if (!result.deferred.length) {
|
|
246
267
|
await fs.rm(path.join(root, `.${client}-retention.json`), { force: true });
|
|
247
268
|
// entryReleases checked above that this is a folder in the releases root.
|
|
248
|
-
if (ENTRY[client]) await fs.rm(releaseProcessDirectory(root, client), { recursive: true, force: true });
|
|
269
|
+
if (ENTRY[client] || client === 'codex') await fs.rm(releaseProcessDirectory(root, client), { recursive: true, force: true });
|
|
249
270
|
}
|
|
250
271
|
result.complete = result.deferred.length === 0;
|
|
251
272
|
} catch (error) { result.error = safeCode(error); }
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
"files": [
|
|
5
5
|
{
|
|
6
6
|
"path": "release/package.json",
|
|
7
|
-
"sha256": "
|
|
7
|
+
"sha256": "befbca064334e8ff7f9d192f095ac1bd786a3882385a450fc2c2d2a5c9b2b7dd"
|
|
8
8
|
},
|
|
9
9
|
{
|
|
10
10
|
"path": "neutral-host-bootstrap.mjs",
|
|
@@ -36,7 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
{
|
|
38
38
|
"path": "core/hellolynk-client-profile.mjs",
|
|
39
|
-
"sha256": "
|
|
39
|
+
"sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
|
|
40
40
|
},
|
|
41
41
|
{
|
|
42
42
|
"path": "core/hellolynk-client-update-entry.mjs",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
},
|
|
53
53
|
{
|
|
54
54
|
"path": "core/hellolynk-release-retention.mjs",
|
|
55
|
-
"sha256": "
|
|
55
|
+
"sha256": "9b02a42a510fa5ef4a251b6e675f9ccc60f20b718d17c1617027cd048c5634dd"
|
|
56
56
|
},
|
|
57
57
|
{
|
|
58
58
|
"path": "core/hellolynk-contact-request-e2ee.mjs",
|
|
@@ -168,7 +168,7 @@
|
|
|
168
168
|
},
|
|
169
169
|
{
|
|
170
170
|
"path": "core/hellolynk-native-addressing-tools.cjs",
|
|
171
|
-
"sha256": "
|
|
171
|
+
"sha256": "f4bd81b3d03277e28955a62409383a0c4072c6212567f71425df4e18ece75d63"
|
|
172
172
|
},
|
|
173
173
|
{
|
|
174
174
|
"path": "core/hellolynk-native-human-confirmation-broker.mjs",
|
|
@@ -180,7 +180,7 @@
|
|
|
180
180
|
},
|
|
181
181
|
{
|
|
182
182
|
"path": "core/hellolynk-neutral-host-installation-store.mjs",
|
|
183
|
-
"sha256": "
|
|
183
|
+
"sha256": "dfb6bba4c627082cd6fca8cb38dc786f905e38f3e83ccf91dbfe57e55639e8a2"
|
|
184
184
|
},
|
|
185
185
|
{
|
|
186
186
|
"path": "core/hellolynk-neutral-host-launcher.mjs",
|
|
@@ -188,7 +188,7 @@
|
|
|
188
188
|
},
|
|
189
189
|
{
|
|
190
190
|
"path": "core/hellolynk-neutral-host-process.mjs",
|
|
191
|
-
"sha256": "
|
|
191
|
+
"sha256": "e744bacf65f7b86cbcf7d654189c0e07e09401083e2b6f9053b17c1c3bacd1b4"
|
|
192
192
|
},
|
|
193
193
|
{
|
|
194
194
|
"path": "core/hellolynk-neutral-host-store-adapters.mjs",
|
|
@@ -312,7 +312,7 @@
|
|
|
312
312
|
},
|
|
313
313
|
{
|
|
314
314
|
"path": "bootstrap/core/hellolynk-client-profile.mjs",
|
|
315
|
-
"sha256": "
|
|
315
|
+
"sha256": "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"
|
|
316
316
|
},
|
|
317
317
|
{
|
|
318
318
|
"path": "bootstrap/core/hellolynk-host-auth.mjs",
|
|
@@ -328,7 +328,7 @@
|
|
|
328
328
|
},
|
|
329
329
|
{
|
|
330
330
|
"path": "bootstrap/core/hellolynk-neutral-host-installation-store.mjs",
|
|
331
|
-
"sha256": "
|
|
331
|
+
"sha256": "dfb6bba4c627082cd6fca8cb38dc786f905e38f3e83ccf91dbfe57e55639e8a2"
|
|
332
332
|
},
|
|
333
333
|
{
|
|
334
334
|
"path": "bootstrap/core/hellolynk-platform-credential-store.mjs",
|
|
@@ -344,7 +344,7 @@
|
|
|
344
344
|
},
|
|
345
345
|
{
|
|
346
346
|
"path": "release/server/codex-neutral-production.mjs",
|
|
347
|
-
"sha256": "
|
|
347
|
+
"sha256": "7b3249f1b35c331cf2b00ac2873a54d12fc6f61cf341bb82cec98e49ce221b92"
|
|
348
348
|
},
|
|
349
349
|
{
|
|
350
350
|
"path": "release/server/neutral-host-adapter.mjs",
|
|
@@ -7,6 +7,7 @@ import { discoverNeutralHost } from "./neutral-host-adapter.mjs";
|
|
|
7
7
|
import { launchPinnedNeutralHostBootstrap, verifyPinnedNeutralHostRuntime } from "../runtime/core/hellolynk-neutral-host-launcher.mjs";
|
|
8
8
|
import { resolveClientAccountRoot } from "../runtime/core/hellolynk-client-account.mjs";
|
|
9
9
|
import { startInstalledClientUpdate } from "../runtime/core/hellolynk-client-update-entry.mjs";
|
|
10
|
+
import { recordCodexReleaseProcess } from "../runtime/core/hellolynk-release-retention.mjs";
|
|
10
11
|
|
|
11
12
|
const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
12
13
|
const PLUGIN_ROOT = path.dirname(MODULE_DIR);
|
|
@@ -23,9 +24,9 @@ const TRUSTED_BOOTSTRAP_FILES = Object.freeze(new Map([
|
|
|
23
24
|
["runtime/neutral-host-bootstrap.mjs", "53513deed7f0155a6603a1489d1609fae37776442358181ff7650d0cd9351f0a"],
|
|
24
25
|
["runtime/bootstrap/core/hellolynk-runtime-safety.cjs", "43d576d9f774409f4a735793c1503a6f397e787b10c59a4ac9cc9257299f5efe"],
|
|
25
26
|
["runtime/bootstrap/core/hellolynk-host-protected-store.mjs", "89b6e6c4ce9c6ba2984f8c301de4251da6004bdd402cc44d578a4505c21b75b1"],
|
|
26
|
-
["runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs", "
|
|
27
|
+
["runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs", "dfb6bba4c627082cd6fca8cb38dc786f905e38f3e83ccf91dbfe57e55639e8a2"],
|
|
27
28
|
["runtime/bootstrap/core/hellolynk-host-auth.mjs", "db1ec56f4e60e6450fba50188eecf89748f39f5d7a75c2456c30a6053233555d"],
|
|
28
|
-
["runtime/bootstrap/core/hellolynk-client-profile.mjs", "
|
|
29
|
+
["runtime/bootstrap/core/hellolynk-client-profile.mjs", "8be37831c96610e3d0a64848ada86488970cc6079e49764b47d8a8b2ba852269"],
|
|
29
30
|
["runtime/bootstrap/core/hellolynk-platform-credential-store.mjs", "f27f5f377cc4d6d2b5664212cf8fcc4c98d910064c1677ff9c1f776df1a59dba"],
|
|
30
31
|
["runtime/bootstrap/core/hellolynk-client-core.mjs", "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"],
|
|
31
32
|
["runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1", "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"],
|
|
@@ -216,6 +217,7 @@ async function prepareSelectedRelease(options, verifiedInitial = null) {
|
|
|
216
217
|
assertReleaseBinding(binding, inspection);
|
|
217
218
|
const release = await loader.activateNeutralHostRelease(inspection);
|
|
218
219
|
if (!release || typeof release.run !== "function") throw codeError("neutral_codex_runner_integrity_invalid");
|
|
220
|
+
await recordCodexReleaseProcess(path.resolve(releaseRoot, "../.."));
|
|
219
221
|
return Object.freeze({ release, inspection, binding, initialBindingFingerprint,
|
|
220
222
|
releaseRoot, storeRoot, installationStore, protectedStore,
|
|
221
223
|
runnerBinding: (root) => currentRunnerBinding(storeRoot, root, binding, initialBindingFingerprint) });
|