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

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 +86 -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 +160 -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,955 @@
1
+ import crypto from "node:crypto";
2
+ import { createLocalPeerAccess, normalizeLocalIdentityOwner } from "./hellolynk-local-identity-trust.mjs";
3
+ import { createLocalPeerTransport } from "./hellolynk-local-peer-transport.mjs";
4
+ import { ACCOUNT_CLIENTS, assertClientAccountCurrent } from "./hellolynk-client-account.mjs";
5
+ import { spawn } from "node:child_process";
6
+ import path from "node:path";
7
+
8
+ import {
9
+ createDeviceRuntimeSessionManager,
10
+ issueDeviceRuntimeSession,
11
+ normalizeHelloLynkBaseUrl,
12
+ } from "./hellolynk-client-core.mjs";
13
+ import { readClientProfile } from "./hellolynk-client-profile.mjs";
14
+ import { createPlatformCredentialStore } from "./hellolynk-platform-credential-store.mjs";
15
+ import { createDirectTextE2eeClientContext } from "./hellolynk-direct-text-e2ee-client.mjs";
16
+ import { openContactRequestIntroEnvelope } from "./hellolynk-contact-request-e2ee.mjs";
17
+ import {
18
+ createLocalMessenger,
19
+ createOnlineRelayTransport,
20
+ } from "./hellolynk-local-messaging.mjs";
21
+ import { createIndexedLocalMessageStore } from "./hellolynk-indexed-message-store.mjs";
22
+ import { createLocalContactRequestStore } from "./hellolynk-local-contact-requests.mjs";
23
+ import { createLocalContactStore } from "./hellolynk-local-contacts.mjs";
24
+ import {
25
+ createLocalDataProtector,
26
+ createWindowsLocalDataKeyProvider,
27
+ } from "./hellolynk-local-data-protection.mjs";
28
+ import { createLocalDataMigrationCoordinator } from "./hellolynk-local-data-migration.mjs";
29
+ import { createHelloLynkHostTransportClient } from "./hellolynk-host-transport-client.mjs";
30
+ import { createNeutralHostInstallationStore } from "./hellolynk-neutral-host-installation-store.mjs";
31
+ import { createHostProtectedStore } from "./hellolynk-host-protected-store.mjs";
32
+ import {
33
+ assertHelloLynkNeutralHostReleaseCurrent,
34
+ discoverHelloLynkNeutralHost,
35
+ startHelloLynkNeutralHostProcess,
36
+ } from "./hellolynk-neutral-host-process.mjs";
37
+ import { createNeutralHostStoreAdapters } from "./hellolynk-neutral-host-store-adapters.mjs";
38
+ import { createWindowsHumanConfirmationLedger } from "./hellolynk-human-confirmation-ledger.mjs";
39
+ import { createHelloLynkNativeHumanConfirmationBroker } from "./hellolynk-native-human-confirmation-broker.mjs";
40
+ import { createHelloLynkHumanRelationshipController } from "./hellolynk-human-relationship-controller.mjs";
41
+ import runtimeSafety from "./hellolynk-runtime-safety.cjs";
42
+
43
+ const { sanitizeHelloLynkChildEnvironment, assertHelloLynkClientNodeVersion } = runtimeSafety;
44
+
45
+ export const HELLOLYNK_NEUTRAL_HOST_ASSEMBLY_CONTRACT = "hellolynk-neutral-host-assembly-v1";
46
+ const ACCOUNT_TARGETS = new Set(["account", "devices", "keys"]);
47
+ const ADAPTER_DISPLAY_NAMES = Object.freeze({
48
+ codex: "Codex", openclaw: "OpenClaw", "trae-work": "TRAE Work",
49
+ workbuddy: "WorkBuddy", cursor: "Cursor", dsh: "DSH", "claude-code": "Claude Code",
50
+ });
51
+ const WINDOWS_ACCOUNT_LAUNCH_ACK = "hellolynk-account-opened";
52
+
53
+ export function createHelloLynkAccountLauncher(options = {}) {
54
+ const baseUrl = normalizeHelloLynkBaseUrl(options.baseUrl, {
55
+ allowLoopbackHttp: options.allowLoopbackHttp === true,
56
+ });
57
+ const accountBase = new URL("/account", `${new URL(baseUrl).origin}/`);
58
+ const spawnImpl = options.spawnImpl || spawn;
59
+ if (typeof spawnImpl !== "function") throw failure("neutral_host_account_launcher_spawn_invalid");
60
+ const launchTimeoutMs = options.launchTimeoutMs ?? 10_000;
61
+ if (!Number.isSafeInteger(launchTimeoutMs) || launchTimeoutMs < 1 || launchTimeoutMs > 30_000) {
62
+ throw failure("neutral_host_account_launcher_timeout_invalid");
63
+ }
64
+ const platform = String(options.platform || process.platform);
65
+ const childEnvironment = sanitizeHelloLynkChildEnvironment(options.env || process.env);
66
+
67
+ return Object.freeze(async (input) => {
68
+ if (!input || Array.isArray(input) || typeof input !== "object"
69
+ || !ACCOUNT_TARGETS.has(input.target)
70
+ || Object.keys(input).some((key) => !["target", "deviceRequest"].includes(key))
71
+ || (Object.hasOwn(input, "deviceRequest") && (input.target !== "account"
72
+ || !/^devreq_[a-f0-9]{48}$/.test(input.deviceRequest)))) {
73
+ throw failure("neutral_host_account_launcher_target_invalid");
74
+ }
75
+ const target = new URL(accountBase);
76
+ if (input.deviceRequest) target.searchParams.set("deviceRequest", input.deviceRequest);
77
+ else target.hash = input.target;
78
+ let child;
79
+ try {
80
+ if (platform === "win32") {
81
+ child = spawnImpl("powershell.exe", [
82
+ "-NoLogo", "-NoProfile", "-NonInteractive", "-Command",
83
+ "$ErrorActionPreference='Stop'; $target=$env:HELLOLYNK_ACCOUNT_URL; Remove-Item Env:HELLOLYNK_ACCOUNT_URL; Start-Process -FilePath $target; Write-Output 'hellolynk-account-opened'",
84
+ ], {
85
+ env: { ...childEnvironment, HELLOLYNK_ACCOUNT_URL: target.toString() },
86
+ // Windows PowerShell can exit 0 without running -Command when Node
87
+ // starts it with DETACHED_PROCESS. This short helper must stay attached.
88
+ detached: false,
89
+ stdio: ["ignore", "pipe", "ignore"],
90
+ windowsHide: true,
91
+ });
92
+ } else {
93
+ child = spawnImpl(platform === "darwin" ? "open" : "xdg-open", [target.toString()], {
94
+ env: childEnvironment,
95
+ detached: true,
96
+ stdio: "ignore",
97
+ });
98
+ }
99
+ } catch {
100
+ return Object.freeze({ opened: false });
101
+ }
102
+ if (!child || typeof child.once !== "function") return Object.freeze({ opened: false });
103
+ const outcome = await new Promise((resolve) => {
104
+ let settled = false;
105
+ let timer;
106
+ let acknowledgement = "";
107
+ const onOutput = (bytes) => { acknowledgement = (acknowledgement + String(bytes)).slice(0, 64); };
108
+ child.stdout?.on("data", onOutput);
109
+ const finish = (value) => {
110
+ if (settled) return;
111
+ settled = true;
112
+ clearTimeout(timer);
113
+ child.off?.("error", onError);
114
+ child.off?.("close", onClose);
115
+ child.stdout?.off("data", onOutput);
116
+ resolve(value);
117
+ };
118
+ const onError = () => finish("failed");
119
+ const onClose = (code, signal) => finish(code === 0 && signal == null
120
+ && (platform !== "win32" || acknowledgement.trim() === WINDOWS_ACCOUNT_LAUNCH_ACK) ? "opened" : "failed");
121
+ child.once("error", onError);
122
+ child.once("close", onClose);
123
+ timer = setTimeout(() => finish("unknown"), launchTimeoutMs);
124
+ });
125
+ if (outcome === "unknown") {
126
+ child.stdout?.destroy?.();
127
+ child.unref?.();
128
+ throw failure("neutral_host_account_launcher_outcome_unknown");
129
+ }
130
+ if (outcome !== "opened") return Object.freeze({ opened: false });
131
+ child.unref?.();
132
+ return Object.freeze({ opened: true });
133
+ });
134
+ }
135
+
136
+ export async function startHelloLynkNeutralHostAssembly(options = {}, dependencies = {}) {
137
+ assertHelloLynkClientNodeVersion();
138
+ const root = absoluteRoot(options.root, "neutral_host_assembly_root_required");
139
+ const runtimeRoot = path.resolve(options.runtimeRoot || path.join(root, "runtime"));
140
+ const installationStore = dependencies.installationStore || createNeutralHostInstallationStore({ root });
141
+ const installation = await activeInstallation(installationStore);
142
+ // Before the profile, credential and key reads, so a refusal reaches the launcher at once.
143
+ await assertHelloLynkNeutralHostReleaseCurrent({ runtimeRoot, hostInstallationId: installation.hostInstallationId });
144
+ const descriptors = normalizeDescriptors(options.profiles);
145
+ const protectedStore = dependencies.protectedStore || createHostProtectedStore({ root });
146
+ const transportOwner = Object.freeze({
147
+ principalId: installation.principalId,
148
+ hostInstallationId: installation.hostInstallationId,
149
+ });
150
+ const hostTransportCredentialProvider = bindTransportCredentialProvider(
151
+ dependencies.hostTransportCredentialProvider
152
+ || protectedStore.hostTransportCredentialProvider,
153
+ transportOwner,
154
+ );
155
+ const hostTransportSessionProvider = bindTransportSessionProvider(
156
+ dependencies.hostTransportSessionProvider
157
+ || protectedStore.hostTransportSessionProvider,
158
+ transportOwner,
159
+ );
160
+ // The protected credential is read alongside the profiles and keys, but it
161
+ // must resolve before any E2EE runtime is built or the Host lifecycle
162
+ // starts: missing, corrupt, ambiguous, expired, or cross-owner state cannot
163
+ // start the local Host. Credential and key reads settle before startup returns.
164
+ const credentialRead = settle(() => hostTransportCredentialProvider.get());
165
+ let profiles;
166
+ let credentialStore;
167
+ let keysRead = null;
168
+ try {
169
+ profiles = await Promise.all(descriptors.map(async (descriptor) => {
170
+ const profile = await (dependencies.readProfile || readClientProfile)(descriptor.profileFile);
171
+ validateProfile(profile, installation);
172
+ return Object.freeze({ descriptor, profile });
173
+ }));
174
+ validateBindings(profiles, installation);
175
+ const clientRoot = commonProfileClientRoot(profiles);
176
+ credentialStore = dependencies.credentialStore || (
177
+ dependencies.createCredentialStore || createPlatformCredentialStore
178
+ )({
179
+ clientRoot,
180
+ storeRoot: path.join(clientRoot, "credentials"),
181
+ e2eeStoreRoot: path.join(clientRoot, "e2ee-private-keys"),
182
+ });
183
+ // One helper process reads every profile's keys. It only fills the read
184
+ // cache: each runtime still loads its own keys and reports its own errors.
185
+ if (typeof credentialStore.directTextPrivateKeys?.preload === "function") {
186
+ keysRead = settle(() => credentialStore.directTextPrivateKeys.preload(profiles.map(({ profile }) => profile.profileId)));
187
+ }
188
+ } catch (error) {
189
+ await credentialRead;
190
+ throw error;
191
+ }
192
+ const credential = await credentialRead;
193
+ await keysRead;
194
+ let hostTransportCredential = credential.value;
195
+ credential.value = null;
196
+ if (credential.error) throw credential.error;
197
+ if (typeof hostTransportCredential !== "string" || !hostTransportCredential) {
198
+ hostTransportCredential = null;
199
+ throw failure("host_transport_credential_required");
200
+ }
201
+ hostTransportCredential = null;
202
+
203
+ const runtimeByDelegate = new Map();
204
+ const assertLocalIdentityRuntimeCurrent = async (profile, adapterId = null) => {
205
+ const current = await activeInstallation(installationStore);
206
+ validateProfile(profile, current);
207
+ const binding = current.delegateBindings.find((item) => item.profileId === profile.profileId
208
+ && item.delegateId === profile.delegateId && item.authorizationVersion === profile.authorizationVersion);
209
+ if (!binding) throw failure("neutral_host_same_host_authorization_inactive");
210
+ const clients = binding.adapters.filter((client) => ACCOUNT_CLIENTS.includes(client) && (!adapterId || client === adapterId));
211
+ if (!clients.length) throw failure("workbench_account_changed");
212
+ for (const client of clients) {
213
+ try { assertClientAccountCurrent(client, root); return; } catch {}
214
+ }
215
+ throw failure("workbench_account_changed");
216
+ };
217
+ let sameHostDeliver = null;
218
+ let contacts = null;
219
+ const assertCurrentSameHostBinding = async (delegateId, authorizationVersion) => {
220
+ const current = await activeInstallation(installationStore);
221
+ if (current.principalId !== installation.principalId
222
+ || current.hostInstallationId !== installation.hostInstallationId) {
223
+ throw failure("neutral_host_same_host_installation_changed");
224
+ }
225
+ const binding = current.delegateBindings.find((item) => item.delegateId === delegateId);
226
+ if (!binding || binding.authorizationVersion !== authorizationVersion) {
227
+ throw failure("neutral_host_same_host_authorization_inactive");
228
+ }
229
+ return binding;
230
+ };
231
+ const initializedProfiles = await Promise.all(profiles.map(async ({ profile }) => {
232
+ const peerOptions = { root, profile,
233
+ clients: [...new Set(installation.delegateBindings.flatMap((binding) => binding.adapters)
234
+ .filter((client) => ACCOUNT_CLIENTS.includes(client)))],
235
+ assertCurrent: () => assertLocalIdentityRuntimeCurrent(profile) };
236
+ const localPeerAccess = !dependencies.createLocalPeerAccess && (profile.allowLoopbackHttp === true || !peerOptions.clients.length)
237
+ ? async (_input, _visit, { required = false } = {}) => {
238
+ if (required) throw failure("local_peer_unavailable");
239
+ return null;
240
+ } : (dependencies.createLocalPeerAccess || createLocalPeerAccess)(peerOptions);
241
+ const localPeer = createLocalPeerTransport({ profile, access: localPeerAccess });
242
+ const sessionKey = runtimeSessionKey(profile);
243
+ const manager = (dependencies.createRuntimeSessionManager || createDeviceRuntimeSessionManager)({
244
+ sessionStore: credentialStore.runtimeSessions,
245
+ sessionKey,
246
+ issue: async ({ previousSession } = {}) => {
247
+ let deviceCredential = await credentialStore.load(profile.profileId);
248
+ try {
249
+ return await (dependencies.issueRuntimeSession || issueDeviceRuntimeSession)({
250
+ baseUrl: profile.baseUrl,
251
+ allowLoopbackHttp: profile.allowLoopbackHttp,
252
+ deviceCredential,
253
+ expectedPrincipalId: profile.principalId,
254
+ expectedAgentId: profile.agentId,
255
+ expectedDelegateId: profile.delegateId,
256
+ expectedHostId: profile.hostId,
257
+ expectedAuthorizationVersion: profile.authorizationVersion,
258
+ previousSession,
259
+ fetchImpl: dependencies.fetchImpl,
260
+ });
261
+ } finally { deviceCredential = null; }
262
+ },
263
+ });
264
+ const sessionProvider = Object.freeze({
265
+ get: () => manager.get({
266
+ expectedPrincipalId: profile.principalId,
267
+ expectedAgentId: profile.agentId,
268
+ expectedDelegateId: profile.delegateId,
269
+ expectedHostId: profile.hostId,
270
+ expectedAuthorizationVersion: profile.authorizationVersion,
271
+ }),
272
+ clear: (error) => manager.clear(error),
273
+ status: () => manager.status(),
274
+ });
275
+ const e2ee = await (dependencies.createE2eeContext || createDirectTextE2eeClientContext)({
276
+ profileId: profile.profileId,
277
+ baseUrl: profile.baseUrl,
278
+ allowLoopbackHttp: profile.allowLoopbackHttp,
279
+ principalId: profile.principalId,
280
+ hellolynkId: profile.hellolynkId,
281
+ agentId: profile.agentId,
282
+ delegateId: profile.delegateId,
283
+ hostId: profile.hostId,
284
+ authorizationVersion: profile.authorizationVersion,
285
+ deviceId: profile.deviceId,
286
+ e2eeVersion: 2,
287
+ deferCloudRegistration: true,
288
+ localClaimTimeoutMs: 30_000,
289
+ sessionProvider,
290
+ credentialStore,
291
+ fetchImpl: dependencies.fetchImpl,
292
+ sameHostClaimProvider: async (input, { signal } = {}) => {
293
+ signal?.throwIfAborted();
294
+ await assertLocalIdentityRuntimeCurrent(profile);
295
+ if (input.recipientHostId && input.recipientHostId !== installation.hostInstallationId) {
296
+ return localPeer.claim(input, { signal });
297
+ }
298
+ const recipient = runtimeByDelegate.get(input.recipientDelegateId);
299
+ if (!recipient) {
300
+ if (input.recipientHostId === installation.hostInstallationId) throw failure("neutral_host_same_host_recipient_unavailable");
301
+ return localPeer.claim(input, { signal });
302
+ }
303
+ await assertCurrentSameHostBinding(profile.delegateId, profile.authorizationVersion);
304
+ await assertCurrentSameHostBinding(recipient.profile.delegateId, recipient.profile.authorizationVersion);
305
+ signal?.throwIfAborted();
306
+ if (input.recipientDeviceId != null && input.recipientDeviceId !== recipient.profile.deviceId) {
307
+ throw failure("neutral_host_same_host_device_mismatch");
308
+ }
309
+ const claim = await recipient.e2ee.createSameHostClaim({
310
+ routeKind: input.routeKind,
311
+ recipientDelegateId: input.recipientDelegateId,
312
+ recipientHostId: installation.hostInstallationId,
313
+ clientMessageId: input.clientMessageId,
314
+ senderPrincipalId: input.senderPrincipalId,
315
+ senderDelegateId: input.senderDelegateId,
316
+ senderDelegateAuthorizationVersion: input.senderDelegateAuthorizationVersion,
317
+ }, { signal });
318
+ signal?.throwIfAborted();
319
+ await assertCurrentSameHostBinding(profile.delegateId, profile.authorizationVersion);
320
+ await assertCurrentSameHostBinding(recipient.profile.delegateId, recipient.profile.authorizationVersion);
321
+ signal?.throwIfAborted();
322
+ return claim;
323
+ },
324
+ });
325
+ const contactE2ee = (dependencies.createContactE2ee || createContactInboundE2ee)({
326
+ profile,
327
+ credentialStore,
328
+ now: options.now,
329
+ });
330
+ return Object.freeze({ profile, manager, sessionProvider, e2ee, contactE2ee, localPeer, localPeerAccess });
331
+ }));
332
+
333
+ const endpoints = [];
334
+ for (const { profile, manager, sessionProvider, e2ee, contactE2ee, localPeer, localPeerAccess } of initializedProfiles) {
335
+ runtimeByDelegate.set(profile.delegateId, Object.freeze({ profile, manager, sessionProvider, e2ee, localPeer, localPeerAccess }));
336
+ const binding = installation.delegateBindings.find((item) => item.delegateId === profile.delegateId);
337
+ const boundAdapters = [...new Set((binding?.adapters ?? []).filter((id) => ACCOUNT_CLIENTS.includes(id)))];
338
+ endpoints.push(Object.freeze({
339
+ principalId: profile.principalId,
340
+ principalLabel: null,
341
+ hostInstallationId: profile.hostId,
342
+ delegateId: profile.delegateId,
343
+ // A single installed adapter identifies the local AI's display name.
344
+ // Shared or unknown adapter bindings cannot identify one client label.
345
+ delegateLabel: boundAdapters.length === 1 ? ADAPTER_DISPLAY_NAMES[boundAdapters[0]] : profile.agentId,
346
+ hellolynkId: profile.hellolynkId,
347
+ authorizationVersion: profile.authorizationVersion,
348
+ identityKeyId: e2ee.identityKeyId,
349
+ runtimeHeaders: () => runtimeHeaders(sessionProvider),
350
+ e2ee,
351
+ contactE2ee,
352
+ }));
353
+ }
354
+
355
+ const transport = dependencies.transport || createHelloLynkHostTransportClient({
356
+ baseUrl: commonValue(profiles, "baseUrl", "neutral_host_assembly_base_url_mismatch"),
357
+ allowLoopbackHttp: commonValue(profiles, "allowLoopbackHttp", "neutral_host_assembly_base_url_mismatch"),
358
+ expectedPrincipalId: installation.principalId,
359
+ expectedHostId: installation.hostInstallationId,
360
+ credentialProvider: hostTransportCredentialProvider,
361
+ sessionProvider: hostTransportSessionProvider,
362
+ runtimeHeadersForDelegate: async (delegateId) => {
363
+ const runtime = runtimeByDelegate.get(delegateId);
364
+ if (!runtime) throw failure("neutral_host_assembly_delegate_unknown");
365
+ return runtimeHeaders(runtime.sessionProvider);
366
+ },
367
+ fetchImpl: dependencies.fetchImpl,
368
+ });
369
+
370
+ const canonicalProfile = profiles.find(({ profile }) => profile.profileId === installation.canonicalProfileId)?.profile;
371
+ if (!canonicalProfile) throw failure("neutral_host_assembly_canonical_profile_missing");
372
+ const accountLauncher = dependencies.accountLauncher === null
373
+ ? null
374
+ : dependencies.accountLauncher ?? createHelloLynkAccountLauncher({
375
+ baseUrl: canonicalProfile.baseUrl,
376
+ allowLoopbackHttp: canonicalProfile.allowLoopbackHttp,
377
+ });
378
+ // Business records are plaintext. The former local data key is opened only
379
+ // to convert an installation that still carries the encryption journal.
380
+ let localDataKeyProvider = dependencies.localDataKeyProvider || null;
381
+ const createLegacyDataProtector = () => dependencies.dataProtector || createLocalDataProtector({
382
+ region: installation.region,
383
+ principalId: installation.principalId,
384
+ hostInstallationId: installation.hostInstallationId,
385
+ keyProvider: localDataKeyProvider ||= (
386
+ dependencies.createLocalDataKeyProvider || createWindowsLocalDataKeyProvider
387
+ )({ root: path.join(root, "local-data-key") }),
388
+ });
389
+ const localDataMigration = dependencies.localDataMigration || createLocalDataMigrationCoordinator({
390
+ root,
391
+ region: installation.region,
392
+ principalId: installation.principalId,
393
+ hostInstallationId: installation.hostInstallationId,
394
+ profileIds: profiles.map(({ profile }) => profile.profileId),
395
+ canonicalProfileId: canonicalProfile.profileId,
396
+ createLegacyDataProtector,
397
+ });
398
+ const messageStore = dependencies.messageStore || await createIndexedLocalMessageStore({
399
+ profileId: canonicalProfile.profileId,
400
+ storePath: path.join(root, "messages", `${canonicalProfile.profileId}.json`),
401
+ ownerScope: { region: installation.region, principalId: installation.principalId },
402
+ deferInitialization: true,
403
+ });
404
+ const contactRequestStore = dependencies.contactRequestStore || createLocalContactRequestStore({
405
+ profileId: canonicalProfile.profileId,
406
+ root,
407
+ });
408
+ contacts = dependencies.contacts || createLocalContactStore({
409
+ profileId: canonicalProfile.profileId,
410
+ root,
411
+ });
412
+ const adapters = dependencies.storeAdapters || createNeutralHostStoreAdapters({
413
+ principalId: installation.principalId,
414
+ profileId: canonicalProfile.profileId,
415
+ messageStore,
416
+ contactRequestStore,
417
+ journalPath: path.join(root, "neutral-host-recovery-v1.json"),
418
+ });
419
+ const productionEndpoints = endpoints.map((endpoint) => {
420
+ const runtime = runtimeByDelegate.get(endpoint.delegateId);
421
+ const clients = installation.delegateBindings.find((item) => item.delegateId === endpoint.delegateId)?.adapters.filter((id) => ACCOUNT_CLIENTS.includes(id)) || [];
422
+ const assertEndpointCurrent = () => {
423
+ if (!clients.length) return;
424
+ for (const client of clients) {
425
+ try { assertClientAccountCurrent(client, root); return; } catch {}
426
+ }
427
+ throw failure("workbench_account_changed");
428
+ };
429
+ const relayTransport = (dependencies.createOutboundTransport || createOnlineRelayTransport)({
430
+ baseUrl: runtime.profile.baseUrl,
431
+ allowLoopbackHttp: runtime.profile.allowLoopbackHttp,
432
+ sessionProvider: runtime.sessionProvider,
433
+ fetchImpl: dependencies.fetchImpl,
434
+ });
435
+ const routeTransport = Object.freeze({
436
+ async sendText(message) {
437
+ assertEndpointCurrent();
438
+ const sealed = message?.e2ee;
439
+ const crossHostLocal = sealed?.version === "2" && sealed.claimId?.startsWith("local_peer_");
440
+ const sameHostLocal = sealed?.version === "2" && sealed.recipientHostId === installation.hostInstallationId
441
+ && (sealed.routeKind === "owned_delegate" || sealed.claimId?.startsWith("same_host_"));
442
+ if (!crossHostLocal && !sameHostLocal) {
443
+ return relayTransport.sendText(message);
444
+ }
445
+ if (typeof sameHostDeliver !== "function") throw failure("neutral_host_same_host_delivery_unavailable");
446
+ const recipient = runtimeByDelegate.get(sealed.recipientDelegateId);
447
+ if (!crossHostLocal && !recipient) throw failure("neutral_host_same_host_recipient_unavailable");
448
+ await assertLocalIdentityRuntimeCurrent(runtime.profile);
449
+ await assertCurrentSameHostBinding(runtime.profile.delegateId, runtime.profile.authorizationVersion);
450
+ if (!crossHostLocal) await assertCurrentSameHostBinding(recipient.profile.delegateId, recipient.profile.authorizationVersion);
451
+ const protectedMetadata = sealed.envelope?.protected;
452
+ const { authorizationScope: _authorizationScope, intentSource: _intentSource, ...outerMetadata } = protectedMetadata;
453
+ const senderIdentity = await runtime.e2ee.publicDeviceIdentity();
454
+ const event = Object.freeze({
455
+ schemaVersion: 2,
456
+ eventId: `same_host_${crypto.createHash("sha256").update(`${message.clientMessageId}\0${sealed.envelopeDigest}`).digest("hex").slice(0, 32)}`,
457
+ ...outerMetadata,
458
+ e2eeEnvelope: sealed.envelope,
459
+ envelopeDigest: sealed.envelopeDigest,
460
+ senderIdentity,
461
+ relayedAt: new Date().toISOString(),
462
+ });
463
+ const delivered = crossHostLocal ? await runtime.localPeer.deliver(event, sealed.nativeContentVersion)
464
+ : await sameHostDeliver(Object.freeze({ event: "message", data: event, localDelivery: true }));
465
+ return Object.freeze({
466
+ ok: delivered?.replyTargetDeleted !== true,
467
+ status: delivered?.replyTargetDeleted ? "reply_target_deleted" : "acknowledged_by_recipient_local_store",
468
+ deliveryPath: crossHostLocal ? "same_computer_local" : "same_host_local",
469
+ duplicate: delivered?.duplicate === true,
470
+ eventId: event.eventId,
471
+ });
472
+ },
473
+ openEventStream: (...args) => relayTransport.openEventStream(...args),
474
+ });
475
+ let messenger = null;
476
+ const localMessenger = () => {
477
+ messenger ||= (dependencies.createMessenger || createLocalMessenger)({
478
+ profileId: canonicalProfile.profileId,
479
+ principalId: installation.principalId,
480
+ delegateId: endpoint.delegateId,
481
+ store: messageStore,
482
+ transport: routeTransport,
483
+ e2ee: runtime.e2ee,
484
+ e2eeMode: "required",
485
+ durableBeforeEncryption: true,
486
+ });
487
+ return messenger;
488
+ };
489
+ return Object.freeze({
490
+ ...endpoint,
491
+ nativeDirectoryAdapterId: clients.length === 1 ? clients[0] : null,
492
+ assertNativeDirectoryCurrent: assertEndpointCurrent,
493
+ outbound: Object.freeze({
494
+ async sendDirectory(input) {
495
+ assertEndpointCurrent();
496
+ if (input.recipientPrincipalId !== installation.principalId && (input.route?.routeKind !== "instance_direct"
497
+ || !["friend_target_request", "friend_target_response"].includes(input.nativeDirectory?.kind)))
498
+ throw failure("native_directory_own_ai_only");
499
+ const e2ee = await runtime.e2ee.prepareOutgoing({
500
+ recipientHelloLynkId: input.recipientHelloLynkId, senderPrincipalId: installation.principalId,
501
+ senderDelegateId: endpoint.delegateId, clientMessageId: input.clientMessageId,
502
+ text: "", nativeDirectory: input.nativeDirectory, intentSource: "human_preauthorized_contact",
503
+ ...input.route,
504
+ }, { persistPrepared: false });
505
+ return routeTransport.sendText({ ...input, clientMessageId: e2ee.envelope.protected.clientMessageId, e2ee });
506
+ },
507
+ async send(input) {
508
+ assertEndpointCurrent();
509
+ const result = await localMessenger().sendText(input);
510
+ if (result?.message?.lastErrorCode === "reply_target_deleted") throw failure("reply_target_deleted");
511
+ if (result?.ok !== true) throw failure(
512
+ safeOutboundCode(result?.errorCode || result?.message?.lastErrorCode || result?.status),
513
+ );
514
+ return result;
515
+ },
516
+ async retry(clientMessageId, options) {
517
+ assertEndpointCurrent();
518
+ return localMessenger().retry(clientMessageId, options);
519
+ },
520
+ }),
521
+ });
522
+ });
523
+ const applicationOutbox = Object.freeze({
524
+ ...adapters.outbox,
525
+ async retry(clientMessageId, delegateId, options) {
526
+ const endpoint = productionEndpoints.find((item) => item.delegateId === delegateId);
527
+ if (!endpoint) throw failure("neutral_host_assembly_delegate_unknown");
528
+ return endpoint.outbound.retry(clientMessageId, options);
529
+ },
530
+ });
531
+ const canonicalRuntime = runtimeByDelegate.get(canonicalProfile.delegateId);
532
+ if (!canonicalRuntime) throw failure("neutral_host_assembly_canonical_runtime_missing");
533
+ const humanRelationships = dependencies.humanRelationships
534
+ || (dependencies.createHumanRelationships || createHelloLynkHumanRelationshipController)({
535
+ cloud: transport,
536
+ e2ee: canonicalRuntime.e2ee,
537
+ localStore: contactRequestStore,
538
+ runtimeHeaders: () => runtimeHeaders(canonicalRuntime.sessionProvider),
539
+ principalId: canonicalProfile.principalId,
540
+ hellolynkId: canonicalProfile.hellolynkId,
541
+ deviceId: canonicalProfile.deviceId,
542
+ identityKeyId: canonicalRuntime.e2ee.identityKeyId,
543
+ contactAuthorizationStore: contacts,
544
+ now: options.now,
545
+ });
546
+ const humanConfirmationLedger = dependencies.humanConfirmationLedger
547
+ || createWindowsHumanConfirmationLedger({
548
+ root: path.join(root, "human-confirmation-ledger"),
549
+ });
550
+ const nativeHumanConfirmationBrokerFactory = dependencies.nativeHumanConfirmationBrokerFactory
551
+ || ((input) => createHelloLynkNativeHumanConfirmationBroker({
552
+ ledger: humanConfirmationLedger,
553
+ launcher: dependencies.nativeHumanConfirmationLauncher,
554
+ hostGeneration: input.hostGeneration,
555
+ onApproved: input.onApproved,
556
+ approvalDrainTimeoutMs: options.drainTimeoutMs,
557
+ }));
558
+ let host;
559
+ try {
560
+ host = await (dependencies.startProcess || startHelloLynkNeutralHostProcess)({
561
+ region: installation.region,
562
+ principalId: installation.principalId,
563
+ hostInstallationId: installation.hostInstallationId,
564
+ runtimeRoot,
565
+ transport,
566
+ endpoints: productionEndpoints,
567
+ localStore: adapters.localStore,
568
+ contactStore: adapters.contactStore,
569
+ outbox: adapters.outbox,
570
+ inboxStore: applicationOutbox,
571
+ contacts,
572
+ localIdentityProof: async (context) => {
573
+ const { request } = context;
574
+ const runtime = runtimeByDelegate.get(request.target.delegateId);
575
+ if (!runtime || request.target.hostInstallationId !== installation.hostInstallationId
576
+ || request.target.authorizationVersion !== runtime.profile.authorizationVersion
577
+ || context.delegateId !== runtime.profile.delegateId) throw failure("neutral_host_local_identity_unavailable");
578
+ await assertLocalIdentityRuntimeCurrent(runtime.profile, context.adapterId);
579
+ const owner = normalizeLocalIdentityOwner({ region: runtime.profile.region, baseUrl: runtime.profile.baseUrl,
580
+ principalId: runtime.profile.principalId, hellolynkId: runtime.profile.hellolynkId });
581
+ if (JSON.stringify(owner) !== JSON.stringify(request.owner)) throw failure("neutral_host_local_identity_owner_mismatch");
582
+ // Immutable local identity metadata needs no E2EE state lock. Simultaneous
583
+ // sends between two Hosts therefore cannot acquire one another's locks.
584
+ return { deviceId: runtime.profile.deviceId, identityKeyId: runtime.e2ee.identityKeyId,
585
+ safetyCode: runtime.e2ee.localDeviceSafetyCode };
586
+ },
587
+ localPeerExchange: async (context) => {
588
+ const { request } = context;
589
+ const runtime = runtimeByDelegate.get(request.target.delegateId);
590
+ if (!runtime || context.delegateId !== runtime.profile.delegateId
591
+ || request.target.hostInstallationId !== installation.hostInstallationId
592
+ || request.target.authorizationVersion !== runtime.profile.authorizationVersion) throw failure("local_peer_target_changed");
593
+ await assertLocalIdentityRuntimeCurrent(runtime.profile, context.adapterId);
594
+ const owner = normalizeLocalIdentityOwner({ region: runtime.profile.region, baseUrl: runtime.profile.baseUrl,
595
+ principalId: runtime.profile.principalId, hellolynkId: runtime.profile.hellolynkId });
596
+ if (JSON.stringify(owner) !== JSON.stringify(request.owner)) throw failure("local_peer_owner_mismatch");
597
+ // Verify the source's current protected binding, not an arbitrary key
598
+ // supplied in an HTTP request. No profile/store lock spans this proof.
599
+ await runtime.localPeerAccess({ peerHelloLynkId: owner.hellolynkId,
600
+ peerDelegateId: request.source.delegateId, peerHostId: request.source.hostInstallationId,
601
+ peerAuthorizationVersion: request.source.authorizationVersion }, async () => true,
602
+ { required: true, proveIdentity: false, timeoutMs: 30_000 });
603
+ await assertLocalIdentityRuntimeCurrent(runtime.profile, context.adapterId);
604
+ if (request.action === "claim") {
605
+ if (request.payload.recipientDeviceId && request.payload.recipientDeviceId !== runtime.profile.deviceId) throw failure("local_peer_target_changed");
606
+ const claim = await runtime.e2ee.createSameHostClaim({ routeKind: request.payload.routeKind,
607
+ clientMessageId: request.payload.clientMessageId, recipientDelegateId: runtime.profile.delegateId,
608
+ recipientHostId: installation.hostInstallationId, senderPrincipalId: owner.principalId,
609
+ senderDelegateId: request.source.delegateId, senderDelegateAuthorizationVersion: request.source.authorizationVersion });
610
+ return { ...claim, claimId: claim.claimId.replace(/^same_host_/, "local_peer_") };
611
+ }
612
+ if (typeof sameHostDeliver !== "function") throw failure("local_peer_receive_unavailable");
613
+ const event = request.payload.event;
614
+ const delivered = await sameHostDeliver({ event: "message", data: event, localDelivery: true });
615
+ return { acknowledged: delivered.acknowledged === true, duplicate: delivered.duplicate === true,
616
+ replyTargetDeleted: delivered.replyTargetDeleted === true, eventId: event.eventId, envelopeDigest: event.envelopeDigest };
617
+ },
618
+ applicationOutbox,
619
+ capabilityResolver: protectedStore.capabilityResolver,
620
+ humanRelationships,
621
+ accountLauncher,
622
+ nativeHumanConfirmationBrokerFactory,
623
+ sameHostDeliveryRegistrar: (deliver) => { sameHostDeliver = deliver; },
624
+ prepareLocalData: async (context) => {
625
+ await (dependencies.prepareLocalData || ((input) => localDataMigration.prepare(input)))(context);
626
+ await messageStore.initialize?.();
627
+ },
628
+ port: options.port,
629
+ drainTimeoutMs: options.drainTimeoutMs,
630
+ maintenanceEveryMs: options.maintenanceEveryMs,
631
+ });
632
+ } catch (error) {
633
+ try { await messageStore.close?.(); }
634
+ finally { localDataKeyProvider?.clear?.(); }
635
+ throw error;
636
+ }
637
+
638
+ return Object.freeze({
639
+ contractId: HELLOLYNK_NEUTRAL_HOST_ASSEMBLY_CONTRACT,
640
+ host,
641
+ installation,
642
+ endpoints: Object.freeze(productionEndpoints.map((endpoint) => Object.freeze({
643
+ delegateId: endpoint.delegateId,
644
+ identityKeyId: endpoint.identityKeyId,
645
+ e2eeVersion: endpoint.e2ee.version,
646
+ }))),
647
+ runtimeByDelegate,
648
+ stores: Object.freeze({
649
+ messageStore,
650
+ contactRequestStore,
651
+ contacts,
652
+ recovery: adapters.journal,
653
+ localDataMigration,
654
+ }),
655
+ async stop(reason) {
656
+ try {
657
+ return await host.stop(reason);
658
+ } finally {
659
+ try { await messageStore.close?.(); }
660
+ finally { localDataKeyProvider?.clear?.(); }
661
+ }
662
+ },
663
+ });
664
+ }
665
+
666
+ export async function statusHelloLynkNeutralHostAssembly(options = {}, dependencies = {}) {
667
+ const root = absoluteRoot(options.root, "neutral_host_assembly_root_required");
668
+ const installations = dependencies.installationStore || createNeutralHostInstallationStore({ root });
669
+ const registry = await installations.status();
670
+ const active = registry.installations.filter((item) => item.state === "active");
671
+ let discovery = null;
672
+ if (active.length === 1) {
673
+ discovery = await (dependencies.discover || discoverHelloLynkNeutralHost)({
674
+ region: active[0].region,
675
+ principalId: active[0].principalId,
676
+ hostInstallationId: active[0].hostInstallationId,
677
+ runtimeRoot: path.resolve(options.runtimeRoot || path.join(root, "runtime")),
678
+ }).catch((error) => {
679
+ if (["ENOENT", "neutral_host_runtime_path_invalid"].includes(error?.code)) return null;
680
+ throw error;
681
+ });
682
+ }
683
+ return Object.freeze({
684
+ contractId: HELLOLYNK_NEUTRAL_HOST_ASSEMBLY_CONTRACT,
685
+ registry,
686
+ running: Boolean(discovery),
687
+ discovery,
688
+ });
689
+ }
690
+
691
+ export function createContactInboundE2ee(options = {}) {
692
+ const profile = options.profile;
693
+ const privateKeys = options.credentialStore?.directTextPrivateKeys;
694
+ const stateLock = options.credentialStore?.runtimeSessions;
695
+ const now = typeof options.now === "function" ? options.now : () => Date.now();
696
+ if (!privateKeys || !stateLock?.withLock) throw failure("neutral_host_contact_e2ee_store_invalid");
697
+ return Object.freeze({
698
+ async openIncoming(input = {}) {
699
+ if (input.recipientPrincipalId !== profile.principalId
700
+ || input.recipientDelegateId !== profile.delegateId) {
701
+ throw failure("neutral_host_contact_e2ee_route_mismatch");
702
+ }
703
+ const event = input.event;
704
+ return stateLock.withLock(profile.profileId, async () => {
705
+ const state = await privateKeys.load(profile.profileId);
706
+ if (!state || state.owner?.principalId !== profile.principalId
707
+ || state.owner?.delegateId !== profile.delegateId || state.owner?.hostId !== profile.hostId
708
+ || Number(state.owner?.authorizationVersion) !== profile.authorizationVersion) {
709
+ throw failure("neutral_host_contact_e2ee_owner_mismatch");
710
+ }
711
+ if (state.deviceKeyBundle?.keyId !== event.recipientIdentityKeyId) {
712
+ throw failure("neutral_host_contact_e2ee_identity_mismatch");
713
+ }
714
+ const preKey = state.preKeys?.find((item) => item.privateKeyBundle?.preKeyId === event.recipientPreKeyId);
715
+ if (!preKey || !["registered", "used"].includes(preKey.state)) {
716
+ throw failure("neutral_host_contact_e2ee_prekey_unavailable");
717
+ }
718
+ const opened = openContactRequestIntroEnvelope({
719
+ envelope: event.e2eeEnvelope,
720
+ senderDeviceIdentity: event.senderIdentity,
721
+ recipientDeviceKeyBundle: state.deviceKeyBundle,
722
+ recipientOneTimePreKey: preKey.privateKeyBundle,
723
+ expectedSender: {
724
+ principalId: event.senderPrincipalId,
725
+ hellolynkId: event.senderHelloLynkId,
726
+ deviceId: event.senderDeviceId,
727
+ },
728
+ expectedRecipient: {
729
+ principalId: profile.principalId,
730
+ hellolynkId: profile.hellolynkId,
731
+ deviceId: profile.deviceId,
732
+ },
733
+ }, { nowMs: now() });
734
+ assertContactOuterBindings(event, opened);
735
+ let commitStarted = false;
736
+ return Object.freeze({
737
+ text: opened.introText,
738
+ introDigestSalt: opened.introDigestSalt,
739
+ envelopeDigest: opened.envelopeDigest,
740
+ async commit() {
741
+ if (commitStarted) return;
742
+ commitStarted = true;
743
+ try {
744
+ await stateLock.withLock(profile.profileId, async () => {
745
+ const latest = await privateKeys.load(profile.profileId);
746
+ consumeContactPreKey(latest, event, opened.envelopeDigest, now());
747
+ await privateKeys.save(profile.profileId, latest);
748
+ });
749
+ } catch (error) { commitStarted = false; throw error; }
750
+ },
751
+ });
752
+ });
753
+ },
754
+ });
755
+ }
756
+
757
+ function assertContactOuterBindings(event, opened) {
758
+ if (opened.envelopeDigest !== event.envelopeDigest) throw failure("neutral_host_contact_e2ee_digest_mismatch");
759
+ for (const field of [
760
+ "attemptId", "clientRequestId", "requestDigest", "introDigest", "introWeight",
761
+ "senderPrincipalId", "senderHelloLynkId", "senderDeviceId", "senderIdentityKeyId",
762
+ "recipientPrincipalId", "recipientHelloLynkId", "recipientDeviceId", "recipientIdentityKeyId",
763
+ "recipientPreKeyId", "createdAt", "expiresAt",
764
+ ]) {
765
+ if (event[field] !== opened.protectedMetadata[field]) {
766
+ throw failure("neutral_host_contact_e2ee_binding_mismatch");
767
+ }
768
+ }
769
+ if (opened.protectedMetadata.authorizationScope !== "contact.request.send"
770
+ || opened.protectedMetadata.intentSource !== "hellolynk_host_explicit_human_ui"
771
+ || opened.protectedMetadata.messageType !== "contact_request_intro") {
772
+ throw failure("neutral_host_contact_e2ee_binding_mismatch");
773
+ }
774
+ }
775
+
776
+ function consumeContactPreKey(state, event, envelopeDigest, nowMs) {
777
+ const preKeyId = event.recipientPreKeyId;
778
+ const messageId = event.clientRequestId;
779
+ const consumed = state.consumedPreKeys?.find((item) => item.preKeyId === preKeyId);
780
+ const preKey = state.preKeys?.find((item) => item.privateKeyBundle?.preKeyId === preKeyId);
781
+ if (consumed) {
782
+ if (consumed.clientMessageId !== messageId || consumed.envelopeDigest !== envelopeDigest) {
783
+ throw failure("neutral_host_contact_e2ee_prekey_conflict");
784
+ }
785
+ return false;
786
+ }
787
+ if (!preKey) throw failure("neutral_host_contact_e2ee_prekey_unavailable");
788
+ if (preKey.state === "used" && (preKey.usedClientMessageId !== messageId
789
+ || preKey.usedEnvelopeDigest !== envelopeDigest)) {
790
+ throw failure("neutral_host_contact_e2ee_prekey_conflict");
791
+ }
792
+ const expiresAt = openedExpiry(event);
793
+ if (Date.parse(expiresAt) <= nowMs) throw failure("neutral_host_contact_e2ee_prekey_expired");
794
+ preKey.state = "used";
795
+ preKey.claimExpiresAt = expiresAt;
796
+ preKey.usedClientMessageId = messageId;
797
+ preKey.usedEnvelopeDigest = envelopeDigest;
798
+ if (!Array.isArray(state.consumedPreKeys)) throw failure("neutral_host_contact_e2ee_state_invalid");
799
+ if (state.consumedPreKeys.length >= 512) state.consumedPreKeys.shift();
800
+ state.consumedPreKeys.push({
801
+ preKeyId,
802
+ clientMessageId: messageId,
803
+ envelopeDigest,
804
+ consumedAt: new Date(nowMs).toISOString(),
805
+ });
806
+ return true;
807
+ }
808
+
809
+ function openedExpiry(event) {
810
+ const value = String(event.e2eeEnvelope?.protected?.expiresAt || event.expiresAt || "");
811
+ if (!Number.isFinite(Date.parse(value))) throw failure("neutral_host_contact_e2ee_expiry_invalid");
812
+ return value;
813
+ }
814
+
815
+ async function runtimeHeaders(provider) {
816
+ const session = await provider.get();
817
+ const secret = String(session?.sessionSecret || "");
818
+ if (!/^runtime_[a-f0-9]{48}$/.test(secret)) throw failure("neutral_host_assembly_runtime_invalid");
819
+ return {
820
+ "x-hellolynk-runtime-session-id": safeId(session.sessionId, "neutral_host_assembly_runtime_invalid"),
821
+ "x-hellolynk-runtime-session-secret": secret,
822
+ "x-hellolynk-e2ee-version": "2",
823
+ };
824
+ }
825
+
826
+ function runtimeSessionKey(profile) {
827
+ const digest = crypto.createHash("sha256").update(JSON.stringify({
828
+ profileId: profile.profileId,
829
+ baseUrl: profile.baseUrl,
830
+ principalId: profile.principalId,
831
+ agentId: profile.agentId,
832
+ delegateId: profile.delegateId,
833
+ hostId: profile.hostId,
834
+ authorizationVersion: profile.authorizationVersion,
835
+ deviceId: profile.deviceId,
836
+ })).digest("hex").slice(0, 40);
837
+ return `host-runtime-${digest}`;
838
+ }
839
+
840
+ function settle(operation) {
841
+ return Promise.resolve().then(operation).then((value) => ({ value, error: null }), (error) => ({ value: null, error }));
842
+ }
843
+
844
+ function bindTransportCredentialProvider(provider, owner) {
845
+ if (!provider || typeof provider.get !== "function") {
846
+ throw failure("host_transport_credential_provider_required");
847
+ }
848
+ return Object.freeze({ get: () => provider.get(owner) });
849
+ }
850
+
851
+ function bindTransportSessionProvider(provider, owner) {
852
+ if (!provider || typeof provider.get !== "function" || typeof provider.save !== "function") {
853
+ throw failure("host_transport_session_provider_required");
854
+ }
855
+ return Object.freeze({
856
+ get: () => provider.get(owner),
857
+ save: (value) => provider.save(value, owner),
858
+ ...(typeof provider.remove === "function" ? { remove: () => provider.remove(owner) } : {}),
859
+ });
860
+ }
861
+
862
+ async function activeInstallation(store) {
863
+ const status = await store.status();
864
+ const active = status.installations.filter((item) => item.state === "active");
865
+ if (active.length !== 1) throw failure(active.length ? "neutral_host_assembly_installation_ambiguous" : "neutral_host_assembly_installation_missing");
866
+ return active[0];
867
+ }
868
+
869
+ function normalizeDescriptors(value) {
870
+ if (!Array.isArray(value) || value.length < 1 || value.length > 64) throw failure("neutral_host_assembly_profiles_required");
871
+ const seen = new Set();
872
+ return value.map((descriptor) => {
873
+ if (!descriptor || typeof descriptor !== "object" || Array.isArray(descriptor)
874
+ || Object.keys(descriptor).length !== 1 || typeof descriptor.profileFile !== "string") {
875
+ throw failure("neutral_host_assembly_profile_descriptor_invalid");
876
+ }
877
+ const profileFile = path.resolve(descriptor.profileFile);
878
+ if (seen.has(profileFile)) throw failure("neutral_host_assembly_profile_duplicate");
879
+ seen.add(profileFile);
880
+ return Object.freeze({ profileFile });
881
+ });
882
+ }
883
+
884
+ function validateProfile(profile, installation) {
885
+ if (profile.schemaVersion !== 2) throw failure("neutral_host_assembly_schema_2_required");
886
+ if (!profile.hellolynkId) throw failure("neutral_host_assembly_hellolynk_id_required");
887
+ if (profile.region !== installation.region || profile.principalId !== installation.principalId
888
+ || profile.hostId !== installation.hostInstallationId) {
889
+ throw failure("neutral_host_assembly_profile_owner_mismatch");
890
+ }
891
+ }
892
+
893
+ function validateBindings(profiles, installation) {
894
+ if (profiles.length !== installation.delegateBindings.length) throw failure("neutral_host_assembly_binding_set_mismatch");
895
+ const delegates = new Set();
896
+ for (const { profile } of profiles) {
897
+ if (delegates.has(profile.delegateId)) throw failure("neutral_host_assembly_delegate_duplicate");
898
+ delegates.add(profile.delegateId);
899
+ const binding = installation.delegateBindings.find((item) => item.profileId === profile.profileId);
900
+ if (!binding || binding.delegateId !== profile.delegateId
901
+ || binding.authorizationVersion !== profile.authorizationVersion) {
902
+ throw failure("neutral_host_assembly_binding_mismatch");
903
+ }
904
+ }
905
+ }
906
+
907
+ function commonProfileClientRoot(profiles) {
908
+ const roots = profiles.map(({ descriptor, profile }) => {
909
+ const profileFile = path.resolve(descriptor.profileFile);
910
+ const profileDirectory = path.dirname(profileFile);
911
+ const expectedFile = path.join(profileDirectory, `${profile.profileId}.json`);
912
+ if (path.basename(profileDirectory).toLowerCase() !== "profiles"
913
+ || !sameFilesystemPath(profileFile, expectedFile)) {
914
+ throw failure("neutral_host_assembly_profile_location_invalid");
915
+ }
916
+ return path.dirname(profileDirectory);
917
+ });
918
+ const first = roots[0];
919
+ if (roots.some((root) => !sameFilesystemPath(root, first))) {
920
+ throw failure("neutral_host_assembly_client_root_mismatch");
921
+ }
922
+ return first;
923
+ }
924
+
925
+ function sameFilesystemPath(left, right) {
926
+ const normalizedLeft = path.resolve(left);
927
+ const normalizedRight = path.resolve(right);
928
+ return process.platform === "win32"
929
+ ? normalizedLeft.toLowerCase() === normalizedRight.toLowerCase()
930
+ : normalizedLeft === normalizedRight;
931
+ }
932
+
933
+ function commonValue(profiles, field, code) {
934
+ const first = profiles[0].profile[field];
935
+ if (profiles.some(({ profile }) => profile[field] !== first)) throw failure(code);
936
+ return first;
937
+ }
938
+
939
+ function absoluteRoot(value, code) {
940
+ if (!value || !path.isAbsolute(String(value))) throw failure(code);
941
+ return path.resolve(String(value));
942
+ }
943
+
944
+ function safeId(value, code) {
945
+ const normalized = String(value || "");
946
+ if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,199}$/.test(normalized)) throw failure(code);
947
+ return normalized;
948
+ }
949
+
950
+ function failure(code) { const error = new Error(code); error.code = code; return error; }
951
+
952
+ function safeOutboundCode(value) {
953
+ const code = String(value || "delivery_state_unknown");
954
+ return /^[a-z][a-z0-9_]{1,127}$/.test(code) ? code : "delivery_state_unknown";
955
+ }