@hellolynk-ai/codex-plugin 0.0.0-stage → 0.2.100

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.
Files changed (123) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/LICENSE +92 -0
  3. package/README.md +101 -2
  4. package/bin/hellolynk-client-setup.mjs +2748 -0
  5. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  6. package/hellolynk-client-pack-manifest.json +619 -0
  7. package/hellolynk-client-pack-pin.json +4 -0
  8. package/package.json +58 -4
  9. package/plugins/hellolynk-codex/.app.json +3 -0
  10. package/plugins/hellolynk-codex/.codex-plugin/plugin.json +38 -0
  11. package/plugins/hellolynk-codex/.mcp.json +19 -0
  12. package/plugins/hellolynk-codex/assets/hellolynk-mark.png +0 -0
  13. package/plugins/hellolynk-codex/package.json +11 -0
  14. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  15. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  16. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  17. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  18. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  19. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  20. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  21. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  22. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  23. package/plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  24. package/plugins/hellolynk-codex/runtime/connect-activate.mjs +837 -0
  25. package/plugins/hellolynk-codex/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  26. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +180 -0
  27. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs +770 -0
  28. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +191 -0
  29. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  30. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  31. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +453 -0
  32. package/plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  33. package/plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  34. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  35. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  36. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-auth.mjs +347 -0
  37. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  38. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  39. package/plugins/hellolynk-codex/runtime/core/hellolynk-host.mjs +2374 -0
  40. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  41. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  42. package/plugins/hellolynk-codex/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  43. package/plugins/hellolynk-codex/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  44. package/plugins/hellolynk-codex/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  45. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  46. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  47. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  48. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  49. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  50. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  51. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  52. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  53. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-inbox.mjs +230 -0
  54. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-notices.cjs +60 -0
  55. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  56. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs +280 -0
  57. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  58. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  59. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-notice.mjs +50 -0
  60. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  61. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  62. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  63. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  64. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  65. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  66. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  67. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  68. package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  69. package/plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  70. package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +277 -0
  71. package/plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  72. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  73. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  74. package/plugins/hellolynk-codex/runtime/neutral-host-bootstrap.mjs +64 -0
  75. package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +70 -0
  76. package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +156 -0
  77. package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +358 -0
  78. package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +4 -0
  79. package/plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1 +281 -0
  80. package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +844 -0
  81. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  82. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  83. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  84. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  85. package/plugins/hellolynk-codex/runtime/workbench/candidate-bootstrap.mjs +54 -0
  86. package/plugins/hellolynk-codex/runtime/workbench/contract.mjs +1314 -0
  87. package/plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json +215 -0
  88. package/plugins/hellolynk-codex/runtime/workbench/host-capabilities.mjs +80 -0
  89. package/plugins/hellolynk-codex/runtime/workbench/i18n.mjs +1127 -0
  90. package/plugins/hellolynk-codex/runtime/workbench/loopback-surface.mjs +740 -0
  91. package/plugins/hellolynk-codex/runtime/workbench/mcp-app-bundle.mjs +97 -0
  92. package/plugins/hellolynk-codex/runtime/workbench/service.mjs +485 -0
  93. package/plugins/hellolynk-codex/runtime/workbench/state.mjs +421 -0
  94. package/plugins/hellolynk-codex/runtime/workbench/transports.mjs +171 -0
  95. package/plugins/hellolynk-codex/runtime/workbench/ui/app.mjs +6120 -0
  96. package/plugins/hellolynk-codex/runtime/workbench/ui/connection-form.mjs +155 -0
  97. package/plugins/hellolynk-codex/runtime/workbench/ui/hellolynk-mark.png +0 -0
  98. package/plugins/hellolynk-codex/runtime/workbench/ui/http-transport.mjs +247 -0
  99. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  100. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/close.svg +3 -0
  101. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/contacts.svg +3 -0
  102. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/inbox.svg +3 -0
  103. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/refresh.svg +3 -0
  104. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/search.svg +3 -0
  105. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/security.svg +3 -0
  106. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/settings.svg +3 -0
  107. package/plugins/hellolynk-codex/runtime/workbench/ui/index.html +139 -0
  108. package/plugins/hellolynk-codex/runtime/workbench/ui/loopback-entry.mjs +26 -0
  109. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  110. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  111. package/plugins/hellolynk-codex/runtime/workbench/ui/styles.css +686 -0
  112. package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +520 -0
  113. package/plugins/hellolynk-codex/server/mcp-proxy.mjs +306 -0
  114. package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -0
  115. package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -0
  116. package/plugins/hellolynk-codex/server/native-reply-routes.mjs +32 -0
  117. package/plugins/hellolynk-codex/server/native-task-client.mjs +382 -0
  118. package/plugins/hellolynk-codex/server/native-task-runtime.mjs +8 -0
  119. package/plugins/hellolynk-codex/server/neutral-host-adapter.mjs +649 -0
  120. package/plugins/hellolynk-codex/server/workbench-runtime.mjs +180 -0
  121. package/plugins/hellolynk-codex/skills/hellolynk-open/SKILL.md +11 -0
  122. package/plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml +4 -0
  123. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
@@ -0,0 +1,837 @@
1
+ #!/usr/bin/env node
2
+
3
+ import assert from "node:assert/strict";
4
+ import crypto from "node:crypto";
5
+ import fs from "node:fs/promises";
6
+ import path from "node:path";
7
+
8
+ import { readClientProfile } from "../plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs";
9
+ import { createNeutralHostInstallationStore } from "../plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs";
10
+ import { clientAccountBaseRoot } from "../plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs";
11
+ import { createDeviceEnrollmentClient } from "../plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs";
12
+ import { verifyPinnedNeutralHostRuntime } from "../plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs";
13
+ import {
14
+ createHostProtectedStore,
15
+ HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES,
16
+ HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES,
17
+ HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES,
18
+ HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES,
19
+ HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES,
20
+ HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES,
21
+ HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES,
22
+ verifyHostProtectedStorePath,
23
+ } from "../plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs";
24
+
25
+ const ADAPTER_IDS = Object.freeze(["codex", "cursor", "dsh", "openclaw", "trae-work", "workbuddy", "claude-code"]);
26
+
27
+ const args = parseArgs(process.argv.slice(2));
28
+ const requestedClient = requiredClient(args.client);
29
+ const preflightOnly = optionalBoolean(args.preflight, "activation_preflight_invalid");
30
+ const preBindOnly = optionalBoolean(args["pre-bind"], "activation_pre_bind_invalid");
31
+ if (preBindOnly && !preflightOnly) throw new Error("activation_pre_bind_invalid");
32
+ const finalCurrentRoot = requiredPersistentRoot(
33
+ args["final-current-root"],
34
+ "activation_final_current_root_invalid",
35
+ );
36
+ const root = requiredAbsolute(args.root, "activation_root_invalid");
37
+ const releaseCurrentRoot = args["release-current-root"]
38
+ ? requiredPersistentRoot(args["release-current-root"], "activation_release_root_invalid") : finalCurrentRoot;
39
+ if (releaseCurrentRoot !== finalCurrentRoot && path.join(releaseCurrentRoot, "host") !== clientAccountBaseRoot(root)) {
40
+ throw new Error("activation_persistent_binding_path_invalid");
41
+ }
42
+ const releaseRoot = requiredAbsolute(args["release-root"], "activation_release_root_invalid");
43
+ const expectedReleaseManifestSha256 = requiredSha256(
44
+ args["expected-release-manifest-sha256"],
45
+ "activation_expected_release_manifest_sha256_invalid",
46
+ );
47
+ const profileManifestPath = requiredAbsolute(
48
+ args["profile-manifest"],
49
+ "activation_profile_manifest_invalid",
50
+ );
51
+ if (path.resolve(root) !== path.join(finalCurrentRoot, "host")
52
+ || !isStrictDescendant(releaseRoot, path.join(releaseCurrentRoot, "releases"))
53
+ || path.resolve(profileManifestPath) !== path.join(finalCurrentRoot, "host", "profile-manifest-v1.json")
54
+ || [root, releaseRoot, profileManifestPath].some(hasForbiddenPersistentSegment)) {
55
+ throw new Error("activation_persistent_binding_path_invalid");
56
+ }
57
+ const runtimeRoot = path.join(root, "runtime");
58
+
59
+ let profileManifest = null;
60
+ try { profileManifest = JSON.parse(await fs.readFile(profileManifestPath, "utf8")); }
61
+ catch (error) {
62
+ if (!(preBindOnly && error?.code === "ENOENT")) throw error;
63
+ }
64
+ if (profileManifest) validateProfileManifest(profileManifest);
65
+
66
+ const releaseManifestPath = path.join(releaseRoot, "runtime", "neutral-host-release-manifest.json");
67
+ const releasePinPath = path.join(releaseRoot, "runtime", "neutral-host-release-pin.json");
68
+ const runnerRelativePath = "runtime/neutral-host-bootstrap.mjs";
69
+ await fs.access(path.join(releaseRoot, ...runnerRelativePath.split("/")));
70
+ const releaseManifestSha256 = crypto.createHash("sha256")
71
+ .update(await fs.readFile(releaseManifestPath))
72
+ .digest("hex");
73
+ const releasePin = JSON.parse(await fs.readFile(releasePinPath, "utf8"));
74
+ assert.deepEqual(Object.keys(releasePin).sort(), ["contract", "manifestSha256"]);
75
+ assert.equal(releasePin.contract, "hellolynk-neutral-host-release-pin-v1");
76
+ assert.equal(releasePin.manifestSha256, releaseManifestSha256);
77
+ assert.equal(releaseManifestSha256, expectedReleaseManifestSha256);
78
+
79
+ const installations = createNeutralHostInstallationStore(preflightOnly ? {
80
+ root,
81
+ readOnly: true,
82
+ verifyPath: ({ path: targetPath, directory }) => verifyHostProtectedStorePath({
83
+ root,
84
+ path: targetPath,
85
+ directory,
86
+ }),
87
+ } : { root });
88
+ const installationStatus = await installations.status();
89
+ const active = installationStatus.installations.filter((item) => item.state === "active");
90
+ const unclaimed = installationStatus.installations.filter((item) => item.state === "unclaimed");
91
+ const freshPreBind = preBindOnly && active.length === 0 && unclaimed.length === 1
92
+ && installationStatus.installations.length === 1;
93
+ if (!freshPreBind) assert.equal(active.length, 1);
94
+ const installation = freshPreBind ? unclaimed[0] : active[0];
95
+ const store = createHostProtectedStore({ root, installationStore: installations, readOnly: preflightOnly });
96
+ let recoveryPreBind = false;
97
+ if (preBindOnly && !freshPreBind && (!profileManifest || installation.delegateBindings.length !== profileManifest.profiles.length)) {
98
+ const recorded = new Set((profileManifest?.profiles || []).map((file) => path.basename(file, ".json")));
99
+ const missing = installation.delegateBindings.filter((item) => !recorded.has(item.profileId));
100
+ if (missing.length !== 1 || missing[0].adapters?.length !== 1 || missing[0].adapters[0] !== requestedClient
101
+ || recorded.size !== installation.delegateBindings.length - 1) throw new Error("activation_profile_manifest_invalid");
102
+ const recovery = await createDeviceEnrollmentClient({ root: finalCurrentRoot, hostRoot: root, baseUrl: "https://hellolynk.com" }, {
103
+ protectedStore: store, installationStore: installations,
104
+ }).verifyLocalRecovery({ clientType: requestedClient, profileId: missing[0].profileId });
105
+ // Complete only the read-only projection. The ordinary enrollment retry
106
+ // validates live authorization and publishes the real manifest afterwards.
107
+ profileManifest = { contract: "hellolynk-neutral-host-profile-manifest-v1",
108
+ profiles: [...(profileManifest?.profiles || []), recovery.configPath] };
109
+ validateProfileManifest(profileManifest);
110
+ recoveryPreBind = true;
111
+ }
112
+ if (freshPreBind && profileManifest !== null) throw new Error("activation_profile_manifest_invalid");
113
+ if (!freshPreBind && profileManifest === null) throw new Error("activation_profile_manifest_invalid");
114
+ profileManifest ||= Object.freeze({
115
+ contract: "hellolynk-neutral-host-profile-manifest-v1",
116
+ profiles: Object.freeze([]),
117
+ });
118
+ assert.equal(installation.delegateBindings.length, profileManifest.profiles.length);
119
+ const existingAdapters = installation.delegateBindings.map((binding) => binding.adapters?.[0]);
120
+ const profileIdsByAdapter = freshPreBind ? Object.freeze({}) : resolveAdapterProfileIds(
121
+ profileManifest,
122
+ installation,
123
+ preflightOnly ? existingAdapters : [requestedClient],
124
+ );
125
+ if (preflightOnly && !freshPreBind) await verifyManifestProfiles(profileManifest, installation);
126
+
127
+ const targetProfileId = profileIdsByAdapter[requestedClient] || null;
128
+ const verificationAdapter = targetProfileId ? requestedClient : (existingAdapters[0] || requestedClient);
129
+ const verificationDelegate = freshPreBind ? Object.freeze({
130
+ region: "prebind",
131
+ principalId: "prebind-principal",
132
+ delegateId: "prebind-delegate",
133
+ hostInstallationId: installation.hostInstallationId,
134
+ profileId: `prebind-${requestedClient}`,
135
+ }) : await installations.resolveBinding({
136
+ profileId: profileIdsByAdapter[verificationAdapter],
137
+ });
138
+ const verificationScopes = {
139
+ codex: HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES,
140
+ cursor: HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES,
141
+ dsh: HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES,
142
+ openclaw: HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES,
143
+ "trae-work": HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES,
144
+ workbuddy: HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES,
145
+ "claude-code": HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES,
146
+ }[verificationAdapter];
147
+ const releaseVerification = await verifyPinnedNeutralHostRuntime({
148
+ runtimeRoot,
149
+ releaseRoot,
150
+ releaseManifestSha256,
151
+ runnerRelativePath,
152
+ profileManifestPath,
153
+ region: verificationDelegate.region,
154
+ principalId: verificationDelegate.principalId,
155
+ delegateId: verificationDelegate.delegateId,
156
+ hostInstallationId: verificationDelegate.hostInstallationId,
157
+ profileId: verificationDelegate.profileId,
158
+ adapterId: verificationAdapter,
159
+ version: 2,
160
+ scopes: verificationScopes,
161
+ }, { storeRoot: root });
162
+ assert.equal(releaseVerification.verifiedFileCount >= 8, true);
163
+
164
+ let transport = null;
165
+ let bootstrapTransportRequired = false;
166
+ let protectedStorePresent = true;
167
+ try { await fs.lstat(path.join(root, "capabilities-v1.dpapi.clixml")); }
168
+ catch (error) {
169
+ if (error?.code !== "ENOENT") throw error;
170
+ protectedStorePresent = false;
171
+ }
172
+ if (!protectedStorePresent) {
173
+ if (!(freshPreBind || recoveryPreBind)) await store.status();
174
+ await verifyHostProtectedStorePath({ root, path: root, directory: true });
175
+ bootstrapTransportRequired = true;
176
+ } else {
177
+ assert.equal((await store.status()).protected, true);
178
+ try { transport = preflightOnly ? await store.verifyTransport() : await store.resolveTransport(); }
179
+ catch (error) {
180
+ if (!(freshPreBind || recoveryPreBind) || String(error?.code || error?.message) !== "host_transport_credential_required") throw error;
181
+ bootstrapTransportRequired = true;
182
+ }
183
+ }
184
+ if (transport) {
185
+ assert.equal(transport.credential.hostInstallationId, installation.hostInstallationId);
186
+ assert.equal(transport.credential.principalId, installation.principalId);
187
+ }
188
+
189
+ const specs = [
190
+ {
191
+ adapterId: "codex",
192
+ profileId: profileIdsByAdapter.codex,
193
+ scopes: HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES,
194
+ resolve: () => store.resolveCodexNeutralBinding(),
195
+ verify: () => store.verifyCodexNeutralBinding(),
196
+ save: (input) => store.saveCodexNeutralBinding({
197
+ ...input,
198
+ runtimeRoot,
199
+ releaseRoot,
200
+ releaseManifestSha256,
201
+ runnerRelativePath,
202
+ profileManifestPath,
203
+ }),
204
+ replace: (previousBinding, replacementBinding) => store.replaceCodexNeutralBinding({
205
+ previousBinding,
206
+ replacementBinding: {
207
+ ...replacementBinding,
208
+ runtimeRoot,
209
+ releaseRoot,
210
+ releaseManifestSha256,
211
+ runnerRelativePath,
212
+ profileManifestPath,
213
+ },
214
+ }),
215
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
216
+ store.migrateCodexNeutralBinding({
217
+ previousBinding,
218
+ replacementBinding,
219
+ previousCapabilityId,
220
+ })
221
+ ),
222
+ current: (binding) => (
223
+ hasExactScopes(binding, HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES)
224
+ && path.resolve(binding.releaseRoot) === releaseRoot
225
+ && binding.releaseManifestSha256 === releaseManifestSha256
226
+ && binding.runnerRelativePath === runnerRelativePath
227
+ && path.resolve(binding.profileManifestPath) === profileManifestPath
228
+ ),
229
+ upgrade: (input) => ({
230
+ ...input,
231
+ runtimeRoot,
232
+ releaseRoot,
233
+ releaseManifestSha256,
234
+ runnerRelativePath,
235
+ profileManifestPath,
236
+ }),
237
+ migrateScopes: true,
238
+ },
239
+ {
240
+ adapterId: "openclaw",
241
+ profileId: profileIdsByAdapter.openclaw,
242
+ scopes: HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES,
243
+ resolve: () => store.resolveOpenClawNeutralBinding({ profileId: profileIdsByAdapter.openclaw }),
244
+ verify: () => store.verifyOpenClawNeutralBinding({ profileId: profileIdsByAdapter.openclaw }),
245
+ save: (input) => store.saveOpenClawNeutralBinding({
246
+ ...input,
247
+ runtimeRoot,
248
+ releaseRoot,
249
+ releaseManifestSha256,
250
+ runnerRelativePath,
251
+ profileManifestPath,
252
+ }),
253
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
254
+ store.migrateOpenClawNeutralBinding({
255
+ previousBinding,
256
+ replacementBinding,
257
+ previousCapabilityId,
258
+ })
259
+ ),
260
+ current: (binding) => (
261
+ hasExactScopes(binding, HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES)
262
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
263
+ && binding.releaseManifestSha256 === releaseManifestSha256
264
+ && binding.runnerRelativePath === runnerRelativePath
265
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath
266
+ ),
267
+ upgrade: (input) => ({
268
+ ...input,
269
+ runtimeRoot,
270
+ releaseRoot,
271
+ releaseManifestSha256,
272
+ runnerRelativePath,
273
+ profileManifestPath,
274
+ }),
275
+ migrateScopes: true,
276
+ },
277
+ {
278
+ adapterId: "trae-work",
279
+ profileId: profileIdsByAdapter["trae-work"],
280
+ scopes: HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES,
281
+ resolve: () => store.resolveTraeWorkNeutralBinding({ profileId: profileIdsByAdapter["trae-work"] }),
282
+ verify: () => store.verifyTraeWorkNeutralBinding({ profileId: profileIdsByAdapter["trae-work"] }),
283
+ save: (input) => store.saveTraeWorkNeutralBinding({
284
+ ...input,
285
+ runtimeRoot,
286
+ releaseRoot,
287
+ releaseManifestSha256,
288
+ runnerRelativePath,
289
+ profileManifestPath,
290
+ }),
291
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
292
+ store.migrateTraeWorkNeutralBinding({
293
+ previousBinding,
294
+ replacementBinding,
295
+ previousCapabilityId,
296
+ })
297
+ ),
298
+ current: (binding) => (
299
+ hasExactScopes(binding, HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES)
300
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
301
+ && binding.releaseManifestSha256 === releaseManifestSha256
302
+ && binding.runnerRelativePath === runnerRelativePath
303
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath
304
+ ),
305
+ upgrade: (input) => ({
306
+ ...input,
307
+ runtimeRoot,
308
+ releaseRoot,
309
+ releaseManifestSha256,
310
+ runnerRelativePath,
311
+ profileManifestPath,
312
+ }),
313
+ migrateScopes: true,
314
+ },
315
+ {
316
+ adapterId: "workbuddy",
317
+ profileId: profileIdsByAdapter.workbuddy,
318
+ scopes: HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES,
319
+ resolve: () => store.resolveWorkBuddyNeutralBinding({ profileId: profileIdsByAdapter.workbuddy }),
320
+ verify: () => store.verifyWorkBuddyNeutralBinding({ profileId: profileIdsByAdapter.workbuddy }),
321
+ save: (input) => store.saveWorkBuddyNeutralBinding({
322
+ ...input,
323
+ runtimeRoot,
324
+ releaseRoot,
325
+ releaseManifestSha256,
326
+ runnerRelativePath,
327
+ profileManifestPath,
328
+ }),
329
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
330
+ store.migrateWorkBuddyNeutralBinding({
331
+ previousBinding,
332
+ replacementBinding,
333
+ previousCapabilityId,
334
+ })
335
+ ),
336
+ current: (binding) => (
337
+ hasExactScopes(binding, HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES)
338
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
339
+ && binding.releaseManifestSha256 === releaseManifestSha256
340
+ && binding.runnerRelativePath === runnerRelativePath
341
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath
342
+ ),
343
+ upgrade: (input) => ({
344
+ ...input,
345
+ runtimeRoot,
346
+ releaseRoot,
347
+ releaseManifestSha256,
348
+ runnerRelativePath,
349
+ profileManifestPath,
350
+ }),
351
+ migrateScopes: true,
352
+ },
353
+ {
354
+ adapterId: "claude-code",
355
+ profileId: profileIdsByAdapter["claude-code"],
356
+ scopes: HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES,
357
+ resolve: () => store.resolveClaudeCodeNeutralBinding({ profileId: profileIdsByAdapter["claude-code"] }),
358
+ verify: () => store.verifyClaudeCodeNeutralBinding({ profileId: profileIdsByAdapter["claude-code"] }),
359
+ save: (input) => store.saveClaudeCodeNeutralBinding({
360
+ ...input,
361
+ runtimeRoot,
362
+ releaseRoot,
363
+ releaseManifestSha256,
364
+ runnerRelativePath,
365
+ profileManifestPath,
366
+ }),
367
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
368
+ store.migrateClaudeCodeNeutralBinding({
369
+ previousBinding,
370
+ replacementBinding,
371
+ previousCapabilityId,
372
+ })
373
+ ),
374
+ current: (binding) => (
375
+ hasExactScopes(binding, HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES)
376
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
377
+ && binding.releaseManifestSha256 === releaseManifestSha256
378
+ && binding.runnerRelativePath === runnerRelativePath
379
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath
380
+ ),
381
+ upgrade: (input) => ({
382
+ ...input,
383
+ runtimeRoot,
384
+ releaseRoot,
385
+ releaseManifestSha256,
386
+ runnerRelativePath,
387
+ profileManifestPath,
388
+ }),
389
+ // Claude has no legacy scope set; use the existing atomic binding migration
390
+ // path for a pinned release change, preserving the same exact scopes.
391
+ migrateScopes: true,
392
+ },
393
+ {
394
+ adapterId: "cursor",
395
+ profileId: profileIdsByAdapter.cursor,
396
+ scopes: HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES,
397
+ resolve: () => store.resolveCursorNeutralBinding({ profileId: profileIdsByAdapter.cursor }),
398
+ verify: () => store.verifyCursorNeutralBinding({ profileId: profileIdsByAdapter.cursor }),
399
+ save: (input) => store.saveCursorNeutralBinding({
400
+ ...input,
401
+ runtimeRoot,
402
+ releaseRoot,
403
+ releaseManifestSha256,
404
+ runnerRelativePath,
405
+ profileManifestPath,
406
+ }),
407
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => (
408
+ store.migrateCursorNeutralBinding({
409
+ previousBinding,
410
+ replacementBinding,
411
+ previousCapabilityId,
412
+ })
413
+ ),
414
+ current: (binding) => (
415
+ hasExactScopes(binding, HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES)
416
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
417
+ && binding.releaseManifestSha256 === releaseManifestSha256
418
+ && binding.runnerRelativePath === runnerRelativePath
419
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath
420
+ ),
421
+ upgrade: (input) => ({
422
+ ...input,
423
+ runtimeRoot,
424
+ releaseRoot,
425
+ releaseManifestSha256,
426
+ runnerRelativePath,
427
+ profileManifestPath,
428
+ }),
429
+ migrateScopes: true,
430
+ },
431
+ {
432
+ adapterId: "dsh",
433
+ profileId: profileIdsByAdapter.dsh,
434
+ scopes: HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES,
435
+ resolve: () => store.resolveDshNeutralBinding({ profileId: profileIdsByAdapter.dsh }),
436
+ verify: () => store.verifyDshNeutralBinding({ profileId: profileIdsByAdapter.dsh }),
437
+ save: (input) => store.saveDshNeutralBinding({ ...input, runtimeRoot, releaseRoot, releaseManifestSha256, runnerRelativePath, profileManifestPath }),
438
+ migrate: (previousBinding, replacementBinding, previousCapabilityId) => store.migrateDshNeutralBinding({ previousBinding, replacementBinding, previousCapabilityId }),
439
+ current: (binding) => hasExactScopes(binding, HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES)
440
+ && path.resolve(binding.releaseRoot || ".") === releaseRoot
441
+ && binding.releaseManifestSha256 === releaseManifestSha256
442
+ && binding.runnerRelativePath === runnerRelativePath
443
+ && path.resolve(binding.profileManifestPath || ".") === profileManifestPath,
444
+ upgrade: (input) => ({ ...input, runtimeRoot, releaseRoot, releaseManifestSha256, runnerRelativePath, profileManifestPath }),
445
+ migrateScopes: true,
446
+ },
447
+ ];
448
+
449
+ const results = [];
450
+ for (const spec of specs.filter((item) => item.adapterId === requestedClient)) {
451
+ if (preflightOnly) {
452
+ results.push(await preflightBinding(spec, targetProfileId));
453
+ continue;
454
+ }
455
+ let existing = null;
456
+ try {
457
+ existing = await spec.resolve();
458
+ } catch (error) {
459
+ if (!isRecoverableMissingBinding(error)) throw error;
460
+ }
461
+ if (existing) {
462
+ if (!spec.current || spec.current(existing)) {
463
+ results.push(publicResult(existing, true));
464
+ continue;
465
+ }
466
+ if (hasExactScopes(existing, spec.scopes) && typeof spec.replace === "function") {
467
+ const priorCapabilityId = existing.capability.capabilityId;
468
+ const priorBinding = codexBindingInput(existing);
469
+ const updated = await spec.replace(priorBinding, priorBinding);
470
+ const resolved = await spec.resolve();
471
+ assert.equal(updated.releaseManifestSha256, releaseManifestSha256);
472
+ assert.equal(resolved.capability.capabilityId, priorCapabilityId);
473
+ results.push(publicResult(resolved, true, true));
474
+ continue;
475
+ }
476
+ if (spec.migrateScopes === true) {
477
+ results.push(await migrateScopedBinding({ store, installations, installation, spec, existing }));
478
+ continue;
479
+ }
480
+ const priorCapabilityId = existing.capability.capabilityId;
481
+ const priorBinding = codexBindingInput(existing);
482
+ const updated = await spec.replace(priorBinding, priorBinding);
483
+ const resolved = await spec.resolve();
484
+ assert.equal(updated.releaseManifestSha256, releaseManifestSha256);
485
+ assert.equal(resolved.capability.capabilityId, priorCapabilityId);
486
+ results.push(publicResult(resolved, true, true));
487
+ continue;
488
+ }
489
+ const delegate = await installations.resolveBinding({ profileId: spec.profileId });
490
+ const capability = await store.issue({
491
+ region: delegate.region,
492
+ principalId: delegate.principalId,
493
+ delegateId: delegate.delegateId,
494
+ hostInstallationId: delegate.hostInstallationId,
495
+ adapterId: spec.adapterId,
496
+ version: 2,
497
+ scopes: spec.scopes,
498
+ });
499
+ try {
500
+ await spec.save({
501
+ profileId: spec.profileId,
502
+ delegateId: delegate.delegateId,
503
+ adapterId: spec.adapterId,
504
+ version: 2,
505
+ scopes: spec.scopes,
506
+ });
507
+ const resolved = await spec.resolve();
508
+ assert.equal(resolved.capability.capabilityId, capability.capabilityId);
509
+ results.push(publicResult(resolved, false));
510
+ } catch (error) {
511
+ await store.revoke(capability.capabilityId).catch(() => {});
512
+ throw error;
513
+ }
514
+ }
515
+
516
+ process.stdout.write(`${JSON.stringify({
517
+ ok: true,
518
+ preflight: preflightOnly,
519
+ preBind: preBindOnly,
520
+ readOnly: preflightOnly,
521
+ protected: true,
522
+ installationState: installation.state,
523
+ profileManifestState: freshPreBind ? "deferred" : recoveryPreBind ? "recovery_pending" : "verified",
524
+ bootstrapTransportRequired,
525
+ transportCredentialState: bootstrapTransportRequired
526
+ ? "bootstrap_required" : (preflightOnly ? "active" : transport.credential.state),
527
+ releaseManifestSha256,
528
+ verifiedReleaseFileCount: releaseVerification.verifiedFileCount,
529
+ clients: results,
530
+ }, null, 2)}\n`);
531
+
532
+ async function preflightBinding(spec, targetProfile) {
533
+ if (!targetProfile) {
534
+ return Object.freeze({
535
+ adapterId: spec.adapterId,
536
+ profileId: null,
537
+ delegatePresent: false,
538
+ bindingPresent: false,
539
+ bindingState: "awaiting_delegate_binding",
540
+ plannedMutation: "deferred",
541
+ bindingUpdated: false,
542
+ });
543
+ }
544
+ let existing = null;
545
+ try {
546
+ if (protectedStorePresent) existing = await spec.verify();
547
+ } catch (error) {
548
+ if (!isRecoverableMissingBinding(error)) throw error;
549
+ }
550
+ if (!existing) {
551
+ return Object.freeze({
552
+ adapterId: spec.adapterId,
553
+ profileId: targetProfile,
554
+ delegatePresent: true,
555
+ bindingPresent: false,
556
+ bindingState: "missing",
557
+ plannedMutation: "issue_and_save",
558
+ bindingUpdated: false,
559
+ });
560
+ }
561
+ const current = !spec.current || spec.current(existing);
562
+ const plannedMutation = current
563
+ ? "none"
564
+ : hasExactScopes(existing, spec.scopes) && typeof spec.replace === "function"
565
+ ? "replace"
566
+ : spec.migrateScopes === true ? "migrate" : "replace";
567
+ return Object.freeze({
568
+ ...publicResult(existing, true),
569
+ delegatePresent: true,
570
+ bindingPresent: true,
571
+ bindingState: current ? "current" : `${plannedMutation}_required`,
572
+ plannedMutation,
573
+ bindingUpdated: false,
574
+ });
575
+ }
576
+
577
+ function validateProfileManifest(manifest) {
578
+ if (!manifest || typeof manifest !== "object" || Array.isArray(manifest)
579
+ || Object.keys(manifest).sort().join(",") !== "contract,profiles"
580
+ || manifest.contract !== "hellolynk-neutral-host-profile-manifest-v1"
581
+ || !Array.isArray(manifest.profiles)
582
+ || manifest.profiles.length < 1 || manifest.profiles.length > ADAPTER_IDS.length) {
583
+ throw new Error("activation_profile_manifest_invalid");
584
+ }
585
+ for (const profileFile of manifest.profiles) {
586
+ const absolute = requiredAbsolute(profileFile, "activation_profile_manifest_invalid");
587
+ if (!isStrictDescendant(absolute, path.join(finalCurrentRoot, "profiles"))
588
+ || hasForbiddenPersistentSegment(absolute)) {
589
+ throw new Error("activation_profile_manifest_invalid");
590
+ }
591
+ }
592
+ }
593
+
594
+ async function verifyManifestProfiles(manifest, installationValue) {
595
+ for (const profileFile of manifest.profiles) {
596
+ const profile = await readClientProfile(profileFile);
597
+ const profileId = path.basename(profileFile, ".json");
598
+ const delegate = installationValue.delegateBindings.find((item) => item.profileId === profileId);
599
+ if (profile.contract !== "hellolynk-client-profile-v1"
600
+ || profile.schemaVersion !== 2
601
+ || profile.profileId !== profileId
602
+ || !delegate
603
+ || profile.region !== installationValue.region
604
+ || profile.principalId !== installationValue.principalId
605
+ || profile.hostId !== installationValue.hostInstallationId
606
+ || profile.delegateId !== delegate.delegateId
607
+ || profile.authorizationVersion !== delegate.authorizationVersion) {
608
+ throw new Error("activation_profile_binding_invalid");
609
+ }
610
+ }
611
+ }
612
+
613
+ function publicResult(binding, reused, bindingUpdated = false) {
614
+ return {
615
+ adapterId: binding.adapterId,
616
+ profileId: binding.profileId,
617
+ capabilityState: binding.capability.state,
618
+ version: binding.version,
619
+ scopeCount: binding.scopes.length,
620
+ reused,
621
+ bindingUpdated,
622
+ };
623
+ }
624
+
625
+ async function migrateScopedBinding({ store, installations, installation, spec, existing }) {
626
+ const delegate = await installations.resolveBinding({ profileId: spec.profileId });
627
+ assert.equal(delegate.region, installation.region);
628
+ assert.equal(delegate.principalId, installation.principalId);
629
+ assert.equal(delegate.hostInstallationId, installation.hostInstallationId);
630
+ const priorBinding = neutralBindingInput(existing);
631
+ const replacementBinding = Object.freeze(spec.upgrade ? spec.upgrade({
632
+ ...priorBinding,
633
+ scopes: [...spec.scopes],
634
+ runtimeRoot,
635
+ }) : {
636
+ ...priorBinding,
637
+ scopes: [...spec.scopes],
638
+ runtimeRoot,
639
+ });
640
+ const migrated = await spec.migrate(
641
+ priorBinding,
642
+ replacementBinding,
643
+ existing.capability.capabilityId,
644
+ );
645
+ const resolved = await spec.resolve();
646
+ assert.equal(resolved.capability.capabilityId, migrated.capability.capabilityId);
647
+ assert.notEqual(resolved.capability.capabilityId, existing.capability.capabilityId);
648
+ return {
649
+ ...publicResult(resolved, false, true),
650
+ capabilityMigrated: true,
651
+ };
652
+ }
653
+
654
+ function neutralBindingInput(binding) {
655
+ return {
656
+ contract: binding.contract,
657
+ region: binding.region,
658
+ principalId: binding.principalId,
659
+ delegateId: binding.delegateId,
660
+ hostInstallationId: binding.hostInstallationId,
661
+ profileId: binding.profileId,
662
+ adapterId: binding.adapterId,
663
+ version: binding.version,
664
+ scopes: [...binding.scopes],
665
+ runtimeRoot: binding.runtimeRoot,
666
+ ...(binding.releaseRoot ? {
667
+ releaseRoot: binding.releaseRoot,
668
+ releaseManifestSha256: binding.releaseManifestSha256,
669
+ runnerRelativePath: binding.runnerRelativePath,
670
+ profileManifestPath: binding.profileManifestPath,
671
+ } : {}),
672
+ };
673
+ }
674
+
675
+ function hasExactScopes(binding, expected) {
676
+ return binding.scopes.length === expected.length
677
+ && binding.scopes.every((scope, index) => scope === expected[index]);
678
+ }
679
+
680
+ function codexBindingInput(binding) {
681
+ return {
682
+ contract: binding.contract,
683
+ region: binding.region,
684
+ principalId: binding.principalId,
685
+ delegateId: binding.delegateId,
686
+ hostInstallationId: binding.hostInstallationId,
687
+ profileId: binding.profileId,
688
+ adapterId: binding.adapterId,
689
+ version: binding.version,
690
+ scopes: [...binding.scopes],
691
+ runtimeRoot: binding.runtimeRoot,
692
+ releaseRoot: binding.releaseRoot,
693
+ releaseManifestSha256: binding.releaseManifestSha256,
694
+ runnerRelativePath: binding.runnerRelativePath,
695
+ profileManifestPath: binding.profileManifestPath,
696
+ };
697
+ }
698
+
699
+ function isRecoverableMissingBinding(error) {
700
+ return [
701
+ "neutral_codex_binding_configuration_required",
702
+ "neutral_codex_capability_missing",
703
+ "neutral_openclaw_binding_configuration_required",
704
+ "neutral_openclaw_capability_missing",
705
+ "neutral_trae_work_binding_configuration_required",
706
+ "neutral_trae_work_capability_missing",
707
+ "neutral_workbuddy_binding_configuration_required",
708
+ "neutral_workbuddy_capability_missing",
709
+ "neutral_claude_code_binding_configuration_required",
710
+ "neutral_claude_code_capability_missing",
711
+ "neutral_cursor_binding_configuration_required",
712
+ "neutral_cursor_capability_missing",
713
+ "neutral_dsh_binding_configuration_required",
714
+ "neutral_dsh_capability_missing",
715
+ ].includes(error?.code || error?.message);
716
+ }
717
+
718
+ function parseArgs(argv) {
719
+ const options = {};
720
+ for (let index = 0; index < argv.length; index += 2) {
721
+ const key = argv[index];
722
+ const value = argv[index + 1];
723
+ if (!key?.startsWith("--") || value == null) throw new Error("activation_arguments_invalid");
724
+ const name = key.slice(2);
725
+ if (!new Set([
726
+ "root", "release-root", "expected-release-manifest-sha256", "profile-manifest", "client",
727
+ "final-current-root", "release-current-root", "preflight", "pre-bind",
728
+ ]).has(name)
729
+ || Object.hasOwn(options, name)) throw new Error("activation_arguments_invalid");
730
+ options[name] = value;
731
+ }
732
+ return options;
733
+ }
734
+
735
+ function optionalBoolean(value, code) {
736
+ if (value == null) return false;
737
+ if (!new Set(["false", "true"]).has(value)) throw new Error(code);
738
+ return value === "true";
739
+ }
740
+
741
+ function requiredClient(value) {
742
+ const client = String(value || "");
743
+ if (!ADAPTER_IDS.includes(client)) throw new Error("activation_client_invalid");
744
+ return client;
745
+ }
746
+
747
+ function requiredAbsolute(value, code) {
748
+ const text = String(value || "");
749
+ if (!path.isAbsolute(text) || path.resolve(text) !== text) throw new Error(code);
750
+ return text;
751
+ }
752
+
753
+ function requiredSha256(value, code) {
754
+ const text = String(value || "");
755
+ if (!/^[a-f0-9]{64}$/.test(text)) throw new Error(code);
756
+ return text;
757
+ }
758
+
759
+ function requiredPersistentRoot(value, code) {
760
+ const rootValue = requiredAbsolute(value, code);
761
+ if (hasForbiddenPersistentSegment(rootValue)
762
+ || /(?:^|[-_.])stage(?:$|[-_.])/iu.test(path.basename(rootValue))) {
763
+ throw new Error(code);
764
+ }
765
+ return rootValue;
766
+ }
767
+
768
+ function hasForbiddenPersistentSegment(value) {
769
+ const normalized = path.resolve(String(value || "")).replaceAll("\\", "/").toLowerCase();
770
+ return normalized.includes("/.codex/worktrees/")
771
+ || normalized.endsWith("/.codex/worktrees")
772
+ || /(?:^|\/)[^/]*hellolynk[^/]*stage[^/]*(?:\/|$)/u.test(normalized);
773
+ }
774
+
775
+ function isStrictDescendant(value, parent) {
776
+ const relative = path.relative(path.resolve(parent), path.resolve(value));
777
+ return relative !== "" && !relative.startsWith("..") && !path.isAbsolute(relative);
778
+ }
779
+
780
+ export function resolveAdapterProfileIds(
781
+ profileManifestValue,
782
+ installationValue,
783
+ requestedAdapters = ADAPTER_IDS,
784
+ ) {
785
+ if (!Array.isArray(requestedAdapters) || requestedAdapters.length < 1
786
+ || requestedAdapters.length > ADAPTER_IDS.length
787
+ || new Set(requestedAdapters).size !== requestedAdapters.length
788
+ || requestedAdapters.some((adapterId) => !ADAPTER_IDS.includes(adapterId))) {
789
+ throw new Error("activation_client_invalid");
790
+ }
791
+ const profileFiles = profileManifestValue?.profiles;
792
+ if (!Array.isArray(profileFiles) || profileFiles.length < 1
793
+ || profileFiles.length > ADAPTER_IDS.length) {
794
+ throw new Error("activation_profile_manifest_invalid");
795
+ }
796
+ const manifestProfileIds = new Set(profileFiles.map((profileFile) => {
797
+ const profileId = path.basename(String(profileFile || ""), ".json");
798
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.-]{2,127}$/u.test(profileId)
799
+ || path.basename(String(profileFile || "")) !== `${profileId}.json`) {
800
+ throw new Error("activation_profile_manifest_invalid");
801
+ }
802
+ return profileId;
803
+ }));
804
+ if (manifestProfileIds.size !== profileFiles.length) {
805
+ throw new Error("activation_profile_manifest_invalid");
806
+ }
807
+ const delegateBindings = installationValue?.delegateBindings;
808
+ if (!Array.isArray(delegateBindings) || delegateBindings.length !== manifestProfileIds.size) {
809
+ throw new Error("activation_profile_binding_invalid");
810
+ }
811
+ const bindingProfileIds = new Set(delegateBindings.map((binding) => binding?.profileId));
812
+ if (bindingProfileIds.size !== delegateBindings.length
813
+ || bindingProfileIds.size !== manifestProfileIds.size
814
+ || [...manifestProfileIds].some((profileId) => !bindingProfileIds.has(profileId))) {
815
+ throw new Error("activation_profile_binding_invalid");
816
+ }
817
+ const boundAdapters = new Set();
818
+ for (const binding of delegateBindings) {
819
+ if (!Array.isArray(binding.adapters) || binding.adapters.length !== 1
820
+ || !ADAPTER_IDS.includes(binding.adapters[0]) || boundAdapters.has(binding.adapters[0])) {
821
+ throw new Error("activation_profile_binding_invalid");
822
+ }
823
+ boundAdapters.add(binding.adapters[0]);
824
+ }
825
+ const mapping = Object.fromEntries(requestedAdapters.map((adapterId) => {
826
+ const matches = delegateBindings.filter(
827
+ (binding) => Array.isArray(binding.adapters) && binding.adapters.includes(adapterId)
828
+ && manifestProfileIds.has(binding.profileId),
829
+ );
830
+ if (matches.length !== 1) throw new Error(`activation_${adapterId}_profile_binding_invalid`);
831
+ return [adapterId, matches[0].profileId];
832
+ }));
833
+ if (new Set(Object.values(mapping)).size !== requestedAdapters.length) {
834
+ throw new Error("activation_profile_binding_invalid");
835
+ }
836
+ return Object.freeze(mapping);
837
+ }