@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,520 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { fileURLToPath, pathToFileURL } from "node:url";
5
+
6
+ import { discoverNeutralHost } from "./neutral-host-adapter.mjs";
7
+ import { launchPinnedNeutralHostBootstrap, verifyPinnedNeutralHostRuntime } from "../runtime/core/hellolynk-neutral-host-launcher.mjs";
8
+ import { resolveClientAccountRoot } from "../runtime/core/hellolynk-client-account.mjs";
9
+ import { startInstalledClientUpdate } from "../runtime/core/hellolynk-client-update-entry.mjs";
10
+
11
+ const MODULE_DIR = path.dirname(fileURLToPath(import.meta.url));
12
+ const PLUGIN_ROOT = path.dirname(MODULE_DIR);
13
+ const BOOTSTRAP = path.join(PLUGIN_ROOT, "runtime", "neutral-host-bootstrap.mjs");
14
+ const RELEASE_LOADER = path.join(PLUGIN_ROOT, "runtime", "neutral-host-release-loader.mjs");
15
+ const LOCK_WAIT_MS = 10_000;
16
+ const DISCOVERY_WAIT_MS = 75_000;
17
+ const MAX_DISCOVERY_WAIT_MS = 120_000;
18
+ let installedPrepare = null;
19
+ const TRUSTED_BOOTSTRAP_FILES = Object.freeze(new Map([
20
+ ["runtime/bootstrap/core/hellolynk-local-process-lock.mjs", "5212990a98b9ad2eb83fa2d5fb8a123d687c5ef6daf6cba49a4b6d04a7fe251e"],
21
+ ["runtime/bootstrap/core/hellolynk-client-account.mjs", "ab7ec5dda92e510e7deb14f900fd2df4c6c3373fbeb4af4fb2b6c1a72aa41a99"],
22
+ ["runtime/neutral-host-release-loader.mjs", "15593e237fd5cbd765b65e60efe7a1c87e24a5390ad932c8a23d2c03b8da9770"],
23
+ ["runtime/neutral-host-bootstrap.mjs", "53513deed7f0155a6603a1489d1609fae37776442358181ff7650d0cd9351f0a"],
24
+ ["runtime/bootstrap/core/hellolynk-runtime-safety.cjs", "43d576d9f774409f4a735793c1503a6f397e787b10c59a4ac9cc9257299f5efe"],
25
+ ["runtime/bootstrap/core/hellolynk-host-protected-store.mjs", "89b6e6c4ce9c6ba2984f8c301de4251da6004bdd402cc44d578a4505c21b75b1"],
26
+ ["runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs", "b5f6288732af6f9b9c0c750f6002eae657dde9513fc97674f6e59200f3cc9dc6"],
27
+ ["runtime/bootstrap/core/hellolynk-host-auth.mjs", "db1ec56f4e60e6450fba50188eecf89748f39f5d7a75c2456c30a6053233555d"],
28
+ ["runtime/bootstrap/core/hellolynk-client-profile.mjs", "4b45a78b4d6c4794a1f494bd95777579c32c56a68321a1d3b484048942ae4742"],
29
+ ["runtime/bootstrap/core/hellolynk-platform-credential-store.mjs", "f27f5f377cc4d6d2b5664212cf8fcc4c98d910064c1677ff9c1f776df1a59dba"],
30
+ ["runtime/bootstrap/core/hellolynk-client-core.mjs", "94bbad1d0ccee969bbffed176449e427188f1b892512a64167ab31f63d258e2b"],
31
+ ["runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1", "24357e8ce54090b4ec68e457f02c9e834a80f3273ef3c03a8c0fa7258797fb49"],
32
+ ]));
33
+
34
+ // A still-open cache may load a different installed release during an update.
35
+ // Share initialization objects only when both use the same pinned bootstrap.
36
+ export const CODEX_INITIAL_BINDING_HANDOFF = crypto.createHash("sha256")
37
+ .update(JSON.stringify([...TRUSTED_BOOTSTRAP_FILES])).digest("hex");
38
+
39
+ export async function createCodexNeutralProduction(options = {}, preparedRelease = null) {
40
+ const env = options.env || process.env;
41
+ // The cache delegates to the pinned release that prepared this object.
42
+ // Keep that one initialization alive instead of resolving it a second time.
43
+ if (preparedRelease && path.resolve(preparedRelease.releaseRoot) !== path.resolve(PLUGIN_ROOT)) {
44
+ preparedRelease.binding.capability?.secretBuffer?.fill(0);
45
+ preparedRelease = null;
46
+ }
47
+ const prepared = preparedRelease || await prepareCodexNeutralRelease({ ...options, env }, true);
48
+ const { inspection, releaseRoot, storeRoot, protectedStore, initialBindingFingerprint } = prepared;
49
+ try {
50
+ if (!options.releaseLoaderPath && path.resolve(releaseRoot) !== path.resolve(PLUGIN_ROOT)) {
51
+ const production = await import(pathToFileURL(path.join(releaseRoot, "server/codex-neutral-production.mjs")).href);
52
+ const share = production.CODEX_INITIAL_BINDING_HANDOFF === CODEX_INITIAL_BINDING_HANDOFF;
53
+ if (!share) prepared.binding.capability?.secretBuffer?.fill(0);
54
+ return await production.createCodexNeutralProduction({ ...options,
55
+ releaseLoaderPath: path.join(releaseRoot, "runtime/neutral-host-release-loader.mjs"), storeRoot }, share ? prepared : null);
56
+ }
57
+ // The protected binding alone selects the release; the updater only installs in the background.
58
+ if (!options.clientUpdateReady) {
59
+ await startInstalledClientUpdate({ client: "codex", packageRoot: path.resolve(releaseRoot, "../.."), storeRoot });
60
+ }
61
+ const discover = options.discoverHost || discoverNeutralHost;
62
+ const spawnRunner = options.spawnRunner;
63
+ let initialBinding = prepared.binding;
64
+ let launchBinding = null;
65
+ return Object.freeze({
66
+ capabilityProvider: async () => {
67
+ // Consume before awaiting: two concurrent callers must not share the
68
+ // one-time handoff. Reconnects always resolve current credentials.
69
+ const initial = initialBinding;
70
+ initialBinding = null;
71
+ let resolved; let fingerprint;
72
+ try {
73
+ const current = initial && initialBindingFingerprint
74
+ ? await initializationFingerprint(storeRoot, initial) : null;
75
+ if (current && JSON.stringify(current) === JSON.stringify(initialBindingFingerprint)) {
76
+ resolved = initial;
77
+ fingerprint = current;
78
+ } else {
79
+ initial?.capability?.secretBuffer?.fill(0);
80
+ const latest = await resolveInitialBinding(protectedStore, storeRoot);
81
+ resolved = latest.binding;
82
+ fingerprint = latest.fingerprint;
83
+ }
84
+ if (path.resolve(resolved.releaseRoot) !== releaseRoot
85
+ || resolved.releaseManifestSha256 !== inspection.manifestSha256
86
+ || resolved.runnerRelativePath !== "runtime/neutral-host-bootstrap.mjs") {
87
+ throw codeError("neutral_codex_runner_integrity_invalid");
88
+ }
89
+ const { capability, ...publicLaunch } = resolved;
90
+ launchBinding = { binding: publicLaunch, fingerprint };
91
+ return Object.freeze({
92
+ region: resolved.region,
93
+ principalId: resolved.principalId,
94
+ delegateId: resolved.delegateId,
95
+ hostInstallationId: resolved.hostInstallationId,
96
+ adapterId: "codex",
97
+ runtimeRoot: resolved.runtimeRoot,
98
+ capability,
99
+ });
100
+ } catch (error) {
101
+ initial?.capability?.secretBuffer?.fill(0);
102
+ resolved?.capability?.secretBuffer?.fill(0);
103
+ throw error;
104
+ }
105
+ },
106
+ hostLauncher: async (publicBinding) => {
107
+ const verifiedLaunch = launchBinding;
108
+ launchBinding = null;
109
+ return launchCodexNeutralHost({
110
+ publicBinding,
111
+ verifiedLaunch,
112
+ storeRoot,
113
+ protectedStore,
114
+ discover,
115
+ spawnRunner,
116
+ bootstrapPath: options.bootstrapPath || path.join(releaseRoot, "runtime", "neutral-host-bootstrap.mjs"),
117
+ wait: options.wait,
118
+ launchTimeoutMs: options.launchTimeoutMs,
119
+ env,
120
+ });
121
+ },
122
+ });
123
+ } catch (error) {
124
+ prepared.binding.capability?.secretBuffer?.fill(0);
125
+ throw error;
126
+ }
127
+ }
128
+
129
+ export async function prepareCodexNeutralRelease(options = {}, finalBindingDeferred = false, verifiedInitial = null) {
130
+ const env = options.env || process.env;
131
+ if (options.releaseLoaderPath) {
132
+ const prepared = await prepareSelectedRelease({ ...options, env, loaderPath: path.resolve(options.releaseLoaderPath) }, verifiedInitial);
133
+ if (!finalBindingDeferred) prepared.binding.capability?.secretBuffer?.fill(0);
134
+ return prepared;
135
+ }
136
+ // A warmed conversation can outlive Codex's disposable plugin cache. Keep
137
+ // only the verified installed entry point; that entry still resolves the
138
+ // current account, protected binding and release pin on every reconnect.
139
+ if (installedPrepare) return installedPrepare({ ...options, env }, finalBindingDeferred);
140
+
141
+ // Codex executes the MCP proxy from its disposable plugin cache, while the
142
+ // protected binding deliberately points at an immutable HelloLynk release. Use
143
+ // the cache only as a pinned bootstrap to discover that bound release; the
144
+ // release itself remains the authority for runtime activation.
145
+ const bootstrapLoaderPath = path.resolve(RELEASE_LOADER);
146
+ const bootstrapReleaseRoot = path.dirname(path.dirname(bootstrapLoaderPath));
147
+ const bootstrapTrusted = await loadTrustedBootstrap({
148
+ loaderPath: bootstrapLoaderPath,
149
+ releaseRoot: bootstrapReleaseRoot,
150
+ });
151
+ const storeRoot = resolveClientAccountRoot("codex", absolutePath(
152
+ options.storeRoot || env.HELLOLYNK_NEUTRAL_STORE_ROOT || bootstrapTrusted.defaultStoreRoot("win32", env),
153
+ "neutral_codex_binding_configuration_required",
154
+ ));
155
+ const bootstrapInstallationStore = bootstrapTrusted.createInstallationStore({ root: storeRoot });
156
+ const bootstrapProtectedStore = bootstrapTrusted.createProtectedStore({
157
+ root: storeRoot,
158
+ installationStore: bootstrapInstallationStore,
159
+ });
160
+ const initial = await resolveInitialBinding(bootstrapProtectedStore, storeRoot);
161
+ const bootstrapBinding = initial.binding;
162
+ let handedOff = false;
163
+ try {
164
+ const boundReleaseRoot = absolutePath(bootstrapBinding.releaseRoot, "neutral_codex_binding_invalid");
165
+ if (boundReleaseRoot !== bootstrapReleaseRoot) {
166
+ // An installed update may advance the binding before Codex retires its old
167
+ // cache. Authenticate the new complete runtime with the protected pin;
168
+ // its own production module then validates its own bootstrap hash set.
169
+ await verifyPinnedNeutralHostRuntime(bootstrapBinding, { storeRoot });
170
+ const production = await import(pathToFileURL(path.join(boundReleaseRoot, "server/codex-neutral-production.mjs")).href);
171
+ if (typeof production.prepareCodexNeutralRelease !== "function") throw codeError("neutral_codex_runner_integrity_invalid");
172
+ installedPrepare = production.prepareCodexNeutralRelease;
173
+ const share = production.CODEX_INITIAL_BINDING_HANDOFF === CODEX_INITIAL_BINDING_HANDOFF;
174
+ if (!share) bootstrapBinding.capability.secretBuffer.fill(0);
175
+ const selected = await production.prepareCodexNeutralRelease({ ...options, env, storeRoot,
176
+ releaseLoaderPath: path.join(boundReleaseRoot, "runtime/neutral-host-release-loader.mjs") }, finalBindingDeferred, share ? initial : null);
177
+ handedOff = finalBindingDeferred && selected.binding === bootstrapBinding;
178
+ return selected;
179
+ }
180
+ const selected = await prepareSelectedRelease({
181
+ ...options,
182
+ env,
183
+ storeRoot,
184
+ loaderPath: path.join(boundReleaseRoot, "runtime", "neutral-host-release-loader.mjs"),
185
+ }, initial);
186
+ if (!finalBindingDeferred) selected.binding.capability?.secretBuffer?.fill(0);
187
+ handedOff = finalBindingDeferred && selected.binding === bootstrapBinding;
188
+ return selected;
189
+ } finally {
190
+ if (!handedOff) bootstrapBinding.capability.secretBuffer.fill(0);
191
+ }
192
+ }
193
+
194
+ async function prepareSelectedRelease(options, verifiedInitial = null) {
195
+ const env = options.env || process.env;
196
+ const loaderPath = path.resolve(options.loaderPath);
197
+ const releaseRoot = path.dirname(path.dirname(loaderPath));
198
+ const trusted = await loadTrustedBootstrap({ loaderPath, releaseRoot });
199
+ const storeRoot = resolveClientAccountRoot("codex", absolutePath(
200
+ options.storeRoot || env.HELLOLYNK_NEUTRAL_STORE_ROOT || trusted.defaultStoreRoot("win32", env),
201
+ "neutral_codex_binding_configuration_required",
202
+ ));
203
+ const installationStore = trusted.createInstallationStore({ root: storeRoot });
204
+ const protectedStore = trusted.createProtectedStore({ root: storeRoot, installationStore });
205
+ const loader = trusted.loader;
206
+ const inspection = await loader.inspectNeutralHostRelease();
207
+ const current = verifiedInitial?.fingerprint
208
+ ? await initializationFingerprint(storeRoot, verifiedInitial.binding) : null;
209
+ if (verifiedInitial && (!current || JSON.stringify(current) !== JSON.stringify(verifiedInitial.fingerprint))) {
210
+ verifiedInitial.binding.capability?.secretBuffer?.fill(0);
211
+ verifiedInitial = null;
212
+ }
213
+ const initial = verifiedInitial || await resolveInitialBinding(protectedStore, storeRoot);
214
+ const { binding, fingerprint: initialBindingFingerprint } = initial;
215
+ try {
216
+ assertReleaseBinding(binding, inspection);
217
+ const release = await loader.activateNeutralHostRelease(inspection);
218
+ if (!release || typeof release.run !== "function") throw codeError("neutral_codex_runner_integrity_invalid");
219
+ return Object.freeze({ release, inspection, binding, initialBindingFingerprint,
220
+ releaseRoot, storeRoot, installationStore, protectedStore,
221
+ runnerBinding: (root) => currentRunnerBinding(storeRoot, root, binding, initialBindingFingerprint) });
222
+ } catch (error) {
223
+ binding.capability?.secretBuffer?.fill(0);
224
+ throw error;
225
+ }
226
+ }
227
+
228
+ // The runner in this process starts from the binding resolved above while the
229
+ // same initialization fingerprint holds; otherwise it resolves the current one.
230
+ // Only public launch fields cross over, never the capability.
231
+ async function currentRunnerBinding(storeRoot, root, binding, fingerprint) {
232
+ if (!fingerprint || path.resolve(String(root || "")) !== path.resolve(storeRoot)) return null;
233
+ const current = await initializationFingerprint(storeRoot, binding);
234
+ if (!current || JSON.stringify(current) !== JSON.stringify(fingerprint)) return null;
235
+ const { capability, ...publicBinding } = binding;
236
+ return Object.freeze(publicBinding);
237
+ }
238
+
239
+ async function resolveInitialBinding(protectedStore, storeRoot) {
240
+ const before = await initializationFingerprint(storeRoot);
241
+ const binding = await protectedStore.resolveCodexNeutralBinding();
242
+ const after = await initializationFingerprint(storeRoot, binding);
243
+ return { binding, fingerprint: before && after && before.state === after.state ? after : null };
244
+ }
245
+
246
+ // This is an initialization handoff, not a timed authorization cache. Native
247
+ // resolution has already checked the credentials and registry; reuse only if
248
+ // those exact files, account and launch paths remain unchanged. Directory
249
+ // identity matters, but unrelated lock-file writes must not invalidate it.
250
+ async function initializationFingerprint(storeRoot, binding = null) {
251
+ try {
252
+ if (resolveClientAccountRoot("codex", storeRoot) !== storeRoot) return null;
253
+ const seen = new Map();
254
+ const stat = async (file, directory, maxBytes = null) => {
255
+ const metadata = await fs.lstat(file, { bigint: true });
256
+ if (metadata.isSymbolicLink() || (directory ? !metadata.isDirectory()
257
+ : !metadata.isFile() || metadata.nlink !== 1n)
258
+ || (maxBytes != null && metadata.size > BigInt(maxBytes))) throw new Error("changed");
259
+ const identity = [metadata.dev, metadata.ino, metadata.birthtimeNs];
260
+ if (!directory) identity.push(metadata.size, metadata.ctimeNs, metadata.mtimeNs);
261
+ seen.set(file, identity.join(":"));
262
+ for (let parent = path.dirname(file); !seen.has(parent); parent = path.dirname(parent)) {
263
+ const metadata = await fs.lstat(parent, { bigint: true });
264
+ if (!metadata.isDirectory() || metadata.isSymbolicLink()) throw new Error("changed");
265
+ seen.set(parent, [metadata.dev, metadata.ino, metadata.birthtimeNs].join(":"));
266
+ if (parent === path.dirname(parent)) break;
267
+ }
268
+ };
269
+ await stat(path.join(storeRoot, "capabilities-v1.dpapi.clixml"), false);
270
+ await stat(path.join(storeRoot, "installations-v1.json"), false);
271
+ const state = JSON.stringify([...seen]);
272
+ if (binding) {
273
+ await stat(binding.runtimeRoot, true);
274
+ await stat(binding.profileManifestPath, false, 65536);
275
+ await stat(path.join(binding.releaseRoot, binding.runnerRelativePath), false, 1048576);
276
+ }
277
+ return { state, paths: JSON.stringify([...seen]) };
278
+ } catch { return null; }
279
+ }
280
+
281
+ async function loadTrustedBootstrap({ loaderPath, releaseRoot }) {
282
+ for (const [relative, expectedHash] of TRUSTED_BOOTSTRAP_FILES) {
283
+ const file = relative === "runtime/neutral-host-release-loader.mjs" ? loaderPath : path.join(releaseRoot, ...relative.split("/"));
284
+ await readVerifiedFile(file, expectedHash);
285
+ }
286
+ let loader; let protectedModule; let installationModule;
287
+ try {
288
+ [loader, protectedModule, installationModule] = await Promise.all([
289
+ import(pathToFileURL(loaderPath).href),
290
+ import(pathToFileURL(path.join(releaseRoot, "runtime", "bootstrap", "core", "hellolynk-host-protected-store.mjs")).href),
291
+ import(pathToFileURL(path.join(releaseRoot, "runtime", "bootstrap", "core", "hellolynk-neutral-host-installation-store.mjs")).href),
292
+ ]);
293
+ } catch { throw codeError("neutral_codex_runner_integrity_invalid"); }
294
+ if (typeof loader.inspectNeutralHostRelease !== "function" || typeof loader.activateNeutralHostRelease !== "function"
295
+ || typeof protectedModule.createHostProtectedStore !== "function"
296
+ || typeof installationModule.createNeutralHostInstallationStore !== "function"
297
+ || typeof installationModule.defaultNeutralHostStoreRoot !== "function") {
298
+ throw codeError("neutral_codex_runner_integrity_invalid");
299
+ }
300
+ return Object.freeze({
301
+ loader,
302
+ createProtectedStore: protectedModule.createHostProtectedStore,
303
+ createInstallationStore: installationModule.createNeutralHostInstallationStore,
304
+ defaultStoreRoot: installationModule.defaultNeutralHostStoreRoot,
305
+ });
306
+ }
307
+
308
+ export async function launchCodexNeutralHost(options = {}) {
309
+ // Pre-spawn timings travel with the attempt record the launcher already writes,
310
+ // so a slow cold start can be split into stages without a second evidence file.
311
+ const enteredAt = new Date().toISOString();
312
+ const entered = performance.now();
313
+ const elapsedMs = () => Math.max(0, Math.round(performance.now() - entered));
314
+ const expected = normalizePublicBinding(options.publicBinding);
315
+ const handed = options.verifiedLaunch;
316
+ const current = handed?.fingerprint ? await initializationFingerprint(options.storeRoot, handed.binding) : null;
317
+ const resolved = current && JSON.stringify(current) === JSON.stringify(handed.fingerprint)
318
+ ? handed.binding : await options.protectedStore.resolveCodexNeutralBinding();
319
+ // The provider has just verified this public launch descriptor. A changed
320
+ // account/store still resolves afresh; the runner resolves in its own process.
321
+ resolved.capability?.secretBuffer?.fill(0);
322
+ assertSamePublicBinding(expected, resolved);
323
+ const bindingResolvedMs = elapsedMs();
324
+ const discoverOptions = {
325
+ runtimeRoot: resolved.runtimeRoot,
326
+ region: resolved.region,
327
+ principalId: resolved.principalId,
328
+ hostInstallationId: resolved.hostInstallationId,
329
+ };
330
+ const discovered = await tryDiscover(options.discover, discoverOptions);
331
+ const discoverProbedMs = elapsedMs();
332
+ if (discovered) return;
333
+ const lockPath = path.join(resolved.runtimeRoot, "neutral-host", `${tupleDigest(resolved)}.launch.lock`);
334
+ await assertSafeDirectory(resolved.runtimeRoot, true);
335
+ await assertSafeDirectory(path.dirname(lockPath), true);
336
+ const wait = options.wait || delay;
337
+ const deadline = Date.now() + bounded(options.launchTimeoutMs, DISCOVERY_WAIT_MS, 1_000, MAX_DISCOVERY_WAIT_MS);
338
+ let ownsLock = false;
339
+ while (Date.now() < deadline) {
340
+ if (await tryDiscover(options.discover, discoverOptions)) return;
341
+ try {
342
+ await fs.mkdir(lockPath, { mode: 0o700 });
343
+ await fs.writeFile(path.join(lockPath, "owner.json"), `${JSON.stringify({
344
+ contract: "hellolynk-codex-neutral-launch-lock-v1", pid: process.pid, createdAtMs: Date.now(),
345
+ })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
346
+ ownsLock = true;
347
+ break;
348
+ } catch (error) {
349
+ if (error?.code !== "EEXIST") throw codeError("neutral_codex_launch_failed");
350
+ await assertSafeDirectory(lockPath, false);
351
+ await reclaimDeadLaunchLock(lockPath).catch(() => {});
352
+ await wait(50);
353
+ }
354
+ }
355
+ if (!ownsLock) throw codeError("neutral_codex_launch_lock_timeout");
356
+ const lockAcquiredMs = elapsedMs();
357
+ try {
358
+ if (await tryDiscover(options.discover, discoverOptions)) return;
359
+ await assertBootstrap(options.bootstrapPath, "neutral_codex_runner_unavailable");
360
+ await launchPinnedNeutralHostBootstrap(resolved, {
361
+ storeRoot: options.storeRoot,
362
+ ...(options.spawnRunner ? { spawnRunner: options.spawnRunner } : {}),
363
+ env: options.env,
364
+ wait,
365
+ bootstrapFailureDeadlineMs: deadline,
366
+ stages: { enteredAt, bindingResolvedMs, discoverProbedMs, lockAcquiredMs },
367
+ });
368
+ while (Date.now() < deadline) {
369
+ await wait(50);
370
+ if (await tryDiscover(options.discover, discoverOptions)) return;
371
+ }
372
+ throw codeError("neutral_codex_discovery_timeout");
373
+ } finally {
374
+ await fs.rm(path.join(lockPath, "owner.json"), { force: true }).catch(() => {});
375
+ await fs.rmdir(lockPath).catch(() => {});
376
+ }
377
+ }
378
+
379
+ async function reclaimDeadLaunchLock(lockPath) {
380
+ const metadata = await fs.lstat(lockPath);
381
+ const ownerPath = path.join(lockPath, "owner.json");
382
+ let owner = null;
383
+ try {
384
+ const ownerMetadata = await fs.lstat(ownerPath);
385
+ if (!ownerMetadata.isFile() || ownerMetadata.isSymbolicLink() || ownerMetadata.size > 4096) return false;
386
+ owner = JSON.parse(await fs.readFile(ownerPath, "utf8"));
387
+ } catch (error) { if (error?.code !== "ENOENT") return false; }
388
+ const oldEnough = Date.now() - metadata.mtimeMs > LOCK_WAIT_MS;
389
+ const pid = Number(owner?.pid);
390
+ if (!oldEnough || (Number.isSafeInteger(pid) && pid > 0 && processAlive(pid))) return false;
391
+ await fs.rm(ownerPath, { force: true });
392
+ await fs.rmdir(lockPath).catch(() => {});
393
+ return true;
394
+ }
395
+
396
+ function processAlive(pid) {
397
+ try { process.kill(pid, 0); return true; } catch (error) { return error?.code === "EPERM"; }
398
+ }
399
+
400
+ async function tryDiscover(discover, input) {
401
+ try { return await discover(input); } catch (error) {
402
+ if (["neutral_host_discovery_stale", "neutral_host_discovery_generation_mismatch", "ENOENT"].includes(error?.code)) {
403
+ return null;
404
+ }
405
+ throw error;
406
+ }
407
+ }
408
+
409
+ async function assertBootstrap(value, missingCode) {
410
+ const bootstrap = absolutePath(value, missingCode);
411
+ await readVerifiedFile(bootstrap, TRUSTED_BOOTSTRAP_FILES.get("runtime/neutral-host-bootstrap.mjs"));
412
+ }
413
+
414
+ function assertReleaseBinding(binding, inspection) {
415
+ if (path.resolve(binding.releaseRoot) !== path.resolve(inspection.releaseRoot)
416
+ || binding.releaseManifestSha256 !== inspection.manifestSha256
417
+ || binding.runnerRelativePath !== "runtime/neutral-host-bootstrap.mjs") {
418
+ throw codeError("neutral_codex_runner_integrity_invalid");
419
+ }
420
+ }
421
+
422
+ async function readVerifiedFile(file, expectedHash) {
423
+ await assertNoReparseAncestors(file);
424
+ let handle;
425
+ try { handle = await fs.open(file, "r"); }
426
+ catch (error) {
427
+ if (error?.code === "ENOENT") throw codeError("neutral_codex_runner_unavailable");
428
+ throw codeError("neutral_codex_runner_integrity_invalid");
429
+ }
430
+ try {
431
+ const before = await handle.stat({ bigint: true });
432
+ if (!before.isFile() || before.nlink !== 1n || before.size > 2n * 1024n * 1024n) {
433
+ throw codeError("neutral_codex_runner_integrity_invalid");
434
+ }
435
+ const bytes = await handle.readFile();
436
+ const after = await handle.stat({ bigint: true });
437
+ if (!sameFileIdentity(before, after) || sha256(bytes) !== expectedHash) {
438
+ throw codeError("neutral_codex_runner_integrity_invalid");
439
+ }
440
+ } catch (error) {
441
+ if (/^neutral_codex_/.test(String(error?.code || ""))) throw error;
442
+ throw codeError("neutral_codex_runner_integrity_invalid");
443
+ } finally { await handle.close().catch(() => {}); }
444
+ }
445
+
446
+ async function assertNoReparseAncestors(file) {
447
+ const resolved = path.resolve(file);
448
+ const parsed = path.parse(resolved);
449
+ let cursor = parsed.root;
450
+ for (const segment of path.relative(parsed.root, resolved).split(path.sep).filter(Boolean)) {
451
+ cursor = path.join(cursor, segment);
452
+ let metadata;
453
+ try { metadata = await fs.lstat(cursor); }
454
+ catch (error) {
455
+ if (error?.code === "ENOENT") throw codeError("neutral_codex_runner_unavailable");
456
+ throw codeError("neutral_codex_runner_integrity_invalid");
457
+ }
458
+ if (metadata.isSymbolicLink()) throw codeError("neutral_codex_runner_integrity_invalid");
459
+ }
460
+ }
461
+
462
+ function sameFileIdentity(left, right) {
463
+ return left.dev === right.dev && left.ino === right.ino && left.size === right.size
464
+ && left.mtimeNs === right.mtimeNs && left.nlink === right.nlink;
465
+ }
466
+ function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); }
467
+
468
+ async function assertSafeDirectory(value, create) {
469
+ if (create) await fs.mkdir(value, { recursive: true, mode: 0o700 });
470
+ const metadata = await fs.lstat(value).catch(() => null);
471
+ if (!metadata || !metadata.isDirectory() || metadata.isSymbolicLink()) {
472
+ throw codeError("neutral_codex_binding_invalid");
473
+ }
474
+ }
475
+
476
+ function normalizePublicBinding(value) {
477
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw codeError("neutral_codex_binding_invalid");
478
+ return Object.freeze({
479
+ region: safeId(value.region), principalId: safeId(value.principalId),
480
+ delegateId: safeId(value.delegateId), hostInstallationId: safeId(value.hostInstallationId),
481
+ adapterId: value.adapterId === "codex" ? "codex" : fail("neutral_codex_binding_invalid"),
482
+ capabilityVersion: Number(value.capabilityVersion), scopes: Object.freeze([...(value.scopes || [])].map(String)),
483
+ });
484
+ }
485
+
486
+ function assertSamePublicBinding(expected, actual) {
487
+ if (actual.region !== expected.region || actual.principalId !== expected.principalId
488
+ || actual.delegateId !== expected.delegateId || actual.hostInstallationId !== expected.hostInstallationId
489
+ || actual.adapterId !== expected.adapterId || actual.version !== expected.capabilityVersion
490
+ || JSON.stringify(actual.scopes) !== JSON.stringify(expected.scopes)) {
491
+ throw codeError("neutral_codex_capability_binding_mismatch");
492
+ }
493
+ }
494
+
495
+ function tupleDigest(value) {
496
+ return crypto.createHash("sha256")
497
+ .update(`${value.region}\0${value.principalId}\0${value.hostInstallationId}`).digest("hex");
498
+ }
499
+
500
+ function safeId(value) {
501
+ const text = String(value || "");
502
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(text)) throw codeError("neutral_codex_binding_invalid");
503
+ return text;
504
+ }
505
+
506
+ function absolutePath(value, code) {
507
+ const text = String(value || "");
508
+ if (!text || !path.isAbsolute(text)) throw codeError(code);
509
+ return path.resolve(text);
510
+ }
511
+
512
+ function bounded(value, fallback, minimum, maximum) {
513
+ const number = Number(value ?? fallback);
514
+ if (!Number.isSafeInteger(number) || number < minimum || number > maximum) return fallback;
515
+ return number;
516
+ }
517
+
518
+ function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); }
519
+ function fail(code) { throw codeError(code); }
520
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }