@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.
- package/.agents/plugins/marketplace.json +20 -0
- package/LICENSE +92 -0
- package/README.md +101 -2
- package/bin/hellolynk-client-setup.mjs +2748 -0
- package/bin/hellolynk-mcp-config-shape.mjs +23 -0
- package/hellolynk-client-pack-manifest.json +619 -0
- package/hellolynk-client-pack-pin.json +4 -0
- package/package.json +58 -4
- package/plugins/hellolynk-codex/.app.json +3 -0
- package/plugins/hellolynk-codex/.codex-plugin/plugin.json +38 -0
- package/plugins/hellolynk-codex/.mcp.json +19 -0
- package/plugins/hellolynk-codex/assets/hellolynk-mark.png +0 -0
- package/plugins/hellolynk-codex/package.json +11 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
- package/plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/plugins/hellolynk-codex/runtime/connect-activate.mjs +837 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-agent-configurator.mjs +701 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +180 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs +770 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +191 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +31 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +453 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs +435 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-host-auth.mjs +347 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-host-transport-client.mjs +946 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-host.mjs +2374 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-instance-conversation.mjs +185 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contacts.mjs +1228 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-migration.mjs +766 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +747 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-messaging.mjs +3043 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-local-process-lock.mjs +157 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-model-inbox.mjs +230 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-model-notices.cjs +60 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs +280 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-notice.mjs +50 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +277 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs +471 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
- package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +234 -0
- package/plugins/hellolynk-codex/runtime/neutral-host-bootstrap.mjs +64 -0
- package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +70 -0
- package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +156 -0
- package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +358 -0
- package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +4 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1 +281 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +844 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
- package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
- package/plugins/hellolynk-codex/runtime/workbench/candidate-bootstrap.mjs +54 -0
- package/plugins/hellolynk-codex/runtime/workbench/contract.mjs +1314 -0
- package/plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json +215 -0
- package/plugins/hellolynk-codex/runtime/workbench/host-capabilities.mjs +80 -0
- package/plugins/hellolynk-codex/runtime/workbench/i18n.mjs +1127 -0
- package/plugins/hellolynk-codex/runtime/workbench/loopback-surface.mjs +740 -0
- package/plugins/hellolynk-codex/runtime/workbench/mcp-app-bundle.mjs +97 -0
- package/plugins/hellolynk-codex/runtime/workbench/service.mjs +485 -0
- package/plugins/hellolynk-codex/runtime/workbench/state.mjs +421 -0
- package/plugins/hellolynk-codex/runtime/workbench/transports.mjs +171 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/app.mjs +6120 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/connection-form.mjs +155 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/hellolynk-mark.png +0 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/http-transport.mjs +247 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/arrow-left.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/close.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/contacts.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/inbox.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/refresh.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/search.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/security.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/icons/settings.svg +3 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/index.html +139 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/loopback-entry.mjs +26 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-client.mjs +151 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
- package/plugins/hellolynk-codex/runtime/workbench/ui/styles.css +686 -0
- package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +520 -0
- package/plugins/hellolynk-codex/server/mcp-proxy.mjs +306 -0
- package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -0
- package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -0
- package/plugins/hellolynk-codex/server/native-reply-routes.mjs +32 -0
- package/plugins/hellolynk-codex/server/native-task-client.mjs +382 -0
- package/plugins/hellolynk-codex/server/native-task-runtime.mjs +8 -0
- package/plugins/hellolynk-codex/server/neutral-host-adapter.mjs +649 -0
- package/plugins/hellolynk-codex/server/workbench-runtime.mjs +180 -0
- package/plugins/hellolynk-codex/skills/hellolynk-open/SKILL.md +11 -0
- package/plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml +4 -0
- package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
|
@@ -0,0 +1,2721 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import { lstat, mkdir } from "node:fs/promises";
|
|
3
|
+
import { createConnection } from "node:net";
|
|
4
|
+
import { basename, dirname, isAbsolute, join, relative, resolve } from "node:path";
|
|
5
|
+
import { spawn } from "node:child_process";
|
|
6
|
+
import { fileURLToPath } from "node:url";
|
|
7
|
+
import runtimeSafety from "./hellolynk-runtime-safety.cjs";
|
|
8
|
+
import { readClientProfile } from "./hellolynk-client-profile.mjs";
|
|
9
|
+
import { assertClientAccountCurrent, clientAccountBaseRoot, resolveClientAccountRoot } from "./hellolynk-client-account.mjs";
|
|
10
|
+
export { resolveClientAccountRoot } from "./hellolynk-client-account.mjs";
|
|
11
|
+
import {
|
|
12
|
+
assertCapabilityRecord,
|
|
13
|
+
assertIdentifier,
|
|
14
|
+
HELLOLYNK_HOST_CAPABILITY_CONTRACT,
|
|
15
|
+
} from "./hellolynk-host-auth.mjs";
|
|
16
|
+
import {
|
|
17
|
+
createNeutralHostInstallationStore,
|
|
18
|
+
defaultNeutralHostStoreRoot,
|
|
19
|
+
} from "./hellolynk-neutral-host-installation-store.mjs";
|
|
20
|
+
|
|
21
|
+
const { sanitizeHelloLynkChildEnvironment } = runtimeSafety;
|
|
22
|
+
|
|
23
|
+
export const HELLOLYNK_HOST_PROTECTED_STORE_CONTRACT = "hellolynk-host-protected-store-v1";
|
|
24
|
+
export const HELLOLYNK_HOST_TRANSPORT_CREDENTIAL_CONTRACT = "hellolynk-host-transport-credential-v1";
|
|
25
|
+
export const HELLOLYNK_HOST_TRANSPORT_SESSION_CONTRACT = "hellolynk-host-transport-session-v1";
|
|
26
|
+
export const HELLOLYNK_CODEX_NEUTRAL_BINDING_CONTRACT = "hellolynk-codex-neutral-binding-v1";
|
|
27
|
+
export const HELLOLYNK_OPENCLAW_NEUTRAL_BINDING_CONTRACT = "hellolynk-openclaw-neutral-binding-v1";
|
|
28
|
+
export const HELLOLYNK_TRAE_WORK_NEUTRAL_BINDING_CONTRACT = "hellolynk-trae-work-neutral-binding-v1";
|
|
29
|
+
export const HELLOLYNK_WORKBUDDY_NEUTRAL_BINDING_CONTRACT = "hellolynk-workbuddy-neutral-binding-v1";
|
|
30
|
+
export const HELLOLYNK_CLAUDE_CODE_NEUTRAL_BINDING_CONTRACT = "hellolynk-claude-code-neutral-binding-v1";
|
|
31
|
+
export const HELLOLYNK_CURSOR_NEUTRAL_BINDING_CONTRACT = "hellolynk-cursor-neutral-binding-v1";
|
|
32
|
+
export const HELLOLYNK_DSH_NEUTRAL_BINDING_CONTRACT = "hellolynk-dsh-neutral-binding-v1";
|
|
33
|
+
export const HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
34
|
+
"app-data:authorized-messaging",
|
|
35
|
+
"app-data:changes-read",
|
|
36
|
+
"app-data:contact-requests-read",
|
|
37
|
+
"app-data:contacts-manage",
|
|
38
|
+
"app-data:contacts-read",
|
|
39
|
+
"app-data:human-send-request",
|
|
40
|
+
"app-data:inbox-manage",
|
|
41
|
+
"app-data:inbox-read",
|
|
42
|
+
"app-data:outbox-read",
|
|
43
|
+
"app-data:outbox-retry",
|
|
44
|
+
"attachments:manage",
|
|
45
|
+
"surface:human-ui",
|
|
46
|
+
"ui:launch",
|
|
47
|
+
]);
|
|
48
|
+
export const HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
49
|
+
...HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES,
|
|
50
|
+
]);
|
|
51
|
+
const HELLOLYNK_CODEX_NEUTRAL_PREVIOUS_SCOPES = Object.freeze(
|
|
52
|
+
HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES.filter((scope) => scope !== "app-data:authorized-messaging"),
|
|
53
|
+
);
|
|
54
|
+
const HELLOLYNK_OPENCLAW_NEUTRAL_PREVIOUS_SCOPES = Object.freeze([
|
|
55
|
+
...HELLOLYNK_CODEX_NEUTRAL_PREVIOUS_SCOPES,
|
|
56
|
+
]);
|
|
57
|
+
export const HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
58
|
+
"app-data:authorized-messaging",
|
|
59
|
+
"app-data:changes-read",
|
|
60
|
+
"app-data:contact-requests-read",
|
|
61
|
+
"app-data:contacts-manage",
|
|
62
|
+
"app-data:contacts-read",
|
|
63
|
+
"app-data:human-send-request",
|
|
64
|
+
"app-data:inbox-manage",
|
|
65
|
+
"app-data:inbox-read",
|
|
66
|
+
"app-data:outbox-read",
|
|
67
|
+
"app-data:outbox-retry",
|
|
68
|
+
"attachments:manage",
|
|
69
|
+
"status:read",
|
|
70
|
+
"surface:human-ui",
|
|
71
|
+
"ui:launch",
|
|
72
|
+
]);
|
|
73
|
+
export const HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
74
|
+
...HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES,
|
|
75
|
+
]);
|
|
76
|
+
export const HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
77
|
+
...HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES,
|
|
78
|
+
]);
|
|
79
|
+
export const HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES = Object.freeze([
|
|
80
|
+
"app-data:authorized-messaging",
|
|
81
|
+
"app-data:changes-read",
|
|
82
|
+
"app-data:contact-requests-read",
|
|
83
|
+
"app-data:contacts-manage",
|
|
84
|
+
"app-data:contacts-read",
|
|
85
|
+
"app-data:human-send-request",
|
|
86
|
+
"app-data:inbox-manage",
|
|
87
|
+
"app-data:inbox-read",
|
|
88
|
+
"app-data:outbox-read",
|
|
89
|
+
"app-data:outbox-retry",
|
|
90
|
+
"attachments:manage",
|
|
91
|
+
"status:read",
|
|
92
|
+
"surface:human-ui",
|
|
93
|
+
"ui:launch",
|
|
94
|
+
]);
|
|
95
|
+
// DSH's native assistant may read/reply only through contact-authorized routes.
|
|
96
|
+
// Human Workbench commands still require independently issued single-use grants.
|
|
97
|
+
const HELLOLYNK_CURSOR_NEUTRAL_PREVIOUS_SCOPES = Object.freeze(
|
|
98
|
+
HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES.filter((scope) => scope !== "app-data:authorized-messaging"),
|
|
99
|
+
);
|
|
100
|
+
export const HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES = Object.freeze([...HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES]);
|
|
101
|
+
const HELLOLYNK_DSH_NEUTRAL_PREVIOUS_SCOPES = Object.freeze([...HELLOLYNK_CURSOR_NEUTRAL_PREVIOUS_SCOPES]);
|
|
102
|
+
const HELLOLYNK_TRAE_WORK_NEUTRAL_PREVIOUS_SCOPES = Object.freeze(
|
|
103
|
+
HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES.filter((scope) => scope !== "app-data:authorized-messaging"),
|
|
104
|
+
);
|
|
105
|
+
const HELLOLYNK_WORKBUDDY_NEUTRAL_PREVIOUS_SCOPES = Object.freeze([
|
|
106
|
+
...HELLOLYNK_TRAE_WORK_NEUTRAL_PREVIOUS_SCOPES,
|
|
107
|
+
]);
|
|
108
|
+
const HELLOLYNK_TRAE_WORK_NEUTRAL_LEGACY_SCOPES = Object.freeze([
|
|
109
|
+
"app-data:human-send-request",
|
|
110
|
+
"attachments:manage",
|
|
111
|
+
"status:read",
|
|
112
|
+
"surface:human-ui",
|
|
113
|
+
]);
|
|
114
|
+
const HELLOLYNK_WORKBUDDY_NEUTRAL_LEGACY_SCOPES = Object.freeze([
|
|
115
|
+
...HELLOLYNK_TRAE_WORK_NEUTRAL_LEGACY_SCOPES,
|
|
116
|
+
]);
|
|
117
|
+
const CAPABILITY_ID_PATTERN = /^cap_[A-Za-z0-9_-]{32}$/;
|
|
118
|
+
const TRANSPORT_CREDENTIAL_PATTERN = /^hellolynk_host_transport_v1_([A-Za-z0-9_-]{3,127})\.([A-Za-z0-9_-]{32,})$/;
|
|
119
|
+
const TRANSPORT_SESSION_ID_PATTERN = /^hosttransportsession_[A-Za-z0-9_-]{16,}$/;
|
|
120
|
+
const TRANSPORT_SESSION_SECRET_PATTERN = /^hostsession_[a-f0-9]{48}$/;
|
|
121
|
+
const SCRIPT_PATH = join(
|
|
122
|
+
dirname(fileURLToPath(import.meta.url)),
|
|
123
|
+
"..",
|
|
124
|
+
"platform",
|
|
125
|
+
"windows",
|
|
126
|
+
"hellolynk-host-protected-store.ps1",
|
|
127
|
+
);
|
|
128
|
+
const ROOT_INVOKE_QUEUES = new Map();
|
|
129
|
+
const NEUTRAL_BINDING_READS = new Map();
|
|
130
|
+
const MAX_NEUTRAL_BINDING_READS = 256;
|
|
131
|
+
const NEUTRAL_BINDING_READ = /^(Resolve|Verify)(Codex|OpenClaw|TraeWork|WorkBuddy|ClaudeCode|Cursor|Dsh)NeutralBinding$/;
|
|
132
|
+
const PERSISTENT_CREDENTIAL_EXPIRES_AT = 253402300799999;
|
|
133
|
+
const LEGACY_CREDENTIAL_LIFETIME_MS = 30 * 24 * 60 * 60 * 1000;
|
|
134
|
+
const READ_ONLY_COMMANDS = new Set([
|
|
135
|
+
"LoadDeviceEnrollment",
|
|
136
|
+
"Status", "VerifyCodexNeutralBinding", "VerifyOpenClawNeutralBinding",
|
|
137
|
+
"VerifyTraeWorkNeutralBinding", "VerifyWorkBuddyNeutralBinding",
|
|
138
|
+
"VerifyClaudeCodeNeutralBinding",
|
|
139
|
+
"VerifyCursorNeutralBinding", "VerifyDshNeutralBinding", "VerifyTransport", "VerifyTransportCredentialLifetimeMigration",
|
|
140
|
+
"ResolveCodexNeutralBinding", "ResolveOpenClawNeutralBinding", "ResolveTraeWorkNeutralBinding",
|
|
141
|
+
"ResolveWorkBuddyNeutralBinding", "ResolveClaudeCodeNeutralBinding", "ResolveCursorNeutralBinding", "ResolveDshNeutralBinding",
|
|
142
|
+
]);
|
|
143
|
+
|
|
144
|
+
export function defaultClaudeCodeStoreRoot(env = process.env) {
|
|
145
|
+
return defaultNeutralHostStoreRoot("win32", env);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function assertClaudeCodeStoreRoot(root) {
|
|
149
|
+
const base = clientAccountBaseRoot(root);
|
|
150
|
+
if (basename(base) !== "host") {
|
|
151
|
+
throw codeError("neutral_claude_code_store_root_invalid");
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
export function createHostProtectedStore(options = {}) {
|
|
156
|
+
const platform = options.platform || process.platform;
|
|
157
|
+
if (platform !== "win32") throw codeError("host_protected_store_platform_unsupported");
|
|
158
|
+
const root = assertRoot(options.root || defaultNeutralHostStoreRoot(platform, options.env));
|
|
159
|
+
const helperPath = resolve(options.helperPath || SCRIPT_PATH);
|
|
160
|
+
const clock = normalizeClock(options.clock);
|
|
161
|
+
const random = typeof options.randomBytes === "function" ? options.randomBytes : randomBytes;
|
|
162
|
+
const readOnly = options.readOnly === true;
|
|
163
|
+
const installationStore = options.installationStore || createNeutralHostInstallationStore(readOnly ? {
|
|
164
|
+
root,
|
|
165
|
+
readOnly: true,
|
|
166
|
+
verifyPath: ({ path, directory }) => verifyHostProtectedStorePath({
|
|
167
|
+
root,
|
|
168
|
+
path,
|
|
169
|
+
directory,
|
|
170
|
+
helperPath,
|
|
171
|
+
signal: options.signal,
|
|
172
|
+
}),
|
|
173
|
+
} : { root });
|
|
174
|
+
const nativeInvoke = (command, invokeOptions) => invokeWindowsStore({
|
|
175
|
+
command, root, helperPath, verifyOnly: readOnly, ...(readOnly ? { signal: options.signal } : {}), ...invokeOptions,
|
|
176
|
+
});
|
|
177
|
+
const baseInvoke = options.invoke || ((command, invokeOptions) => serializeRootInvoke(root, () => nativeInvoke(command, invokeOptions)));
|
|
178
|
+
const invoke = async (command, invokeOptions = {}) => {
|
|
179
|
+
if (readOnly && !READ_ONLY_COMMANDS.has(command)) throw codeError("host_protected_store_read_only");
|
|
180
|
+
// Each peer lookup creates a read-only store. Reuse its successful DPAPI
|
|
181
|
+
// result across those instances, while checking live paths and the file
|
|
182
|
+
// on every use. Registration/account checks remain in the public methods.
|
|
183
|
+
if (readOnly && !options.invoke && NEUTRAL_BINDING_READ.test(command)) {
|
|
184
|
+
return readNeutralBinding(command, invokeOptions);
|
|
185
|
+
}
|
|
186
|
+
try {
|
|
187
|
+
return await baseInvoke(command, invokeOptions);
|
|
188
|
+
} finally {
|
|
189
|
+
if (!READ_ONLY_COMMANDS.has(command)) {
|
|
190
|
+
for (const [key, entry] of NEUTRAL_BINDING_READS) {
|
|
191
|
+
if (entry.root === root) NEUTRAL_BINDING_READS.delete(key);
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
};
|
|
196
|
+
const capabilityStorePath = join(root, "capabilities-v1.dpapi.clixml");
|
|
197
|
+
const capabilityCache = new Map();
|
|
198
|
+
const capabilityLoadFlights = new Map();
|
|
199
|
+
let capabilityCacheFingerprint = null;
|
|
200
|
+
|
|
201
|
+
async function readNeutralBinding(command, invokeOptions, queued = false) {
|
|
202
|
+
const key = JSON.stringify([root, helperPath, command.replace(/^Verify/, "Resolve")]);
|
|
203
|
+
const signal = options.signal;
|
|
204
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
205
|
+
signal?.throwIfAborted();
|
|
206
|
+
// A cold native read already verifies root and credential-file ACLs,
|
|
207
|
+
// with the file check under its data mutex. Reuse that result only while
|
|
208
|
+
// protected paths remain intact, without a second VerifyPath process.
|
|
209
|
+
const before = await protectedPathFingerprint(root, capabilityStorePath, helperPath, false, signal);
|
|
210
|
+
const cached = NEUTRAL_BINDING_READS.get(key);
|
|
211
|
+
if (before && cached?.fingerprint === before
|
|
212
|
+
&& await neutralBindingPathsPresent(cached.result.binding, signal)) {
|
|
213
|
+
signal?.throwIfAborted();
|
|
214
|
+
const result = structuredClone(cached.result);
|
|
215
|
+
// Verify keeps its original public-only contract. Resolve's caller
|
|
216
|
+
// decodes a fresh Buffer, so wiping it cannot damage the cached copy.
|
|
217
|
+
if (command.startsWith("Verify")) delete result.record.secret;
|
|
218
|
+
return result;
|
|
219
|
+
}
|
|
220
|
+
// Keep warm reads independent of unrelated native work. On a miss,
|
|
221
|
+
// recheck after entering the existing root queue and publish the result
|
|
222
|
+
// before releasing it, so concurrent callers do not repeat the helper.
|
|
223
|
+
if (!queued) return serializeRootInvoke(root, () => readNeutralBinding(command, invokeOptions, true));
|
|
224
|
+
NEUTRAL_BINDING_READS.delete(key);
|
|
225
|
+
const result = await nativeInvoke(command, invokeOptions);
|
|
226
|
+
const after = await protectedPathFingerprint(root, capabilityStorePath, helperPath, false, signal);
|
|
227
|
+
signal?.throwIfAborted();
|
|
228
|
+
if (before !== after) continue;
|
|
229
|
+
if (before && command.startsWith("Resolve")) {
|
|
230
|
+
if (NEUTRAL_BINDING_READS.size >= MAX_NEUTRAL_BINDING_READS) {
|
|
231
|
+
NEUTRAL_BINDING_READS.delete(NEUTRAL_BINDING_READS.keys().next().value);
|
|
232
|
+
}
|
|
233
|
+
NEUTRAL_BINDING_READS.set(key, { root, fingerprint: after, result: structuredClone(result) });
|
|
234
|
+
}
|
|
235
|
+
return result;
|
|
236
|
+
}
|
|
237
|
+
throw codeError("host_protected_store_changed_during_read");
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
function accountStore(client) {
|
|
241
|
+
if (options.followClientAccount === false) return null;
|
|
242
|
+
const selected = resolveClientAccountRoot(client, root);
|
|
243
|
+
if (selected === root) return null;
|
|
244
|
+
return createHostProtectedStore({ ...options, root: selected, installationStore: undefined });
|
|
245
|
+
}
|
|
246
|
+
function currentCapability(record) {
|
|
247
|
+
assertClientAccountCurrent(record.adapterId, root);
|
|
248
|
+
return record;
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
function invalidateCapabilityCache() {
|
|
252
|
+
capabilityCacheFingerprint = null;
|
|
253
|
+
capabilityCache.clear();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
async function readCapabilityStoreFingerprint() {
|
|
257
|
+
try {
|
|
258
|
+
const metadata = await lstat(capabilityStorePath, { bigint: true });
|
|
259
|
+
return [
|
|
260
|
+
metadata.dev,
|
|
261
|
+
metadata.ino,
|
|
262
|
+
metadata.size,
|
|
263
|
+
metadata.mtimeNs,
|
|
264
|
+
metadata.birthtimeNs,
|
|
265
|
+
].join(":");
|
|
266
|
+
} catch (error) {
|
|
267
|
+
if (error?.code === "ENOENT") return "missing";
|
|
268
|
+
throw error;
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function resolveCapability(capabilityId) {
|
|
273
|
+
const id = assertCapabilityId(capabilityId);
|
|
274
|
+
for (let attempt = 0; attempt < 2; attempt += 1) {
|
|
275
|
+
const fingerprint = await readCapabilityStoreFingerprint();
|
|
276
|
+
if (fingerprint !== capabilityCacheFingerprint) {
|
|
277
|
+
capabilityCacheFingerprint = fingerprint;
|
|
278
|
+
capabilityCache.clear();
|
|
279
|
+
}
|
|
280
|
+
if (capabilityCache.has(id)) return currentCapability(capabilityCache.get(id));
|
|
281
|
+
const existingFlight = capabilityLoadFlights.get(id);
|
|
282
|
+
const flight = existingFlight?.fingerprint === fingerprint
|
|
283
|
+
? existingFlight
|
|
284
|
+
: {
|
|
285
|
+
fingerprint,
|
|
286
|
+
promise: load(id),
|
|
287
|
+
};
|
|
288
|
+
if (flight !== existingFlight) capabilityLoadFlights.set(id, flight);
|
|
289
|
+
try {
|
|
290
|
+
const record = await flight.promise;
|
|
291
|
+
const currentFingerprint = await readCapabilityStoreFingerprint();
|
|
292
|
+
if (currentFingerprint !== fingerprint) {
|
|
293
|
+
capabilityCacheFingerprint = currentFingerprint;
|
|
294
|
+
capabilityCache.clear();
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
capabilityCache.set(id, record);
|
|
298
|
+
return currentCapability(record);
|
|
299
|
+
} finally {
|
|
300
|
+
if (capabilityLoadFlights.get(id) === flight) capabilityLoadFlights.delete(id);
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
throw codeError("host_protected_store_changed_during_read");
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
async function issue(input = {}) {
|
|
307
|
+
const record = createRecord(input, { clock, random });
|
|
308
|
+
let result;
|
|
309
|
+
try {
|
|
310
|
+
result = await invoke("Issue", { stdin: serializeRecord(record) });
|
|
311
|
+
} finally {
|
|
312
|
+
invalidateCapabilityCache();
|
|
313
|
+
}
|
|
314
|
+
return deserializeRecord(result?.record);
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
async function load(capabilityId) {
|
|
318
|
+
const id = assertCapabilityId(capabilityId);
|
|
319
|
+
const result = await invoke("Load", { capabilityId: id });
|
|
320
|
+
return deserializeRecord(result?.record);
|
|
321
|
+
}
|
|
322
|
+
|
|
323
|
+
async function revoke(capabilityId) {
|
|
324
|
+
const id = assertCapabilityId(capabilityId);
|
|
325
|
+
let result;
|
|
326
|
+
try {
|
|
327
|
+
result = await invoke("Revoke", { capabilityId: id });
|
|
328
|
+
} finally {
|
|
329
|
+
invalidateCapabilityCache();
|
|
330
|
+
}
|
|
331
|
+
return Object.freeze({
|
|
332
|
+
capabilityId: assertCapabilityId(result?.capabilityId),
|
|
333
|
+
state: result?.state === "revoked" ? "revoked" : fail("host_capability_state_invalid"),
|
|
334
|
+
revokedAt: assertTimestamp(result?.revokedAt),
|
|
335
|
+
});
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
async function rotate(capabilityId, input = {}) {
|
|
339
|
+
const oldId = assertCapabilityId(capabilityId);
|
|
340
|
+
const record = createRecord(input, { clock, random });
|
|
341
|
+
if (record.capabilityId === oldId) throw codeError("host_capability_rotation_id_reused");
|
|
342
|
+
let result;
|
|
343
|
+
try {
|
|
344
|
+
result = await invoke("Rotate", {
|
|
345
|
+
capabilityId: oldId,
|
|
346
|
+
stdin: serializeRecord(record),
|
|
347
|
+
});
|
|
348
|
+
} finally {
|
|
349
|
+
invalidateCapabilityCache();
|
|
350
|
+
}
|
|
351
|
+
if (result?.replacedCapabilityId !== oldId) throw codeError("host_capability_rotation_invalid");
|
|
352
|
+
return deserializeRecord(result.record);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
async function status() {
|
|
356
|
+
const result = await invoke("Status", {});
|
|
357
|
+
if (result?.contract !== HELLOLYNK_HOST_PROTECTED_STORE_CONTRACT || result?.protected !== true
|
|
358
|
+
|| Object.keys(result).sort().join(",") !== "contract,protected") {
|
|
359
|
+
throw codeError("host_protected_store_status_invalid");
|
|
360
|
+
}
|
|
361
|
+
return Object.freeze({ contract: result.contract, protected: true });
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
async function saveCodexNeutralBinding(input = {}) {
|
|
365
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
366
|
+
const delegateBinding = await resolveCodexInstallationBinding(installationStore, input, installation);
|
|
367
|
+
const descriptor = codexNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
368
|
+
const result = await invoke("SaveCodexNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
369
|
+
return assertPersistentNeutralBinding(publicCodexNeutralBinding(result?.binding), root);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
async function resolveCodexNeutralBinding() {
|
|
373
|
+
const selected = accountStore("codex");
|
|
374
|
+
if (selected) return selected.resolveCodexNeutralBinding();
|
|
375
|
+
const installationBefore = await resolveActiveInstallation(installationStore, {});
|
|
376
|
+
const result = await invoke("ResolveCodexNeutralBinding", {});
|
|
377
|
+
const binding = assertPersistentNeutralBinding(publicCodexNeutralBinding(result?.binding), root);
|
|
378
|
+
const capability = deserializeRecord(result?.record);
|
|
379
|
+
await assertCurrentCodexInstallationBinding(
|
|
380
|
+
installationStore,
|
|
381
|
+
installationBefore,
|
|
382
|
+
binding,
|
|
383
|
+
capability,
|
|
384
|
+
);
|
|
385
|
+
return Object.freeze({ ...binding, capability });
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
async function verifyCodexNeutralBinding() {
|
|
389
|
+
const selected = accountStore("codex");
|
|
390
|
+
if (selected) return selected.verifyCodexNeutralBinding();
|
|
391
|
+
const installationBefore = await resolveActiveInstallation(installationStore, {});
|
|
392
|
+
const result = await invoke("VerifyCodexNeutralBinding", {});
|
|
393
|
+
const binding = assertPersistentNeutralBinding(publicCodexNeutralBinding(result?.binding), root);
|
|
394
|
+
const capability = publicRecord(result?.record);
|
|
395
|
+
await assertCurrentCodexInstallationBinding(
|
|
396
|
+
installationStore,
|
|
397
|
+
installationBefore,
|
|
398
|
+
binding,
|
|
399
|
+
capability,
|
|
400
|
+
);
|
|
401
|
+
return Object.freeze({ ...binding, capability });
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
async function removeCodexNeutralBinding(input = {}) {
|
|
405
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
406
|
+
const delegateBinding = await resolveCodexInstallationBinding(installationStore, input, installation);
|
|
407
|
+
const expected = publicCodexNeutralBinding(input);
|
|
408
|
+
if (expected.region !== installation.region
|
|
409
|
+
|| expected.principalId !== installation.principalId
|
|
410
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
411
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
412
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
413
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
414
|
+
}
|
|
415
|
+
const result = await invoke("RemoveCodexNeutralBinding", {
|
|
416
|
+
stdin: JSON.stringify(expected),
|
|
417
|
+
});
|
|
418
|
+
if (result?.removed !== true) throw codeError("neutral_codex_binding_configuration_required");
|
|
419
|
+
return Object.freeze({ removed: true });
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
async function replaceCodexNeutralBinding(input = {}) {
|
|
423
|
+
const installation = await resolveActiveInstallation(installationStore, input.replacementBinding);
|
|
424
|
+
const delegateBinding = await resolveCodexInstallationBinding(
|
|
425
|
+
installationStore,
|
|
426
|
+
input.replacementBinding,
|
|
427
|
+
installation,
|
|
428
|
+
);
|
|
429
|
+
const previousBinding = publicCodexNeutralBinding(input.previousBinding);
|
|
430
|
+
const replacementBinding = codexNeutralBindingDescriptor(
|
|
431
|
+
input.replacementBinding,
|
|
432
|
+
installation,
|
|
433
|
+
delegateBinding,
|
|
434
|
+
);
|
|
435
|
+
const result = await invoke("ReplaceCodexNeutralBinding", {
|
|
436
|
+
stdin: JSON.stringify({ previousBinding, replacementBinding }),
|
|
437
|
+
});
|
|
438
|
+
return publicCodexNeutralBinding(result?.binding);
|
|
439
|
+
}
|
|
440
|
+
|
|
441
|
+
async function migrateCodexNeutralBinding(input = {}) {
|
|
442
|
+
return migrateNeutralBinding({
|
|
443
|
+
command: "MigrateCodexNeutralBinding",
|
|
444
|
+
input,
|
|
445
|
+
adapterId: "codex",
|
|
446
|
+
bindingCode: "neutral_codex_capability_binding_mismatch",
|
|
447
|
+
descriptor: codexNeutralBindingDescriptor,
|
|
448
|
+
publicBinding: publicCodexNeutralBinding,
|
|
449
|
+
resolveInstallationBinding: resolveCodexInstallationBinding,
|
|
450
|
+
assertCurrent: assertCurrentCodexInstallationBinding,
|
|
451
|
+
});
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
async function saveOpenClawNeutralBinding(input = {}) {
|
|
455
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
456
|
+
const delegateBinding = await resolveOpenClawInstallationBinding(installationStore, input, installation);
|
|
457
|
+
const descriptor = openClawNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
458
|
+
const result = await invoke("SaveOpenClawNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
459
|
+
return assertPersistentNeutralBinding(publicOpenClawNeutralBinding(result?.binding), root);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function resolveOpenClawNeutralBinding(input = {}) {
|
|
463
|
+
const selected = accountStore("openclaw");
|
|
464
|
+
if (selected) return selected.resolveOpenClawNeutralBinding(input);
|
|
465
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
466
|
+
const result = await invoke("ResolveOpenClawNeutralBinding", {});
|
|
467
|
+
const binding = assertPersistentNeutralBinding(publicOpenClawNeutralBinding(result?.binding), root);
|
|
468
|
+
const capability = deserializeRecord(result?.record);
|
|
469
|
+
await assertCurrentOpenClawInstallationBinding(
|
|
470
|
+
installationStore,
|
|
471
|
+
installationBefore,
|
|
472
|
+
binding,
|
|
473
|
+
capability,
|
|
474
|
+
input,
|
|
475
|
+
);
|
|
476
|
+
return Object.freeze({ ...binding, capability });
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
async function verifyOpenClawNeutralBinding(input = {}) {
|
|
480
|
+
const selected = accountStore("openclaw");
|
|
481
|
+
if (selected) return selected.verifyOpenClawNeutralBinding(input);
|
|
482
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
483
|
+
const result = await invoke("VerifyOpenClawNeutralBinding", {});
|
|
484
|
+
const binding = assertPersistentNeutralBinding(publicOpenClawNeutralBinding(result?.binding), root);
|
|
485
|
+
const capability = publicRecord(result?.record);
|
|
486
|
+
await assertCurrentOpenClawInstallationBinding(
|
|
487
|
+
installationStore,
|
|
488
|
+
installationBefore,
|
|
489
|
+
binding,
|
|
490
|
+
capability,
|
|
491
|
+
input,
|
|
492
|
+
);
|
|
493
|
+
return Object.freeze({ ...binding, capability });
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
async function removeOpenClawNeutralBinding(input = {}) {
|
|
497
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
498
|
+
const delegateBinding = await resolveOpenClawInstallationBinding(installationStore, input, installation);
|
|
499
|
+
const expected = publicOpenClawNeutralBinding(input);
|
|
500
|
+
if (expected.region !== installation.region
|
|
501
|
+
|| expected.principalId !== installation.principalId
|
|
502
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
503
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
504
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
505
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
506
|
+
}
|
|
507
|
+
const result = await invoke("RemoveOpenClawNeutralBinding", {
|
|
508
|
+
stdin: JSON.stringify(expected),
|
|
509
|
+
});
|
|
510
|
+
if (result?.removed !== true) throw codeError("neutral_openclaw_binding_configuration_required");
|
|
511
|
+
return Object.freeze({ removed: true });
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
async function migrateOpenClawNeutralBinding(input = {}) {
|
|
515
|
+
return migrateNeutralBinding({
|
|
516
|
+
command: "MigrateOpenClawNeutralBinding",
|
|
517
|
+
input,
|
|
518
|
+
adapterId: "openclaw",
|
|
519
|
+
bindingCode: "neutral_openclaw_capability_binding_mismatch",
|
|
520
|
+
descriptor: openClawNeutralBindingDescriptor,
|
|
521
|
+
publicBinding: publicOpenClawNeutralBinding,
|
|
522
|
+
resolveInstallationBinding: resolveOpenClawInstallationBinding,
|
|
523
|
+
assertCurrent: assertCurrentOpenClawInstallationBinding,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
async function saveTraeWorkNeutralBinding(input = {}) {
|
|
528
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
529
|
+
const delegateBinding = await resolveTraeWorkInstallationBinding(installationStore, input, installation);
|
|
530
|
+
const descriptor = traeWorkNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
531
|
+
const result = await invoke("SaveTraeWorkNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
532
|
+
return assertPersistentNeutralBinding(publicTraeWorkNeutralBinding(result?.binding), root);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
async function resolveTraeWorkNeutralBinding(input = {}) {
|
|
536
|
+
const selected = accountStore("trae-work");
|
|
537
|
+
if (selected) return selected.resolveTraeWorkNeutralBinding(input);
|
|
538
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
539
|
+
const result = await invoke("ResolveTraeWorkNeutralBinding", {});
|
|
540
|
+
const binding = assertPersistentNeutralBinding(publicTraeWorkNeutralBinding(result?.binding), root);
|
|
541
|
+
const capability = deserializeRecord(result?.record);
|
|
542
|
+
await assertCurrentTraeWorkInstallationBinding(
|
|
543
|
+
installationStore,
|
|
544
|
+
installationBefore,
|
|
545
|
+
binding,
|
|
546
|
+
capability,
|
|
547
|
+
input,
|
|
548
|
+
);
|
|
549
|
+
return Object.freeze({ ...binding, capability });
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
async function verifyTraeWorkNeutralBinding(input = {}) {
|
|
553
|
+
const selected = accountStore("trae-work");
|
|
554
|
+
if (selected) return selected.verifyTraeWorkNeutralBinding(input);
|
|
555
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
556
|
+
const result = await invoke("VerifyTraeWorkNeutralBinding", {});
|
|
557
|
+
const binding = assertPersistentNeutralBinding(publicTraeWorkNeutralBinding(result?.binding), root);
|
|
558
|
+
const capability = publicRecord(result?.record);
|
|
559
|
+
await assertCurrentTraeWorkInstallationBinding(
|
|
560
|
+
installationStore,
|
|
561
|
+
installationBefore,
|
|
562
|
+
binding,
|
|
563
|
+
capability,
|
|
564
|
+
input,
|
|
565
|
+
);
|
|
566
|
+
return Object.freeze({ ...binding, capability });
|
|
567
|
+
}
|
|
568
|
+
|
|
569
|
+
async function removeTraeWorkNeutralBinding(input = {}) {
|
|
570
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
571
|
+
const delegateBinding = await resolveTraeWorkInstallationBinding(installationStore, input, installation);
|
|
572
|
+
const expected = publicTraeWorkNeutralBinding(input);
|
|
573
|
+
if (expected.region !== installation.region
|
|
574
|
+
|| expected.principalId !== installation.principalId
|
|
575
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
576
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
577
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
578
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
579
|
+
}
|
|
580
|
+
const result = await invoke("RemoveTraeWorkNeutralBinding", {
|
|
581
|
+
stdin: JSON.stringify(expected),
|
|
582
|
+
});
|
|
583
|
+
if (result?.removed !== true) throw codeError("neutral_trae_work_binding_configuration_required");
|
|
584
|
+
return Object.freeze({ removed: true });
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
async function migrateTraeWorkNeutralBinding(input = {}) {
|
|
588
|
+
return migrateNeutralBinding({
|
|
589
|
+
command: "MigrateTraeWorkNeutralBinding",
|
|
590
|
+
input,
|
|
591
|
+
adapterId: "trae-work",
|
|
592
|
+
bindingCode: "neutral_trae_work_capability_binding_mismatch",
|
|
593
|
+
descriptor: traeWorkNeutralBindingDescriptor,
|
|
594
|
+
publicBinding: publicTraeWorkNeutralBinding,
|
|
595
|
+
resolveInstallationBinding: resolveTraeWorkInstallationBinding,
|
|
596
|
+
assertCurrent: assertCurrentTraeWorkInstallationBinding,
|
|
597
|
+
});
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
async function saveWorkBuddyNeutralBinding(input = {}) {
|
|
601
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
602
|
+
const delegateBinding = await resolveWorkBuddyInstallationBinding(installationStore, input, installation);
|
|
603
|
+
const descriptor = workBuddyNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
604
|
+
const result = await invoke("SaveWorkBuddyNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
605
|
+
return assertPersistentNeutralBinding(publicWorkBuddyNeutralBinding(result?.binding), root);
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
async function resolveWorkBuddyNeutralBinding(input = {}) {
|
|
609
|
+
const selected = accountStore("workbuddy");
|
|
610
|
+
if (selected) return selected.resolveWorkBuddyNeutralBinding(input);
|
|
611
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
612
|
+
const result = await invoke("ResolveWorkBuddyNeutralBinding", {});
|
|
613
|
+
const binding = assertPersistentNeutralBinding(publicWorkBuddyNeutralBinding(result?.binding), root);
|
|
614
|
+
const capability = deserializeRecord(result?.record);
|
|
615
|
+
await assertCurrentWorkBuddyInstallationBinding(
|
|
616
|
+
installationStore,
|
|
617
|
+
installationBefore,
|
|
618
|
+
binding,
|
|
619
|
+
capability,
|
|
620
|
+
input,
|
|
621
|
+
);
|
|
622
|
+
return Object.freeze({ ...binding, capability });
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
async function verifyWorkBuddyNeutralBinding(input = {}) {
|
|
626
|
+
const selected = accountStore("workbuddy");
|
|
627
|
+
if (selected) return selected.verifyWorkBuddyNeutralBinding(input);
|
|
628
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
629
|
+
const result = await invoke("VerifyWorkBuddyNeutralBinding", {});
|
|
630
|
+
const binding = assertPersistentNeutralBinding(publicWorkBuddyNeutralBinding(result?.binding), root);
|
|
631
|
+
const capability = publicRecord(result?.record);
|
|
632
|
+
await assertCurrentWorkBuddyInstallationBinding(
|
|
633
|
+
installationStore,
|
|
634
|
+
installationBefore,
|
|
635
|
+
binding,
|
|
636
|
+
capability,
|
|
637
|
+
input,
|
|
638
|
+
);
|
|
639
|
+
return Object.freeze({ ...binding, capability });
|
|
640
|
+
}
|
|
641
|
+
|
|
642
|
+
async function removeWorkBuddyNeutralBinding(input = {}) {
|
|
643
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
644
|
+
const delegateBinding = await resolveWorkBuddyInstallationBinding(installationStore, input, installation);
|
|
645
|
+
const expected = publicWorkBuddyNeutralBinding(input);
|
|
646
|
+
if (expected.region !== installation.region
|
|
647
|
+
|| expected.principalId !== installation.principalId
|
|
648
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
649
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
650
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
651
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
652
|
+
}
|
|
653
|
+
const result = await invoke("RemoveWorkBuddyNeutralBinding", {
|
|
654
|
+
stdin: JSON.stringify(expected),
|
|
655
|
+
});
|
|
656
|
+
if (result?.removed !== true) throw codeError("neutral_workbuddy_binding_configuration_required");
|
|
657
|
+
return Object.freeze({ removed: true });
|
|
658
|
+
}
|
|
659
|
+
|
|
660
|
+
async function migrateWorkBuddyNeutralBinding(input = {}) {
|
|
661
|
+
return migrateNeutralBinding({
|
|
662
|
+
command: "MigrateWorkBuddyNeutralBinding",
|
|
663
|
+
input,
|
|
664
|
+
adapterId: "workbuddy",
|
|
665
|
+
bindingCode: "neutral_workbuddy_capability_binding_mismatch",
|
|
666
|
+
descriptor: workBuddyNeutralBindingDescriptor,
|
|
667
|
+
publicBinding: publicWorkBuddyNeutralBinding,
|
|
668
|
+
resolveInstallationBinding: resolveWorkBuddyInstallationBinding,
|
|
669
|
+
assertCurrent: assertCurrentWorkBuddyInstallationBinding,
|
|
670
|
+
});
|
|
671
|
+
}
|
|
672
|
+
|
|
673
|
+
async function saveClaudeCodeNeutralBinding(input = {}) {
|
|
674
|
+
assertClaudeCodeStoreRoot(root);
|
|
675
|
+
assertClientAccountCurrent("claude-code", root);
|
|
676
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
677
|
+
const delegateBinding = await resolveClaudeCodeInstallationBinding(installationStore, input, installation);
|
|
678
|
+
const descriptor = claudeCodeNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
679
|
+
const result = await invoke("SaveClaudeCodeNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
680
|
+
return assertPersistentNeutralBinding(publicClaudeCodeNeutralBinding(result?.binding), root);
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
async function resolveClaudeCodeNeutralBinding(input = {}) {
|
|
684
|
+
assertClaudeCodeStoreRoot(root);
|
|
685
|
+
const selected = accountStore("claude-code");
|
|
686
|
+
if (selected) return selected.resolveClaudeCodeNeutralBinding(input);
|
|
687
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
688
|
+
const result = await invoke("ResolveClaudeCodeNeutralBinding", {});
|
|
689
|
+
const binding = assertPersistentNeutralBinding(publicClaudeCodeNeutralBinding(result?.binding), root);
|
|
690
|
+
const capability = deserializeRecord(result?.record);
|
|
691
|
+
await assertCurrentClaudeCodeInstallationBinding(
|
|
692
|
+
installationStore,
|
|
693
|
+
installationBefore,
|
|
694
|
+
binding,
|
|
695
|
+
capability,
|
|
696
|
+
input,
|
|
697
|
+
);
|
|
698
|
+
return Object.freeze({ ...binding, capability });
|
|
699
|
+
}
|
|
700
|
+
|
|
701
|
+
async function verifyClaudeCodeNeutralBinding(input = {}) {
|
|
702
|
+
assertClaudeCodeStoreRoot(root);
|
|
703
|
+
const selected = accountStore("claude-code");
|
|
704
|
+
if (selected) return selected.verifyClaudeCodeNeutralBinding(input);
|
|
705
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
706
|
+
const result = await invoke("VerifyClaudeCodeNeutralBinding", {});
|
|
707
|
+
const binding = assertPersistentNeutralBinding(publicClaudeCodeNeutralBinding(result?.binding), root);
|
|
708
|
+
const capability = publicRecord(result?.record);
|
|
709
|
+
await assertCurrentClaudeCodeInstallationBinding(
|
|
710
|
+
installationStore,
|
|
711
|
+
installationBefore,
|
|
712
|
+
binding,
|
|
713
|
+
capability,
|
|
714
|
+
input,
|
|
715
|
+
);
|
|
716
|
+
return Object.freeze({ ...binding, capability });
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
async function removeClaudeCodeNeutralBinding(input = {}) {
|
|
720
|
+
assertClaudeCodeStoreRoot(root);
|
|
721
|
+
assertClientAccountCurrent("claude-code", root);
|
|
722
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
723
|
+
const delegateBinding = await resolveClaudeCodeInstallationBinding(installationStore, input, installation);
|
|
724
|
+
const expected = publicClaudeCodeNeutralBinding(input);
|
|
725
|
+
if (expected.region !== installation.region
|
|
726
|
+
|| expected.principalId !== installation.principalId
|
|
727
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
728
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
729
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
730
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
731
|
+
}
|
|
732
|
+
const result = await invoke("RemoveClaudeCodeNeutralBinding", {
|
|
733
|
+
stdin: JSON.stringify(expected),
|
|
734
|
+
});
|
|
735
|
+
if (result?.removed !== true) throw codeError("neutral_claude_code_binding_configuration_required");
|
|
736
|
+
return Object.freeze({ removed: true });
|
|
737
|
+
}
|
|
738
|
+
|
|
739
|
+
async function migrateClaudeCodeNeutralBinding(input = {}) {
|
|
740
|
+
assertClaudeCodeStoreRoot(root);
|
|
741
|
+
assertClientAccountCurrent("claude-code", root);
|
|
742
|
+
return migrateNeutralBinding({
|
|
743
|
+
command: "MigrateClaudeCodeNeutralBinding",
|
|
744
|
+
input,
|
|
745
|
+
adapterId: "claude-code",
|
|
746
|
+
bindingCode: "neutral_claude_code_capability_binding_mismatch",
|
|
747
|
+
descriptor: claudeCodeNeutralBindingDescriptor,
|
|
748
|
+
publicBinding: publicClaudeCodeNeutralBinding,
|
|
749
|
+
resolveInstallationBinding: resolveClaudeCodeInstallationBinding,
|
|
750
|
+
assertCurrent: assertCurrentClaudeCodeInstallationBinding,
|
|
751
|
+
});
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
async function saveCursorNeutralBinding(input = {}) {
|
|
755
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
756
|
+
const delegateBinding = await resolveCursorInstallationBinding(installationStore, input, installation);
|
|
757
|
+
const descriptor = cursorNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
758
|
+
const result = await invoke("SaveCursorNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
759
|
+
return assertPersistentNeutralBinding(publicCursorNeutralBinding(result?.binding), root);
|
|
760
|
+
}
|
|
761
|
+
|
|
762
|
+
async function resolveCursorNeutralBinding(input = {}) {
|
|
763
|
+
const selected = accountStore("cursor");
|
|
764
|
+
if (selected) return selected.resolveCursorNeutralBinding(input);
|
|
765
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
766
|
+
const result = await invoke("ResolveCursorNeutralBinding", {});
|
|
767
|
+
const binding = assertPersistentNeutralBinding(publicCursorNeutralBinding(result?.binding), root);
|
|
768
|
+
const capability = deserializeRecord(result?.record);
|
|
769
|
+
await assertCurrentCursorInstallationBinding(
|
|
770
|
+
installationStore,
|
|
771
|
+
installationBefore,
|
|
772
|
+
binding,
|
|
773
|
+
capability,
|
|
774
|
+
input,
|
|
775
|
+
);
|
|
776
|
+
return Object.freeze({ ...binding, capability });
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
async function verifyCursorNeutralBinding(input = {}) {
|
|
780
|
+
const selected = accountStore("cursor");
|
|
781
|
+
if (selected) return selected.verifyCursorNeutralBinding(input);
|
|
782
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
783
|
+
const result = await invoke("VerifyCursorNeutralBinding", {});
|
|
784
|
+
const binding = assertPersistentNeutralBinding(publicCursorNeutralBinding(result?.binding), root);
|
|
785
|
+
const capability = publicRecord(result?.record);
|
|
786
|
+
await assertCurrentCursorInstallationBinding(
|
|
787
|
+
installationStore,
|
|
788
|
+
installationBefore,
|
|
789
|
+
binding,
|
|
790
|
+
capability,
|
|
791
|
+
input,
|
|
792
|
+
);
|
|
793
|
+
return Object.freeze({ ...binding, capability });
|
|
794
|
+
}
|
|
795
|
+
|
|
796
|
+
async function removeCursorNeutralBinding(input = {}) {
|
|
797
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
798
|
+
const delegateBinding = await resolveCursorInstallationBinding(installationStore, input, installation);
|
|
799
|
+
const expected = publicCursorNeutralBinding(input);
|
|
800
|
+
if (expected.region !== installation.region
|
|
801
|
+
|| expected.principalId !== installation.principalId
|
|
802
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
803
|
+
|| expected.profileId !== delegateBinding.profileId
|
|
804
|
+
|| expected.delegateId !== delegateBinding.delegateId) {
|
|
805
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
806
|
+
}
|
|
807
|
+
const result = await invoke("RemoveCursorNeutralBinding", {
|
|
808
|
+
stdin: JSON.stringify(expected),
|
|
809
|
+
});
|
|
810
|
+
if (result?.removed !== true) throw codeError("neutral_cursor_binding_configuration_required");
|
|
811
|
+
return Object.freeze({ removed: true });
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
async function migrateCursorNeutralBinding(input = {}) {
|
|
815
|
+
return migrateNeutralBinding({
|
|
816
|
+
command: "MigrateCursorNeutralBinding",
|
|
817
|
+
input,
|
|
818
|
+
adapterId: "cursor",
|
|
819
|
+
bindingCode: "neutral_cursor_capability_binding_mismatch",
|
|
820
|
+
descriptor: cursorNeutralBindingDescriptor,
|
|
821
|
+
publicBinding: publicCursorNeutralBinding,
|
|
822
|
+
resolveInstallationBinding: resolveCursorInstallationBinding,
|
|
823
|
+
assertCurrent: assertCurrentCursorInstallationBinding,
|
|
824
|
+
});
|
|
825
|
+
}
|
|
826
|
+
|
|
827
|
+
async function saveDshNeutralBinding(input = {}) {
|
|
828
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
829
|
+
const delegateBinding = await resolveDshInstallationBinding(installationStore, input, installation);
|
|
830
|
+
const descriptor = dshNeutralBindingDescriptor(input, installation, delegateBinding);
|
|
831
|
+
const result = await invoke("SaveDshNeutralBinding", { stdin: JSON.stringify(descriptor) });
|
|
832
|
+
return assertPersistentNeutralBinding(publicDshNeutralBinding(result?.binding), root);
|
|
833
|
+
}
|
|
834
|
+
|
|
835
|
+
async function resolveDshNeutralBinding(input = {}) {
|
|
836
|
+
const selected = accountStore("dsh");
|
|
837
|
+
if (selected) return selected.resolveDshNeutralBinding(input);
|
|
838
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
839
|
+
const result = await invoke("ResolveDshNeutralBinding", {});
|
|
840
|
+
const binding = assertPersistentNeutralBinding(publicDshNeutralBinding(result?.binding), root);
|
|
841
|
+
const capability = deserializeRecord(result?.record);
|
|
842
|
+
await assertCurrentDshInstallationBinding(installationStore, installationBefore, binding, capability, input);
|
|
843
|
+
return Object.freeze({ ...binding, capability });
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
async function verifyDshNeutralBinding(input = {}) {
|
|
847
|
+
const selected = accountStore("dsh");
|
|
848
|
+
if (selected) return selected.verifyDshNeutralBinding(input);
|
|
849
|
+
const installationBefore = await resolveActiveInstallation(installationStore, input);
|
|
850
|
+
const result = await invoke("VerifyDshNeutralBinding", {});
|
|
851
|
+
const binding = assertPersistentNeutralBinding(publicDshNeutralBinding(result?.binding), root);
|
|
852
|
+
const capability = publicRecord(result?.record);
|
|
853
|
+
await assertCurrentDshInstallationBinding(installationStore, installationBefore, binding, capability, input);
|
|
854
|
+
return Object.freeze({ ...binding, capability });
|
|
855
|
+
}
|
|
856
|
+
|
|
857
|
+
async function removeDshNeutralBinding(input = {}) {
|
|
858
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
859
|
+
const delegateBinding = await resolveDshInstallationBinding(installationStore, input, installation);
|
|
860
|
+
const expected = publicDshNeutralBinding(input);
|
|
861
|
+
if (expected.region !== installation.region || expected.principalId !== installation.principalId
|
|
862
|
+
|| expected.hostInstallationId !== installation.hostInstallationId
|
|
863
|
+
|| expected.profileId !== delegateBinding.profileId || expected.delegateId !== delegateBinding.delegateId) {
|
|
864
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
865
|
+
}
|
|
866
|
+
const result = await invoke("RemoveDshNeutralBinding", { stdin: JSON.stringify(expected) });
|
|
867
|
+
if (result?.removed !== true) throw codeError("neutral_dsh_binding_configuration_required");
|
|
868
|
+
return Object.freeze({ removed: true });
|
|
869
|
+
}
|
|
870
|
+
|
|
871
|
+
async function migrateDshNeutralBinding(input = {}) {
|
|
872
|
+
return migrateNeutralBinding({
|
|
873
|
+
command: "MigrateDshNeutralBinding", input, adapterId: "dsh",
|
|
874
|
+
bindingCode: "neutral_dsh_capability_binding_mismatch",
|
|
875
|
+
descriptor: dshNeutralBindingDescriptor, publicBinding: publicDshNeutralBinding,
|
|
876
|
+
resolveInstallationBinding: resolveDshInstallationBinding,
|
|
877
|
+
assertCurrent: assertCurrentDshInstallationBinding,
|
|
878
|
+
});
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
// Explicit recovery to the one supported prior DSH scope contract. Normal
|
|
882
|
+
// save/migrate still require current scopes; this cannot select arbitrary scopes.
|
|
883
|
+
async function restoreDshNeutralBinding(input = {}) {
|
|
884
|
+
if (!Array.isArray(input?.replacementBinding?.scopes)
|
|
885
|
+
|| !sameStrings(input.replacementBinding.scopes, HELLOLYNK_DSH_NEUTRAL_PREVIOUS_SCOPES)) {
|
|
886
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
887
|
+
}
|
|
888
|
+
return migrateNeutralBinding({
|
|
889
|
+
command: "RestoreDshNeutralBinding", input, adapterId: "dsh",
|
|
890
|
+
bindingCode: "neutral_dsh_capability_binding_mismatch",
|
|
891
|
+
descriptor: (value, installation, delegate) => dshNeutralBindingDescriptor(value, installation, delegate, true),
|
|
892
|
+
publicBinding: publicDshNeutralBinding,
|
|
893
|
+
resolveInstallationBinding: resolveDshInstallationBinding,
|
|
894
|
+
assertCurrent: assertCurrentDshInstallationBinding,
|
|
895
|
+
});
|
|
896
|
+
}
|
|
897
|
+
|
|
898
|
+
async function migrateNeutralBinding(spec) {
|
|
899
|
+
const previousCapabilityId = assertCapabilityId(spec.input?.previousCapabilityId);
|
|
900
|
+
const installation = await resolveActiveInstallation(
|
|
901
|
+
installationStore,
|
|
902
|
+
spec.input?.replacementBinding,
|
|
903
|
+
);
|
|
904
|
+
const delegateBinding = await spec.resolveInstallationBinding(
|
|
905
|
+
installationStore,
|
|
906
|
+
spec.input?.replacementBinding,
|
|
907
|
+
installation,
|
|
908
|
+
);
|
|
909
|
+
const previousBinding = spec.publicBinding(spec.input?.previousBinding);
|
|
910
|
+
if (previousBinding.region !== installation.region
|
|
911
|
+
|| previousBinding.principalId !== installation.principalId
|
|
912
|
+
|| previousBinding.hostInstallationId !== installation.hostInstallationId
|
|
913
|
+
|| previousBinding.profileId !== delegateBinding.profileId
|
|
914
|
+
|| previousBinding.delegateId !== delegateBinding.delegateId) {
|
|
915
|
+
throw codeError(spec.bindingCode);
|
|
916
|
+
}
|
|
917
|
+
const replacementBinding = spec.descriptor(
|
|
918
|
+
spec.input?.replacementBinding,
|
|
919
|
+
installation,
|
|
920
|
+
delegateBinding,
|
|
921
|
+
);
|
|
922
|
+
const replacementRecord = createRecord({
|
|
923
|
+
region: replacementBinding.region,
|
|
924
|
+
principalId: replacementBinding.principalId,
|
|
925
|
+
delegateId: replacementBinding.delegateId,
|
|
926
|
+
hostInstallationId: replacementBinding.hostInstallationId,
|
|
927
|
+
adapterId: spec.adapterId,
|
|
928
|
+
version: replacementBinding.version,
|
|
929
|
+
scopes: replacementBinding.scopes,
|
|
930
|
+
}, { clock, random });
|
|
931
|
+
if (replacementRecord.capabilityId === previousCapabilityId) {
|
|
932
|
+
replacementRecord.secretBuffer.fill(0);
|
|
933
|
+
throw codeError("host_capability_rotation_id_reused");
|
|
934
|
+
}
|
|
935
|
+
let result;
|
|
936
|
+
try {
|
|
937
|
+
result = await invoke(spec.command, {
|
|
938
|
+
capabilityId: previousCapabilityId,
|
|
939
|
+
stdin: JSON.stringify({
|
|
940
|
+
previousBinding,
|
|
941
|
+
replacementBinding,
|
|
942
|
+
replacementRecord: JSON.parse(serializeRecord(replacementRecord)),
|
|
943
|
+
}),
|
|
944
|
+
});
|
|
945
|
+
} finally {
|
|
946
|
+
replacementRecord.secretBuffer.fill(0);
|
|
947
|
+
invalidateCapabilityCache();
|
|
948
|
+
}
|
|
949
|
+
if (result?.replacedCapabilityId !== previousCapabilityId) {
|
|
950
|
+
throw codeError("host_capability_rotation_invalid");
|
|
951
|
+
}
|
|
952
|
+
let capability;
|
|
953
|
+
try {
|
|
954
|
+
const binding = spec.publicBinding(result?.binding);
|
|
955
|
+
capability = deserializeRecord(result?.record);
|
|
956
|
+
await spec.assertCurrent(installationStore, installation, binding, capability, binding);
|
|
957
|
+
return Object.freeze({ ...binding, capability });
|
|
958
|
+
} catch (error) {
|
|
959
|
+
capability?.secretBuffer?.fill(0);
|
|
960
|
+
throw error;
|
|
961
|
+
}
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
async function saveTransportCredential(input = {}, installationInput = {}) {
|
|
965
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
966
|
+
const record = transportCredentialRecord(input, installation);
|
|
967
|
+
if (record.expiresAt <= assertTimestamp(clock())) throw codeError("host_transport_credential_expired");
|
|
968
|
+
const result = await invoke("SaveTransportCredential", { stdin: JSON.stringify(record) });
|
|
969
|
+
return deserializeTransportCredential(result?.record, installation, clock(), { allowExpired: false });
|
|
970
|
+
}
|
|
971
|
+
|
|
972
|
+
async function disconnectLocalClient(client) {
|
|
973
|
+
if (!["codex", "openclaw", "trae-work", "workbuddy", "cursor", "dsh", "claude-code"].includes(client)) throw codeError("workbench_account_client_invalid");
|
|
974
|
+
if (client === "claude-code") assertClaudeCodeStoreRoot(root);
|
|
975
|
+
try {
|
|
976
|
+
const result = await invoke("DisconnectLocalClient", { stdin: JSON.stringify({ client }) });
|
|
977
|
+
if (result?.disconnected !== true || result.client !== client) throw codeError("workbench_disconnect_failed");
|
|
978
|
+
return Object.freeze({ disconnected: true });
|
|
979
|
+
} finally { invalidateCapabilityCache(); }
|
|
980
|
+
}
|
|
981
|
+
|
|
982
|
+
async function saveEnrollmentTransportCredential(input = {}, installationInput = {}) {
|
|
983
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
984
|
+
const record = transportCredentialRecord(input, installation);
|
|
985
|
+
if (record.expiresAt <= assertTimestamp(clock())) throw codeError("host_transport_credential_expired");
|
|
986
|
+
// The native transaction verifies the durable enrollment approval before
|
|
987
|
+
// replacing an older transport credential. Ordinary saves stay immutable.
|
|
988
|
+
const result = await invoke("SaveEnrollmentTransportCredential", { stdin: JSON.stringify(record) });
|
|
989
|
+
return deserializeTransportCredential(result?.record, installation, clock(), { allowExpired: false });
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
async function loadDeviceEnrollment() {
|
|
993
|
+
return validateDeviceEnrollmentResult(await invoke("LoadDeviceEnrollment"));
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
async function saveDeviceEnrollment(input = {}) {
|
|
997
|
+
if (!Number.isSafeInteger(input.expectedRevision) || input.expectedRevision < 0
|
|
998
|
+
|| !input.value || typeof input.value !== "object" || Array.isArray(input.value)) {
|
|
999
|
+
throw codeError("device_enrollment_store_invalid");
|
|
1000
|
+
}
|
|
1001
|
+
const stdin = JSON.stringify({ expectedRevision: input.expectedRevision, value: input.value });
|
|
1002
|
+
if (Buffer.byteLength(stdin, "utf8") > 60 * 1024) throw codeError("device_enrollment_store_invalid");
|
|
1003
|
+
return validateDeviceEnrollmentResult(await invoke("SaveDeviceEnrollment", { stdin }));
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
async function loadTransportCredential(credentialId, installationInput = {}) {
|
|
1007
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1008
|
+
const result = await invoke("LoadTransportCredential", {
|
|
1009
|
+
credentialId: assertIdentifier(credentialId, "host_transport_credential_id_invalid"),
|
|
1010
|
+
principalId: installation.principalId,
|
|
1011
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1012
|
+
timestamp: assertTimestamp(clock()),
|
|
1013
|
+
});
|
|
1014
|
+
return deserializeTransportCredential(result?.record, installation, clock(), { allowExpired: false });
|
|
1015
|
+
}
|
|
1016
|
+
|
|
1017
|
+
// Explicit maintenance only. Ordinary load/save must continue rejecting expired
|
|
1018
|
+
// credentials and immutable changes. Never return candidate or session secrets.
|
|
1019
|
+
async function migrateTransportCredentialLifetime(input = {}) {
|
|
1020
|
+
if (input.apply === true && readOnly) throw codeError("host_protected_store_read_only");
|
|
1021
|
+
const installation = await resolveActiveInstallation(installationStore, input);
|
|
1022
|
+
const profile = await readClientProfile(input.profileFile);
|
|
1023
|
+
if (profile.principalId !== installation.principalId || profile.hostId !== installation.hostInstallationId
|
|
1024
|
+
|| profile.region !== installation.region
|
|
1025
|
+
|| !installation.delegateBindings?.some((binding) => binding.profileId === profile.profileId
|
|
1026
|
+
&& binding.delegateId === profile.delegateId && binding.authorizationVersion === profile.authorizationVersion)) {
|
|
1027
|
+
throw codeError("host_transport_migration_profile_mismatch");
|
|
1028
|
+
}
|
|
1029
|
+
const relay = new URL(profile.baseUrl.replace(/\/api\/v1\/?$/, ""));
|
|
1030
|
+
if (relay.protocol !== "https:" || relay.username || relay.password || relay.search || relay.hash
|
|
1031
|
+
|| relay.pathname !== "/") throw codeError("host_transport_migration_https_required");
|
|
1032
|
+
const query = { principalId: installation.principalId, hostInstallationId: installation.hostInstallationId };
|
|
1033
|
+
const result = await invoke("VerifyTransportCredentialLifetimeMigration", { ...query, verifyOnly: true });
|
|
1034
|
+
const previous = deserializeTransportCredential(result?.record, installation, clock(), { allowExpired: true });
|
|
1035
|
+
const permanent = previous.expiresAt === PERSISTENT_CREDENTIAL_EXPIRES_AT;
|
|
1036
|
+
if (!permanent && previous.expiresAt - previous.issuedAt !== LEGACY_CREDENTIAL_LIFETIME_MS) {
|
|
1037
|
+
throw codeError("host_transport_migration_lifetime_invalid");
|
|
1038
|
+
}
|
|
1039
|
+
const summary = { credentialId: previous.credentialId, principalId: previous.principalId,
|
|
1040
|
+
hostId: previous.hostInstallationId, previousExpiresAt: previous.expiresAt,
|
|
1041
|
+
expiresAt: permanent ? previous.expiresAt : PERSISTENT_CREDENTIAL_EXPIRES_AT };
|
|
1042
|
+
if (permanent || input.apply !== true) return Object.freeze({ ...summary, status: permanent ? "already_permanent" : "eligible" });
|
|
1043
|
+
let response;
|
|
1044
|
+
try {
|
|
1045
|
+
response = await (options.migrationFetchImpl || globalThis.fetch)(`${relay.origin}/api/v2/host-transport/sessions`, {
|
|
1046
|
+
method: "POST", redirect: "error", signal: AbortSignal.timeout(8_000),
|
|
1047
|
+
headers: { "content-type": "application/json", "x-hellolynk-host-transport-credential": previous.secret },
|
|
1048
|
+
body: "{}",
|
|
1049
|
+
});
|
|
1050
|
+
} catch { throw codeError("host_transport_migration_http_failed"); }
|
|
1051
|
+
if (!response.ok) throw codeError("host_transport_migration_http_failed");
|
|
1052
|
+
let proof;
|
|
1053
|
+
try { proof = await response.json(); } catch { throw codeError("host_transport_migration_proof_invalid"); }
|
|
1054
|
+
if (!proof || typeof proof !== "object" || Array.isArray(proof)
|
|
1055
|
+
|| proof.credentialId !== previous.credentialId || proof.principalId !== previous.principalId
|
|
1056
|
+
|| proof.hostId !== previous.hostInstallationId || Date.parse(proof.credentialCreatedAt) !== previous.issuedAt
|
|
1057
|
+
|| Date.parse(proof.credentialExpiresAt) !== PERSISTENT_CREDENTIAL_EXPIRES_AT
|
|
1058
|
+
|| !TRANSPORT_SESSION_ID_PATTERN.test(proof.hostTransportSessionId)
|
|
1059
|
+
|| !TRANSPORT_SESSION_SECRET_PATTERN.test(proof.hostTransportSessionSecret)
|
|
1060
|
+
|| !(Date.parse(proof.expiresAt) > clock()) || Date.parse(proof.expiresAt) >= PERSISTENT_CREDENTIAL_EXPIRES_AT) {
|
|
1061
|
+
throw codeError("host_transport_migration_proof_invalid");
|
|
1062
|
+
}
|
|
1063
|
+
// Recheck owner/profile after network I/O; the native helper compares every
|
|
1064
|
+
// original credential field under its per-root mutex before the only write.
|
|
1065
|
+
await resolveActiveInstallation(installationStore, installation);
|
|
1066
|
+
const currentProfile = await readClientProfile(input.profileFile);
|
|
1067
|
+
if (JSON.stringify(currentProfile) !== JSON.stringify(profile)) throw codeError("host_transport_migration_profile_changed");
|
|
1068
|
+
const migrated = await invoke("MigrateTransportCredentialLifetime", { ...query, stdin: JSON.stringify({
|
|
1069
|
+
previous, proof: { credentialId: proof.credentialId, principalId: proof.principalId,
|
|
1070
|
+
hostId: proof.hostId, credentialCreatedAt: proof.credentialCreatedAt, credentialExpiresAt: proof.credentialExpiresAt },
|
|
1071
|
+
}) });
|
|
1072
|
+
if (migrated?.credentialId !== previous.credentialId || migrated?.expiresAt !== PERSISTENT_CREDENTIAL_EXPIRES_AT
|
|
1073
|
+
|| migrated?.status !== "migrated") throw codeError("host_transport_migration_result_invalid");
|
|
1074
|
+
return Object.freeze({ ...summary, status: "migrated" });
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
async function revokeTransportCredential(credentialId, installationInput = {}) {
|
|
1078
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1079
|
+
const result = await invoke("RevokeTransportCredential", {
|
|
1080
|
+
credentialId: assertIdentifier(credentialId, "host_transport_credential_id_invalid"),
|
|
1081
|
+
principalId: installation.principalId,
|
|
1082
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1083
|
+
timestamp: assertTimestamp(clock()),
|
|
1084
|
+
});
|
|
1085
|
+
assertExactKeys(result, ["cascadedSessionCount", "credentialId", "revokedAt", "state"]);
|
|
1086
|
+
if (result.credentialId !== credentialId || result.state !== "revoked") {
|
|
1087
|
+
throw codeError("host_transport_credential_revoke_invalid");
|
|
1088
|
+
}
|
|
1089
|
+
return Object.freeze({
|
|
1090
|
+
credentialId: result.credentialId,
|
|
1091
|
+
state: result.state,
|
|
1092
|
+
revokedAt: assertTimestamp(result.revokedAt),
|
|
1093
|
+
cascadedSessionCount: assertNonNegativeInteger(
|
|
1094
|
+
result.cascadedSessionCount,
|
|
1095
|
+
"host_transport_credential_revoke_invalid",
|
|
1096
|
+
),
|
|
1097
|
+
});
|
|
1098
|
+
}
|
|
1099
|
+
|
|
1100
|
+
async function saveTransportSession(input = {}, installationInput = {}) {
|
|
1101
|
+
return writeTransportSession("SaveTransportSession", input, installationInput);
|
|
1102
|
+
}
|
|
1103
|
+
|
|
1104
|
+
async function replaceTransportSession(input = {}, installationInput = {}) {
|
|
1105
|
+
return writeTransportSession("ReplaceTransportSession", input, installationInput);
|
|
1106
|
+
}
|
|
1107
|
+
|
|
1108
|
+
async function writeTransportSession(command, input, installationInput) {
|
|
1109
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1110
|
+
const record = transportSessionRecord(input, installation);
|
|
1111
|
+
const now = assertTimestamp(clock());
|
|
1112
|
+
if (record.expiresAt <= now) throw codeError("host_transport_session_expired");
|
|
1113
|
+
const result = await invoke(command, { stdin: JSON.stringify(record), timestamp: now });
|
|
1114
|
+
return deserializeTransportSession(result?.record, installation, null, now);
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
async function loadTransportSession(sessionId, installationInput = {}) {
|
|
1118
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1119
|
+
const result = await invoke("LoadTransportSession", {
|
|
1120
|
+
sessionId: assertTransportSessionId(sessionId),
|
|
1121
|
+
principalId: installation.principalId,
|
|
1122
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1123
|
+
timestamp: assertTimestamp(clock()),
|
|
1124
|
+
});
|
|
1125
|
+
return deserializeTransportSession(result?.record, installation, null, clock());
|
|
1126
|
+
}
|
|
1127
|
+
|
|
1128
|
+
async function removeTransportSession(sessionId, installationInput = {}) {
|
|
1129
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1130
|
+
const result = await invoke("RemoveTransportSession", {
|
|
1131
|
+
sessionId: assertTransportSessionId(sessionId),
|
|
1132
|
+
principalId: installation.principalId,
|
|
1133
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1134
|
+
});
|
|
1135
|
+
assertExactKeys(result, ["removed", "sessionId"]);
|
|
1136
|
+
if (result.sessionId !== sessionId || typeof result.removed !== "boolean") {
|
|
1137
|
+
throw codeError("host_transport_session_remove_invalid");
|
|
1138
|
+
}
|
|
1139
|
+
return Object.freeze({ sessionId: result.sessionId, removed: result.removed });
|
|
1140
|
+
}
|
|
1141
|
+
|
|
1142
|
+
async function resolveTransport(installationInput = {}) {
|
|
1143
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1144
|
+
const now = assertTimestamp(clock());
|
|
1145
|
+
const result = await invoke("ResolveTransport", {
|
|
1146
|
+
principalId: installation.principalId,
|
|
1147
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1148
|
+
timestamp: now,
|
|
1149
|
+
});
|
|
1150
|
+
assertExactKeys(result, ["credential", "session"]);
|
|
1151
|
+
const credential = deserializeTransportCredential(result.credential, installation, now, { allowExpired: false });
|
|
1152
|
+
const session = result.session === null
|
|
1153
|
+
? null
|
|
1154
|
+
: deserializeTransportSession(result.session, installation, credential.credentialId, now);
|
|
1155
|
+
return Object.freeze({ credential, session });
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1158
|
+
async function verifyTransport(installationInput = {}) {
|
|
1159
|
+
const installation = await resolveActiveInstallation(installationStore, installationInput);
|
|
1160
|
+
const now = assertTimestamp(clock());
|
|
1161
|
+
const result = await invoke("VerifyTransport", {
|
|
1162
|
+
principalId: installation.principalId,
|
|
1163
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1164
|
+
timestamp: now,
|
|
1165
|
+
});
|
|
1166
|
+
assertExactKeys(result, ["credential", "session"]);
|
|
1167
|
+
assertExactKeys(result.credential, [
|
|
1168
|
+
"credentialId", "expiresAt", "hostInstallationId", "principalId",
|
|
1169
|
+
]);
|
|
1170
|
+
const credential = Object.freeze({
|
|
1171
|
+
credentialId: assertIdentifier(result.credential.credentialId, "host_transport_credential_id_invalid"),
|
|
1172
|
+
principalId: assertIdentifier(result.credential.principalId, "host_principal_id_invalid"),
|
|
1173
|
+
hostInstallationId: assertIdentifier(result.credential.hostInstallationId, "host_installation_id_invalid"),
|
|
1174
|
+
expiresAt: assertTimestamp(result.credential.expiresAt),
|
|
1175
|
+
});
|
|
1176
|
+
if (credential.principalId !== installation.principalId
|
|
1177
|
+
|| credential.hostInstallationId !== installation.hostInstallationId
|
|
1178
|
+
|| credential.expiresAt <= now) throw codeError("host_transport_credential_owner_mismatch");
|
|
1179
|
+
let session = null;
|
|
1180
|
+
if (result.session !== null) {
|
|
1181
|
+
assertExactKeys(result.session, [
|
|
1182
|
+
"credentialId", "expiresAt", "hostInstallationId", "principalId", "sessionId",
|
|
1183
|
+
]);
|
|
1184
|
+
session = Object.freeze({
|
|
1185
|
+
sessionId: assertTransportSessionId(result.session.sessionId),
|
|
1186
|
+
credentialId: assertIdentifier(result.session.credentialId, "host_transport_credential_id_invalid"),
|
|
1187
|
+
principalId: assertIdentifier(result.session.principalId, "host_principal_id_invalid"),
|
|
1188
|
+
hostInstallationId: assertIdentifier(result.session.hostInstallationId, "host_installation_id_invalid"),
|
|
1189
|
+
expiresAt: assertTimestamp(result.session.expiresAt),
|
|
1190
|
+
});
|
|
1191
|
+
if (session.credentialId !== credential.credentialId
|
|
1192
|
+
|| session.principalId !== installation.principalId
|
|
1193
|
+
|| session.hostInstallationId !== installation.hostInstallationId
|
|
1194
|
+
|| session.expiresAt <= now) throw codeError("host_transport_session_owner_mismatch");
|
|
1195
|
+
}
|
|
1196
|
+
return Object.freeze({ credential, session });
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
const hostTransportCredentialProvider = Object.freeze({
|
|
1200
|
+
async get(installationInput = {}) {
|
|
1201
|
+
return (await resolveTransport(installationInput)).credential.secret;
|
|
1202
|
+
},
|
|
1203
|
+
});
|
|
1204
|
+
|
|
1205
|
+
const hostTransportSessionProvider = Object.freeze({
|
|
1206
|
+
async get(installationInput = {}) {
|
|
1207
|
+
const resolved = await resolveTransport(installationInput);
|
|
1208
|
+
if (!resolved.session) throw codeError("host_transport_session_invalid");
|
|
1209
|
+
return clientTransportSession(resolved.session);
|
|
1210
|
+
},
|
|
1211
|
+
async save(value, installationInput = {}) {
|
|
1212
|
+
const resolved = await resolveTransport(installationInput);
|
|
1213
|
+
const session = await replaceTransportSession({
|
|
1214
|
+
...value,
|
|
1215
|
+
credentialId: resolved.credential.credentialId,
|
|
1216
|
+
hostInstallationId: value?.hostInstallationId ?? value?.hostId,
|
|
1217
|
+
issuedAt: value?.issuedAt ?? clock(),
|
|
1218
|
+
}, installationInput);
|
|
1219
|
+
return clientTransportSession(session);
|
|
1220
|
+
},
|
|
1221
|
+
async remove(installationInput = {}) {
|
|
1222
|
+
const resolved = await resolveTransport(installationInput);
|
|
1223
|
+
if (!resolved.session) return Object.freeze({ sessionId: null, removed: false });
|
|
1224
|
+
return removeTransportSession(resolved.session.sessionId, installationInput);
|
|
1225
|
+
},
|
|
1226
|
+
});
|
|
1227
|
+
|
|
1228
|
+
return Object.freeze({
|
|
1229
|
+
root,
|
|
1230
|
+
issue,
|
|
1231
|
+
load,
|
|
1232
|
+
revoke,
|
|
1233
|
+
disconnectLocalClient,
|
|
1234
|
+
rotate,
|
|
1235
|
+
status,
|
|
1236
|
+
capabilityResolver: resolveCapability,
|
|
1237
|
+
saveCodexNeutralBinding,
|
|
1238
|
+
resolveCodexNeutralBinding,
|
|
1239
|
+
verifyCodexNeutralBinding,
|
|
1240
|
+
removeCodexNeutralBinding,
|
|
1241
|
+
replaceCodexNeutralBinding,
|
|
1242
|
+
migrateCodexNeutralBinding,
|
|
1243
|
+
saveOpenClawNeutralBinding,
|
|
1244
|
+
resolveOpenClawNeutralBinding,
|
|
1245
|
+
verifyOpenClawNeutralBinding,
|
|
1246
|
+
removeOpenClawNeutralBinding,
|
|
1247
|
+
migrateOpenClawNeutralBinding,
|
|
1248
|
+
saveTraeWorkNeutralBinding,
|
|
1249
|
+
resolveTraeWorkNeutralBinding,
|
|
1250
|
+
verifyTraeWorkNeutralBinding,
|
|
1251
|
+
removeTraeWorkNeutralBinding,
|
|
1252
|
+
migrateTraeWorkNeutralBinding,
|
|
1253
|
+
saveWorkBuddyNeutralBinding,
|
|
1254
|
+
resolveWorkBuddyNeutralBinding,
|
|
1255
|
+
verifyWorkBuddyNeutralBinding,
|
|
1256
|
+
removeWorkBuddyNeutralBinding,
|
|
1257
|
+
migrateWorkBuddyNeutralBinding,
|
|
1258
|
+
saveClaudeCodeNeutralBinding,
|
|
1259
|
+
resolveClaudeCodeNeutralBinding,
|
|
1260
|
+
verifyClaudeCodeNeutralBinding,
|
|
1261
|
+
removeClaudeCodeNeutralBinding,
|
|
1262
|
+
migrateClaudeCodeNeutralBinding,
|
|
1263
|
+
saveCursorNeutralBinding,
|
|
1264
|
+
resolveCursorNeutralBinding,
|
|
1265
|
+
verifyCursorNeutralBinding,
|
|
1266
|
+
removeCursorNeutralBinding,
|
|
1267
|
+
migrateCursorNeutralBinding,
|
|
1268
|
+
saveDshNeutralBinding,
|
|
1269
|
+
resolveDshNeutralBinding,
|
|
1270
|
+
verifyDshNeutralBinding,
|
|
1271
|
+
removeDshNeutralBinding,
|
|
1272
|
+
migrateDshNeutralBinding,
|
|
1273
|
+
restoreDshNeutralBinding,
|
|
1274
|
+
saveTransportCredential,
|
|
1275
|
+
loadDeviceEnrollment,
|
|
1276
|
+
saveDeviceEnrollment,
|
|
1277
|
+
saveEnrollmentTransportCredential,
|
|
1278
|
+
loadTransportCredential,
|
|
1279
|
+
migrateTransportCredentialLifetime,
|
|
1280
|
+
revokeTransportCredential,
|
|
1281
|
+
saveTransportSession,
|
|
1282
|
+
loadTransportSession,
|
|
1283
|
+
removeTransportSession,
|
|
1284
|
+
replaceTransportSession,
|
|
1285
|
+
resolveTransport,
|
|
1286
|
+
verifyTransport,
|
|
1287
|
+
hostTransportCredentialProvider,
|
|
1288
|
+
hostTransportSessionProvider,
|
|
1289
|
+
});
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// The native resolver also checks launch paths outside the credential file.
|
|
1293
|
+
// Check those current paths even on a cache hit; never hide a removed runner
|
|
1294
|
+
// or a directory replaced with a junction behind an unchanged credential.
|
|
1295
|
+
async function neutralBindingPathsPresent(binding, signal) {
|
|
1296
|
+
const paths = [[binding.runtimeRoot, true, 0]];
|
|
1297
|
+
if (binding.adapterId === "codex") paths.push(
|
|
1298
|
+
[binding.releaseRoot, true, 0],
|
|
1299
|
+
[binding.profileManifestPath, false, 65536],
|
|
1300
|
+
[resolve(binding.releaseRoot, binding.runnerRelativePath), false, 1048576],
|
|
1301
|
+
);
|
|
1302
|
+
for (const [target, directory, maxBytes] of paths) {
|
|
1303
|
+
for (let current = target; ; current = dirname(current)) {
|
|
1304
|
+
signal?.throwIfAborted();
|
|
1305
|
+
const stat = await lstat(current).catch(() => null);
|
|
1306
|
+
if (!stat || stat.isSymbolicLink()) return false;
|
|
1307
|
+
if (current === target && !directory) {
|
|
1308
|
+
if (!stat.isFile() || stat.size > maxBytes) return false;
|
|
1309
|
+
} else if (!stat.isDirectory()) return false;
|
|
1310
|
+
if (dirname(current) === current) break;
|
|
1311
|
+
}
|
|
1312
|
+
}
|
|
1313
|
+
return true;
|
|
1314
|
+
}
|
|
1315
|
+
|
|
1316
|
+
// Reuse a successful ACL/path check while files and directory identities stay
|
|
1317
|
+
// unchanged. Files include ctime: Windows ACL edits need not change mtime.
|
|
1318
|
+
// Directories omit timestamps so ordinary writes do not invalidate the cache.
|
|
1319
|
+
// Per the user's 2026-10-12 decision, in-place directory ACL changes can remain
|
|
1320
|
+
// cached until Host restart; cold checks and replacement/junction checks remain.
|
|
1321
|
+
// Credentials, bindings and authorization state are still read separately.
|
|
1322
|
+
const VERIFIED_PATHS = new Map();
|
|
1323
|
+
const MAX_VERIFIED_PATHS = 256;
|
|
1324
|
+
|
|
1325
|
+
async function protectedPathFingerprint(root, target, helperPath, directory, signal) {
|
|
1326
|
+
const components = new Set();
|
|
1327
|
+
for (const leaf of [root, target, helperPath]) {
|
|
1328
|
+
for (let cursor = leaf; ; cursor = dirname(cursor)) {
|
|
1329
|
+
components.add(cursor);
|
|
1330
|
+
if (dirname(cursor) === cursor) break;
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
const parts = [];
|
|
1334
|
+
for (const component of components) {
|
|
1335
|
+
signal?.throwIfAborted();
|
|
1336
|
+
let stat;
|
|
1337
|
+
try { stat = await lstat(component, { bigint: true }); }
|
|
1338
|
+
catch { return null; } // Let the existing verifier report the path error.
|
|
1339
|
+
const isFile = component === helperPath || (component === target && !directory);
|
|
1340
|
+
if (stat.isSymbolicLink() || (isFile ? !stat.isFile() : !stat.isDirectory())) return null;
|
|
1341
|
+
const identity = [component, stat.dev, stat.ino, stat.mode, stat.birthtimeNs];
|
|
1342
|
+
parts.push((isFile
|
|
1343
|
+
? [...identity, stat.nlink, stat.size, stat.ctimeNs, stat.mtimeNs] : identity).join(":"));
|
|
1344
|
+
}
|
|
1345
|
+
signal?.throwIfAborted();
|
|
1346
|
+
return parts.join("\n");
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
export async function verifyHostProtectedStorePath(options = {}) {
|
|
1350
|
+
const root = assertRoot(options.root || defaultNeutralHostStoreRoot("win32", options.env));
|
|
1351
|
+
const target = resolve(String(options.path || ""));
|
|
1352
|
+
const relativeTarget = relative(root, target);
|
|
1353
|
+
if (!isAbsolute(String(options.path || ""))
|
|
1354
|
+
|| (relativeTarget !== "" && (isAbsolute(relativeTarget) || relativeTarget === ".." || relativeTarget.startsWith(`..\\`)))) {
|
|
1355
|
+
throw codeError("host_protected_store_path_invalid");
|
|
1356
|
+
}
|
|
1357
|
+
const helperPath = resolve(options.helperPath || SCRIPT_PATH);
|
|
1358
|
+
const directory = options.directory === true;
|
|
1359
|
+
const key = JSON.stringify([root, target, helperPath, directory]);
|
|
1360
|
+
const before = await protectedPathFingerprint(root, target, helperPath, directory, options.signal);
|
|
1361
|
+
options.signal?.throwIfAborted();
|
|
1362
|
+
if (before && VERIFIED_PATHS.get(key) === before) return Object.freeze({ verified: true });
|
|
1363
|
+
VERIFIED_PATHS.delete(key);
|
|
1364
|
+
const result = await invokeWindowsStore({
|
|
1365
|
+
command: "VerifyPath",
|
|
1366
|
+
root,
|
|
1367
|
+
helperPath,
|
|
1368
|
+
verifyOnly: true,
|
|
1369
|
+
signal: options.signal,
|
|
1370
|
+
pathToVerify: target,
|
|
1371
|
+
verifyDirectory: directory,
|
|
1372
|
+
});
|
|
1373
|
+
assertExactKeys(result, ["verified"]);
|
|
1374
|
+
if (result.verified !== true) throw codeError("host_protected_store_path_invalid");
|
|
1375
|
+
const after = await protectedPathFingerprint(root, target, helperPath, directory, options.signal);
|
|
1376
|
+
if (before && after === before) {
|
|
1377
|
+
if (VERIFIED_PATHS.size >= MAX_VERIFIED_PATHS) VERIFIED_PATHS.delete(VERIFIED_PATHS.keys().next().value);
|
|
1378
|
+
VERIFIED_PATHS.set(key, after);
|
|
1379
|
+
}
|
|
1380
|
+
return Object.freeze({ verified: true });
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
function serializeRootInvoke(root, callback) {
|
|
1384
|
+
const key = process.platform === "win32"
|
|
1385
|
+
? root.replace(/[\\/]+$/, "").toUpperCase()
|
|
1386
|
+
: root;
|
|
1387
|
+
const previous = ROOT_INVOKE_QUEUES.get(key) || Promise.resolve();
|
|
1388
|
+
const current = previous.catch(() => {}).then(callback);
|
|
1389
|
+
ROOT_INVOKE_QUEUES.set(key, current);
|
|
1390
|
+
return current.finally(() => {
|
|
1391
|
+
if (ROOT_INVOKE_QUEUES.get(key) === current) ROOT_INVOKE_QUEUES.delete(key);
|
|
1392
|
+
});
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
async function resolveOpenClawInstallationBinding(store, input, installation) {
|
|
1396
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_openclaw_binding_invalid");
|
|
1397
|
+
const delegateId = input?.delegateId == null
|
|
1398
|
+
? null
|
|
1399
|
+
: assertIdentifier(input.delegateId, "neutral_openclaw_binding_invalid");
|
|
1400
|
+
let binding;
|
|
1401
|
+
try {
|
|
1402
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
1403
|
+
} catch {
|
|
1404
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
1405
|
+
}
|
|
1406
|
+
if (binding.region !== installation.region
|
|
1407
|
+
|| binding.principalId !== installation.principalId
|
|
1408
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
1409
|
+
|| !binding.adapters.includes("openclaw")) {
|
|
1410
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
1411
|
+
}
|
|
1412
|
+
return binding;
|
|
1413
|
+
}
|
|
1414
|
+
|
|
1415
|
+
async function resolveTraeWorkInstallationBinding(store, input, installation) {
|
|
1416
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_trae_work_binding_invalid");
|
|
1417
|
+
const delegateId = input?.delegateId == null
|
|
1418
|
+
? null
|
|
1419
|
+
: assertIdentifier(input.delegateId, "neutral_trae_work_binding_invalid");
|
|
1420
|
+
let binding;
|
|
1421
|
+
try {
|
|
1422
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
1423
|
+
} catch {
|
|
1424
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
1425
|
+
}
|
|
1426
|
+
if (binding.region !== installation.region
|
|
1427
|
+
|| binding.principalId !== installation.principalId
|
|
1428
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
1429
|
+
|| !binding.adapters.includes("trae-work")) {
|
|
1430
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
1431
|
+
}
|
|
1432
|
+
return binding;
|
|
1433
|
+
}
|
|
1434
|
+
|
|
1435
|
+
function traeWorkNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
1436
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES);
|
|
1437
|
+
const acceptedScopes = sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES)
|
|
1438
|
+
|| (input?.allowLegacyScopes === true
|
|
1439
|
+
&& (sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_PREVIOUS_SCOPES)
|
|
1440
|
+
|| sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_LEGACY_SCOPES)));
|
|
1441
|
+
if (!acceptedScopes
|
|
1442
|
+
|| (input?.adapterId ?? "trae-work") !== "trae-work"
|
|
1443
|
+
|| Number(input?.version ?? 2) !== 2) {
|
|
1444
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
1445
|
+
}
|
|
1446
|
+
const release = optionalTraeWorkReleaseBinding(input);
|
|
1447
|
+
return Object.freeze({
|
|
1448
|
+
contract: HELLOLYNK_TRAE_WORK_NEUTRAL_BINDING_CONTRACT,
|
|
1449
|
+
region: installation.region,
|
|
1450
|
+
principalId: installation.principalId,
|
|
1451
|
+
delegateId: delegateBinding.delegateId,
|
|
1452
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1453
|
+
profileId: delegateBinding.profileId,
|
|
1454
|
+
adapterId: "trae-work",
|
|
1455
|
+
version: 2,
|
|
1456
|
+
scopes,
|
|
1457
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_trae_work_binding_invalid"),
|
|
1458
|
+
...release,
|
|
1459
|
+
});
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
function publicTraeWorkNeutralBinding(value) {
|
|
1463
|
+
const releaseKeys = [
|
|
1464
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1465
|
+
];
|
|
1466
|
+
const releaseCount = releaseKeys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1467
|
+
assertExactKeysWithCode(value, [
|
|
1468
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
1469
|
+
"profileId", "region", "runtimeRoot", "scopes", "version",
|
|
1470
|
+
...(releaseCount ? releaseKeys : []),
|
|
1471
|
+
], "neutral_trae_work_binding_invalid");
|
|
1472
|
+
if (releaseCount !== 0 && releaseCount !== releaseKeys.length) {
|
|
1473
|
+
throw codeError("neutral_trae_work_binding_invalid");
|
|
1474
|
+
}
|
|
1475
|
+
if (value.contract !== HELLOLYNK_TRAE_WORK_NEUTRAL_BINDING_CONTRACT
|
|
1476
|
+
|| value.adapterId !== "trae-work" || Number(value.version) !== 2) {
|
|
1477
|
+
throw codeError("neutral_trae_work_binding_invalid");
|
|
1478
|
+
}
|
|
1479
|
+
const scopes = canonicalScopes(value.scopes);
|
|
1480
|
+
if (!sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_EXACT_SCOPES)
|
|
1481
|
+
&& !sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_PREVIOUS_SCOPES)
|
|
1482
|
+
&& !sameStrings(scopes, HELLOLYNK_TRAE_WORK_NEUTRAL_LEGACY_SCOPES)) {
|
|
1483
|
+
throw codeError("neutral_trae_work_binding_invalid");
|
|
1484
|
+
}
|
|
1485
|
+
return Object.freeze({
|
|
1486
|
+
contract: value.contract,
|
|
1487
|
+
region: assertIdentifier(value.region, "neutral_trae_work_binding_invalid"),
|
|
1488
|
+
principalId: assertIdentifier(value.principalId, "neutral_trae_work_binding_invalid"),
|
|
1489
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_trae_work_binding_invalid"),
|
|
1490
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_trae_work_binding_invalid"),
|
|
1491
|
+
profileId: assertIdentifier(value.profileId, "neutral_trae_work_binding_invalid"),
|
|
1492
|
+
adapterId: "trae-work",
|
|
1493
|
+
version: 2,
|
|
1494
|
+
scopes: Object.freeze(scopes),
|
|
1495
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_trae_work_binding_invalid"),
|
|
1496
|
+
...(releaseCount === 0 ? {} : optionalTraeWorkReleaseBinding(value)),
|
|
1497
|
+
});
|
|
1498
|
+
}
|
|
1499
|
+
|
|
1500
|
+
function optionalTraeWorkReleaseBinding(value) {
|
|
1501
|
+
const keys = [
|
|
1502
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1503
|
+
];
|
|
1504
|
+
const count = keys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1505
|
+
if (count === 0) return Object.freeze({});
|
|
1506
|
+
if (count !== keys.length) throw codeError("neutral_trae_work_launch_configuration_invalid");
|
|
1507
|
+
return Object.freeze({
|
|
1508
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_trae_work_launch_configuration_invalid"),
|
|
1509
|
+
releaseManifestSha256: assertSha256(value.releaseManifestSha256),
|
|
1510
|
+
runnerRelativePath: assertRunnerReference(value.runnerRelativePath),
|
|
1511
|
+
profileManifestPath: assertAbsolutePath(
|
|
1512
|
+
value.profileManifestPath,
|
|
1513
|
+
"neutral_trae_work_launch_configuration_invalid",
|
|
1514
|
+
),
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
async function assertCurrentTraeWorkInstallationBinding(store, before, binding, capability, input) {
|
|
1519
|
+
const after = await resolveActiveInstallation(store, input);
|
|
1520
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
1521
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
1522
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
1523
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
1524
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
1525
|
+
}
|
|
1526
|
+
const delegateBinding = await resolveTraeWorkInstallationBinding(store, binding, after);
|
|
1527
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
1528
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
1529
|
+
|| capability.delegateId !== binding.delegateId
|
|
1530
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
1531
|
+
|| capability.adapterId !== "trae-work" || capability.version !== 2
|
|
1532
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
1533
|
+
throw codeError("neutral_trae_work_capability_binding_mismatch");
|
|
1534
|
+
}
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
async function resolveWorkBuddyInstallationBinding(store, input, installation) {
|
|
1538
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_workbuddy_binding_invalid");
|
|
1539
|
+
const delegateId = input?.delegateId == null
|
|
1540
|
+
? null
|
|
1541
|
+
: assertIdentifier(input.delegateId, "neutral_workbuddy_binding_invalid");
|
|
1542
|
+
let binding;
|
|
1543
|
+
try {
|
|
1544
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
1545
|
+
} catch {
|
|
1546
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
1547
|
+
}
|
|
1548
|
+
if (binding.region !== installation.region
|
|
1549
|
+
|| binding.principalId !== installation.principalId
|
|
1550
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
1551
|
+
|| !binding.adapters.includes("workbuddy")) {
|
|
1552
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
1553
|
+
}
|
|
1554
|
+
return binding;
|
|
1555
|
+
}
|
|
1556
|
+
|
|
1557
|
+
function workBuddyNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
1558
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES);
|
|
1559
|
+
const acceptedScopes = sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES)
|
|
1560
|
+
|| (input?.allowLegacyScopes === true
|
|
1561
|
+
&& (sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_PREVIOUS_SCOPES)
|
|
1562
|
+
|| sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_LEGACY_SCOPES)));
|
|
1563
|
+
if (!acceptedScopes
|
|
1564
|
+
|| (input?.adapterId ?? "workbuddy") !== "workbuddy"
|
|
1565
|
+
|| Number(input?.version ?? 2) !== 2) {
|
|
1566
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
1567
|
+
}
|
|
1568
|
+
const release = optionalWorkBuddyReleaseBinding(input);
|
|
1569
|
+
return Object.freeze({
|
|
1570
|
+
contract: HELLOLYNK_WORKBUDDY_NEUTRAL_BINDING_CONTRACT,
|
|
1571
|
+
region: installation.region,
|
|
1572
|
+
principalId: installation.principalId,
|
|
1573
|
+
delegateId: delegateBinding.delegateId,
|
|
1574
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1575
|
+
profileId: delegateBinding.profileId,
|
|
1576
|
+
adapterId: "workbuddy",
|
|
1577
|
+
version: 2,
|
|
1578
|
+
scopes,
|
|
1579
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_workbuddy_binding_invalid"),
|
|
1580
|
+
...release,
|
|
1581
|
+
});
|
|
1582
|
+
}
|
|
1583
|
+
|
|
1584
|
+
function publicWorkBuddyNeutralBinding(value) {
|
|
1585
|
+
const releaseKeys = [
|
|
1586
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1587
|
+
];
|
|
1588
|
+
const releaseCount = releaseKeys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1589
|
+
assertExactKeysWithCode(value, [
|
|
1590
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
1591
|
+
"profileId", "region", "runtimeRoot", "scopes", "version",
|
|
1592
|
+
...(releaseCount ? releaseKeys : []),
|
|
1593
|
+
], "neutral_workbuddy_binding_invalid");
|
|
1594
|
+
if (releaseCount !== 0 && releaseCount !== releaseKeys.length) {
|
|
1595
|
+
throw codeError("neutral_workbuddy_binding_invalid");
|
|
1596
|
+
}
|
|
1597
|
+
if (value.contract !== HELLOLYNK_WORKBUDDY_NEUTRAL_BINDING_CONTRACT
|
|
1598
|
+
|| value.adapterId !== "workbuddy" || Number(value.version) !== 2) {
|
|
1599
|
+
throw codeError("neutral_workbuddy_binding_invalid");
|
|
1600
|
+
}
|
|
1601
|
+
const scopes = canonicalScopes(value.scopes);
|
|
1602
|
+
if (!sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_EXACT_SCOPES)
|
|
1603
|
+
&& !sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_PREVIOUS_SCOPES)
|
|
1604
|
+
&& !sameStrings(scopes, HELLOLYNK_WORKBUDDY_NEUTRAL_LEGACY_SCOPES)) {
|
|
1605
|
+
throw codeError("neutral_workbuddy_binding_invalid");
|
|
1606
|
+
}
|
|
1607
|
+
return Object.freeze({
|
|
1608
|
+
contract: value.contract,
|
|
1609
|
+
region: assertIdentifier(value.region, "neutral_workbuddy_binding_invalid"),
|
|
1610
|
+
principalId: assertIdentifier(value.principalId, "neutral_workbuddy_binding_invalid"),
|
|
1611
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_workbuddy_binding_invalid"),
|
|
1612
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_workbuddy_binding_invalid"),
|
|
1613
|
+
profileId: assertIdentifier(value.profileId, "neutral_workbuddy_binding_invalid"),
|
|
1614
|
+
adapterId: "workbuddy",
|
|
1615
|
+
version: 2,
|
|
1616
|
+
scopes: Object.freeze(scopes),
|
|
1617
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_workbuddy_binding_invalid"),
|
|
1618
|
+
...(releaseCount === 0 ? {} : optionalWorkBuddyReleaseBinding(value)),
|
|
1619
|
+
});
|
|
1620
|
+
}
|
|
1621
|
+
|
|
1622
|
+
function optionalWorkBuddyReleaseBinding(value) {
|
|
1623
|
+
const keys = [
|
|
1624
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1625
|
+
];
|
|
1626
|
+
const count = keys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1627
|
+
if (count === 0) return Object.freeze({});
|
|
1628
|
+
if (count !== keys.length) throw codeError("neutral_workbuddy_launch_configuration_invalid");
|
|
1629
|
+
return Object.freeze({
|
|
1630
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_workbuddy_launch_configuration_invalid"),
|
|
1631
|
+
releaseManifestSha256: assertSha256(value.releaseManifestSha256),
|
|
1632
|
+
runnerRelativePath: assertRunnerReference(value.runnerRelativePath),
|
|
1633
|
+
profileManifestPath: assertAbsolutePath(
|
|
1634
|
+
value.profileManifestPath,
|
|
1635
|
+
"neutral_workbuddy_launch_configuration_invalid",
|
|
1636
|
+
),
|
|
1637
|
+
});
|
|
1638
|
+
}
|
|
1639
|
+
|
|
1640
|
+
async function assertCurrentWorkBuddyInstallationBinding(store, before, binding, capability, input) {
|
|
1641
|
+
const after = await resolveActiveInstallation(store, input);
|
|
1642
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
1643
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
1644
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
1645
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
1646
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
1647
|
+
}
|
|
1648
|
+
const delegateBinding = await resolveWorkBuddyInstallationBinding(store, binding, after);
|
|
1649
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
1650
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
1651
|
+
|| capability.delegateId !== binding.delegateId
|
|
1652
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
1653
|
+
|| capability.adapterId !== "workbuddy" || capability.version !== 2
|
|
1654
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
1655
|
+
throw codeError("neutral_workbuddy_capability_binding_mismatch");
|
|
1656
|
+
}
|
|
1657
|
+
}
|
|
1658
|
+
|
|
1659
|
+
async function resolveClaudeCodeInstallationBinding(store, input, installation) {
|
|
1660
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_claude_code_binding_invalid");
|
|
1661
|
+
const delegateId = input?.delegateId == null
|
|
1662
|
+
? null
|
|
1663
|
+
: assertIdentifier(input.delegateId, "neutral_claude_code_binding_invalid");
|
|
1664
|
+
let binding;
|
|
1665
|
+
try {
|
|
1666
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
1667
|
+
} catch {
|
|
1668
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
1669
|
+
}
|
|
1670
|
+
if (binding.region !== installation.region
|
|
1671
|
+
|| binding.principalId !== installation.principalId
|
|
1672
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
1673
|
+
|| !binding.adapters.includes("claude-code")) {
|
|
1674
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
1675
|
+
}
|
|
1676
|
+
return binding;
|
|
1677
|
+
}
|
|
1678
|
+
|
|
1679
|
+
function claudeCodeNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
1680
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES);
|
|
1681
|
+
const acceptedScopes = sameStrings(scopes, HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES);
|
|
1682
|
+
if (!acceptedScopes
|
|
1683
|
+
|| (input?.adapterId ?? "claude-code") !== "claude-code"
|
|
1684
|
+
|| Number(input?.version ?? 2) !== 2) {
|
|
1685
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
1686
|
+
}
|
|
1687
|
+
const release = optionalClaudeCodeReleaseBinding(input);
|
|
1688
|
+
return Object.freeze({
|
|
1689
|
+
contract: HELLOLYNK_CLAUDE_CODE_NEUTRAL_BINDING_CONTRACT,
|
|
1690
|
+
region: installation.region,
|
|
1691
|
+
principalId: installation.principalId,
|
|
1692
|
+
delegateId: delegateBinding.delegateId,
|
|
1693
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1694
|
+
profileId: delegateBinding.profileId,
|
|
1695
|
+
adapterId: "claude-code",
|
|
1696
|
+
version: 2,
|
|
1697
|
+
scopes,
|
|
1698
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_claude_code_binding_invalid"),
|
|
1699
|
+
...release,
|
|
1700
|
+
});
|
|
1701
|
+
}
|
|
1702
|
+
|
|
1703
|
+
function publicClaudeCodeNeutralBinding(value) {
|
|
1704
|
+
const releaseKeys = [
|
|
1705
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1706
|
+
];
|
|
1707
|
+
const releaseCount = releaseKeys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1708
|
+
assertExactKeysWithCode(value, [
|
|
1709
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
1710
|
+
"profileId", "region", "runtimeRoot", "scopes", "version",
|
|
1711
|
+
...(releaseCount ? releaseKeys : []),
|
|
1712
|
+
], "neutral_claude_code_binding_invalid");
|
|
1713
|
+
if (releaseCount !== 0 && releaseCount !== releaseKeys.length) {
|
|
1714
|
+
throw codeError("neutral_claude_code_binding_invalid");
|
|
1715
|
+
}
|
|
1716
|
+
if (value.contract !== HELLOLYNK_CLAUDE_CODE_NEUTRAL_BINDING_CONTRACT
|
|
1717
|
+
|| value.adapterId !== "claude-code" || Number(value.version) !== 2) {
|
|
1718
|
+
throw codeError("neutral_claude_code_binding_invalid");
|
|
1719
|
+
}
|
|
1720
|
+
const scopes = canonicalScopes(value.scopes);
|
|
1721
|
+
if (!sameStrings(scopes, HELLOLYNK_CLAUDE_CODE_NEUTRAL_EXACT_SCOPES)) {
|
|
1722
|
+
throw codeError("neutral_claude_code_binding_invalid");
|
|
1723
|
+
}
|
|
1724
|
+
return Object.freeze({
|
|
1725
|
+
contract: value.contract,
|
|
1726
|
+
region: assertIdentifier(value.region, "neutral_claude_code_binding_invalid"),
|
|
1727
|
+
principalId: assertIdentifier(value.principalId, "neutral_claude_code_binding_invalid"),
|
|
1728
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_claude_code_binding_invalid"),
|
|
1729
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_claude_code_binding_invalid"),
|
|
1730
|
+
profileId: assertIdentifier(value.profileId, "neutral_claude_code_binding_invalid"),
|
|
1731
|
+
adapterId: "claude-code",
|
|
1732
|
+
version: 2,
|
|
1733
|
+
scopes: Object.freeze(scopes),
|
|
1734
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_claude_code_binding_invalid"),
|
|
1735
|
+
...(releaseCount === 0 ? {} : optionalClaudeCodeReleaseBinding(value)),
|
|
1736
|
+
});
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
function optionalClaudeCodeReleaseBinding(value) {
|
|
1740
|
+
const keys = [
|
|
1741
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1742
|
+
];
|
|
1743
|
+
const count = keys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1744
|
+
if (count === 0) return Object.freeze({});
|
|
1745
|
+
if (count !== keys.length) throw codeError("neutral_claude_code_launch_configuration_invalid");
|
|
1746
|
+
return Object.freeze({
|
|
1747
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_claude_code_launch_configuration_invalid"),
|
|
1748
|
+
releaseManifestSha256: assertSha256(value.releaseManifestSha256),
|
|
1749
|
+
runnerRelativePath: assertRunnerReference(value.runnerRelativePath),
|
|
1750
|
+
profileManifestPath: assertAbsolutePath(
|
|
1751
|
+
value.profileManifestPath,
|
|
1752
|
+
"neutral_claude_code_launch_configuration_invalid",
|
|
1753
|
+
),
|
|
1754
|
+
});
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
async function assertCurrentClaudeCodeInstallationBinding(store, before, binding, capability, input) {
|
|
1758
|
+
const after = await resolveActiveInstallation(store, input);
|
|
1759
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
1760
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
1761
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
1762
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
1763
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
1764
|
+
}
|
|
1765
|
+
const delegateBinding = await resolveClaudeCodeInstallationBinding(store, binding, after);
|
|
1766
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
1767
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
1768
|
+
|| capability.delegateId !== binding.delegateId
|
|
1769
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
1770
|
+
|| capability.adapterId !== "claude-code" || capability.version !== 2
|
|
1771
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
1772
|
+
throw codeError("neutral_claude_code_capability_binding_mismatch");
|
|
1773
|
+
}
|
|
1774
|
+
}
|
|
1775
|
+
|
|
1776
|
+
async function resolveCursorInstallationBinding(store, input, installation) {
|
|
1777
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_cursor_binding_invalid");
|
|
1778
|
+
const delegateId = input?.delegateId == null
|
|
1779
|
+
? null
|
|
1780
|
+
: assertIdentifier(input.delegateId, "neutral_cursor_binding_invalid");
|
|
1781
|
+
let binding;
|
|
1782
|
+
try {
|
|
1783
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
1784
|
+
} catch {
|
|
1785
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
1786
|
+
}
|
|
1787
|
+
if (binding.region !== installation.region
|
|
1788
|
+
|| binding.principalId !== installation.principalId
|
|
1789
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
1790
|
+
|| !binding.adapters.includes("cursor")) {
|
|
1791
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
1792
|
+
}
|
|
1793
|
+
return binding;
|
|
1794
|
+
}
|
|
1795
|
+
|
|
1796
|
+
function cursorNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
1797
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES);
|
|
1798
|
+
if (!sameStrings(scopes, HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES)
|
|
1799
|
+
|| (input?.adapterId ?? "cursor") !== "cursor"
|
|
1800
|
+
|| Number(input?.version ?? 2) !== 2) {
|
|
1801
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
1802
|
+
}
|
|
1803
|
+
const release = optionalCursorReleaseBinding(input);
|
|
1804
|
+
return Object.freeze({
|
|
1805
|
+
contract: HELLOLYNK_CURSOR_NEUTRAL_BINDING_CONTRACT,
|
|
1806
|
+
region: installation.region,
|
|
1807
|
+
principalId: installation.principalId,
|
|
1808
|
+
delegateId: delegateBinding.delegateId,
|
|
1809
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1810
|
+
profileId: delegateBinding.profileId,
|
|
1811
|
+
adapterId: "cursor",
|
|
1812
|
+
version: 2,
|
|
1813
|
+
scopes,
|
|
1814
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_cursor_binding_invalid"),
|
|
1815
|
+
...release,
|
|
1816
|
+
});
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function publicCursorNeutralBinding(value) {
|
|
1820
|
+
const releaseKeys = [
|
|
1821
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1822
|
+
];
|
|
1823
|
+
const releaseCount = releaseKeys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1824
|
+
assertExactKeysWithCode(value, [
|
|
1825
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
1826
|
+
"profileId", "region", "runtimeRoot", "scopes", "version",
|
|
1827
|
+
...(releaseCount ? releaseKeys : []),
|
|
1828
|
+
], "neutral_cursor_binding_invalid");
|
|
1829
|
+
if (releaseCount !== 0 && releaseCount !== releaseKeys.length) {
|
|
1830
|
+
throw codeError("neutral_cursor_binding_invalid");
|
|
1831
|
+
}
|
|
1832
|
+
if (value.contract !== HELLOLYNK_CURSOR_NEUTRAL_BINDING_CONTRACT
|
|
1833
|
+
|| value.adapterId !== "cursor" || Number(value.version) !== 2) {
|
|
1834
|
+
throw codeError("neutral_cursor_binding_invalid");
|
|
1835
|
+
}
|
|
1836
|
+
const scopes = canonicalScopes(value.scopes);
|
|
1837
|
+
if (!sameStrings(scopes, HELLOLYNK_CURSOR_NEUTRAL_EXACT_SCOPES)
|
|
1838
|
+
&& !sameStrings(scopes, HELLOLYNK_CURSOR_NEUTRAL_PREVIOUS_SCOPES)) {
|
|
1839
|
+
throw codeError("neutral_cursor_binding_invalid");
|
|
1840
|
+
}
|
|
1841
|
+
return Object.freeze({
|
|
1842
|
+
contract: value.contract,
|
|
1843
|
+
region: assertIdentifier(value.region, "neutral_cursor_binding_invalid"),
|
|
1844
|
+
principalId: assertIdentifier(value.principalId, "neutral_cursor_binding_invalid"),
|
|
1845
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_cursor_binding_invalid"),
|
|
1846
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_cursor_binding_invalid"),
|
|
1847
|
+
profileId: assertIdentifier(value.profileId, "neutral_cursor_binding_invalid"),
|
|
1848
|
+
adapterId: "cursor",
|
|
1849
|
+
version: 2,
|
|
1850
|
+
scopes: Object.freeze(scopes),
|
|
1851
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_cursor_binding_invalid"),
|
|
1852
|
+
...(releaseCount === 0 ? {} : optionalCursorReleaseBinding(value)),
|
|
1853
|
+
});
|
|
1854
|
+
}
|
|
1855
|
+
|
|
1856
|
+
function optionalCursorReleaseBinding(value) {
|
|
1857
|
+
const keys = [
|
|
1858
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
1859
|
+
];
|
|
1860
|
+
const count = keys.filter((key) => Object.hasOwn(value || {}, key)).length;
|
|
1861
|
+
if (count === 0) return Object.freeze({});
|
|
1862
|
+
if (count !== keys.length) throw codeError("neutral_cursor_launch_configuration_invalid");
|
|
1863
|
+
return Object.freeze({
|
|
1864
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_cursor_launch_configuration_invalid"),
|
|
1865
|
+
releaseManifestSha256: assertSha256(
|
|
1866
|
+
value.releaseManifestSha256,
|
|
1867
|
+
"neutral_cursor_launch_configuration_invalid",
|
|
1868
|
+
),
|
|
1869
|
+
runnerRelativePath: assertRunnerReference(
|
|
1870
|
+
value.runnerRelativePath,
|
|
1871
|
+
"neutral_cursor_launch_configuration_invalid",
|
|
1872
|
+
),
|
|
1873
|
+
profileManifestPath: assertAbsolutePath(
|
|
1874
|
+
value.profileManifestPath,
|
|
1875
|
+
"neutral_cursor_launch_configuration_invalid",
|
|
1876
|
+
),
|
|
1877
|
+
});
|
|
1878
|
+
}
|
|
1879
|
+
|
|
1880
|
+
async function assertCurrentCursorInstallationBinding(store, before, binding, capability, input) {
|
|
1881
|
+
const after = await resolveActiveInstallation(store, input);
|
|
1882
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
1883
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
1884
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
1885
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
1886
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
1887
|
+
}
|
|
1888
|
+
const delegateBinding = await resolveCursorInstallationBinding(store, binding, after);
|
|
1889
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
1890
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
1891
|
+
|| capability.delegateId !== binding.delegateId
|
|
1892
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
1893
|
+
|| capability.adapterId !== "cursor" || capability.version !== 2
|
|
1894
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
1895
|
+
throw codeError("neutral_cursor_capability_binding_mismatch");
|
|
1896
|
+
}
|
|
1897
|
+
}
|
|
1898
|
+
|
|
1899
|
+
async function resolveDshInstallationBinding(store, input, installation) {
|
|
1900
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_dsh_binding_invalid");
|
|
1901
|
+
const delegateId = input?.delegateId == null ? null : assertIdentifier(input.delegateId, "neutral_dsh_binding_invalid");
|
|
1902
|
+
let binding;
|
|
1903
|
+
try { binding = await store.resolveBinding({ profileId, delegateId }); } catch {
|
|
1904
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
1905
|
+
}
|
|
1906
|
+
if (binding.region !== installation.region || binding.principalId !== installation.principalId
|
|
1907
|
+
|| binding.hostInstallationId !== installation.hostInstallationId || !binding.adapters.includes("dsh")) {
|
|
1908
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
1909
|
+
}
|
|
1910
|
+
return binding;
|
|
1911
|
+
}
|
|
1912
|
+
|
|
1913
|
+
function dshNeutralBindingDescriptor(input, installation, delegateBinding, allowPrevious = false) {
|
|
1914
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES);
|
|
1915
|
+
if (!(sameStrings(scopes, HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES)
|
|
1916
|
+
|| (allowPrevious && sameStrings(scopes, HELLOLYNK_DSH_NEUTRAL_PREVIOUS_SCOPES)))
|
|
1917
|
+
|| (input?.adapterId ?? "dsh") !== "dsh" || Number(input?.version ?? 2) !== 2) {
|
|
1918
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
1919
|
+
}
|
|
1920
|
+
return Object.freeze({
|
|
1921
|
+
contract: HELLOLYNK_DSH_NEUTRAL_BINDING_CONTRACT, region: installation.region,
|
|
1922
|
+
principalId: installation.principalId, delegateId: delegateBinding.delegateId,
|
|
1923
|
+
hostInstallationId: installation.hostInstallationId, profileId: delegateBinding.profileId,
|
|
1924
|
+
adapterId: "dsh", version: 2, scopes,
|
|
1925
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_dsh_binding_invalid"),
|
|
1926
|
+
...optionalDshReleaseBinding(input),
|
|
1927
|
+
});
|
|
1928
|
+
}
|
|
1929
|
+
|
|
1930
|
+
function publicDshNeutralBinding(value) {
|
|
1931
|
+
assertExactKeysWithCode(value, [
|
|
1932
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
1933
|
+
"profileId", "profileManifestPath", "region", "releaseManifestSha256", "releaseRoot",
|
|
1934
|
+
"runnerRelativePath", "runtimeRoot", "scopes", "version",
|
|
1935
|
+
], "neutral_dsh_binding_invalid");
|
|
1936
|
+
if (value.contract !== HELLOLYNK_DSH_NEUTRAL_BINDING_CONTRACT || value.adapterId !== "dsh"
|
|
1937
|
+
|| Number(value.version) !== 2) throw codeError("neutral_dsh_binding_invalid");
|
|
1938
|
+
const scopes = canonicalScopes(value.scopes);
|
|
1939
|
+
if (!sameStrings(scopes, HELLOLYNK_DSH_NEUTRAL_EXACT_SCOPES)
|
|
1940
|
+
&& !sameStrings(scopes, HELLOLYNK_DSH_NEUTRAL_PREVIOUS_SCOPES)) throw codeError("neutral_dsh_binding_invalid");
|
|
1941
|
+
return Object.freeze({
|
|
1942
|
+
contract: value.contract, region: assertIdentifier(value.region, "neutral_dsh_binding_invalid"),
|
|
1943
|
+
principalId: assertIdentifier(value.principalId, "neutral_dsh_binding_invalid"),
|
|
1944
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_dsh_binding_invalid"),
|
|
1945
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_dsh_binding_invalid"),
|
|
1946
|
+
profileId: assertIdentifier(value.profileId, "neutral_dsh_binding_invalid"), adapterId: "dsh",
|
|
1947
|
+
version: 2, scopes: Object.freeze(scopes),
|
|
1948
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_dsh_binding_invalid"),
|
|
1949
|
+
...optionalDshReleaseBinding(value),
|
|
1950
|
+
});
|
|
1951
|
+
}
|
|
1952
|
+
|
|
1953
|
+
function optionalDshReleaseBinding(value) {
|
|
1954
|
+
return Object.freeze({
|
|
1955
|
+
releaseRoot: assertAbsolutePath(value?.releaseRoot, "neutral_dsh_launch_configuration_invalid"),
|
|
1956
|
+
releaseManifestSha256: assertSha256(value?.releaseManifestSha256, "neutral_dsh_launch_configuration_invalid"),
|
|
1957
|
+
runnerRelativePath: assertRunnerReference(value?.runnerRelativePath, "neutral_dsh_launch_configuration_invalid"),
|
|
1958
|
+
profileManifestPath: assertAbsolutePath(value?.profileManifestPath, "neutral_dsh_launch_configuration_invalid"),
|
|
1959
|
+
});
|
|
1960
|
+
}
|
|
1961
|
+
|
|
1962
|
+
async function assertCurrentDshInstallationBinding(store, before, binding, capability, input) {
|
|
1963
|
+
const after = await resolveActiveInstallation(store, input);
|
|
1964
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
1965
|
+
|| before.hostInstallationId !== after.hostInstallationId || binding.region !== after.region
|
|
1966
|
+
|| binding.principalId !== after.principalId || binding.hostInstallationId !== after.hostInstallationId) {
|
|
1967
|
+
throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
1968
|
+
}
|
|
1969
|
+
const delegateBinding = await resolveDshInstallationBinding(store, binding, after);
|
|
1970
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
1971
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
1972
|
+
|| capability.delegateId !== binding.delegateId || capability.hostInstallationId !== binding.hostInstallationId
|
|
1973
|
+
|| capability.adapterId !== "dsh" || capability.version !== 2 || capability.state !== "active"
|
|
1974
|
+
|| !sameStrings(capability.scopes, binding.scopes)) throw codeError("neutral_dsh_capability_binding_mismatch");
|
|
1975
|
+
}
|
|
1976
|
+
|
|
1977
|
+
function openClawNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
1978
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES);
|
|
1979
|
+
if (!(sameStrings(scopes, HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES)
|
|
1980
|
+
|| (input?.allowLegacyScopes === true
|
|
1981
|
+
&& sameStrings(scopes, HELLOLYNK_OPENCLAW_NEUTRAL_PREVIOUS_SCOPES)))
|
|
1982
|
+
|| (input?.adapterId ?? "openclaw") !== "openclaw"
|
|
1983
|
+
|| Number(input?.version ?? 2) !== 2) {
|
|
1984
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
1985
|
+
}
|
|
1986
|
+
return Object.freeze({
|
|
1987
|
+
contract: HELLOLYNK_OPENCLAW_NEUTRAL_BINDING_CONTRACT,
|
|
1988
|
+
region: installation.region,
|
|
1989
|
+
principalId: installation.principalId,
|
|
1990
|
+
delegateId: delegateBinding.delegateId,
|
|
1991
|
+
hostInstallationId: installation.hostInstallationId,
|
|
1992
|
+
profileId: delegateBinding.profileId,
|
|
1993
|
+
adapterId: "openclaw",
|
|
1994
|
+
version: 2,
|
|
1995
|
+
scopes,
|
|
1996
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_openclaw_binding_invalid"),
|
|
1997
|
+
releaseRoot: assertAbsolutePath(input?.releaseRoot, "neutral_openclaw_binding_invalid"),
|
|
1998
|
+
releaseManifestSha256: assertSha256(input?.releaseManifestSha256),
|
|
1999
|
+
runnerRelativePath: assertRunnerReference(input?.runnerRelativePath),
|
|
2000
|
+
profileManifestPath: assertAbsolutePath(
|
|
2001
|
+
input?.profileManifestPath,
|
|
2002
|
+
"neutral_openclaw_binding_invalid",
|
|
2003
|
+
),
|
|
2004
|
+
});
|
|
2005
|
+
}
|
|
2006
|
+
|
|
2007
|
+
function publicOpenClawNeutralBinding(value) {
|
|
2008
|
+
const legacyKeys = [
|
|
2009
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
2010
|
+
"profileId", "region", "runtimeRoot", "scopes", "version",
|
|
2011
|
+
];
|
|
2012
|
+
const currentKeys = [
|
|
2013
|
+
...legacyKeys,
|
|
2014
|
+
"profileManifestPath", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
2015
|
+
];
|
|
2016
|
+
const actualKeys = Object.keys(value || {}).sort();
|
|
2017
|
+
const isLegacy = sameStrings(actualKeys, [...legacyKeys].sort());
|
|
2018
|
+
if (!isLegacy && !sameStrings(actualKeys, [...currentKeys].sort())) {
|
|
2019
|
+
throw codeError("neutral_openclaw_binding_invalid");
|
|
2020
|
+
}
|
|
2021
|
+
if (value.contract !== HELLOLYNK_OPENCLAW_NEUTRAL_BINDING_CONTRACT
|
|
2022
|
+
|| value.adapterId !== "openclaw" || Number(value.version) !== 2) {
|
|
2023
|
+
throw codeError("neutral_openclaw_binding_invalid");
|
|
2024
|
+
}
|
|
2025
|
+
const scopes = canonicalScopes(value.scopes);
|
|
2026
|
+
if (!sameStrings(scopes, HELLOLYNK_OPENCLAW_NEUTRAL_EXACT_SCOPES)
|
|
2027
|
+
&& !sameStrings(scopes, HELLOLYNK_OPENCLAW_NEUTRAL_PREVIOUS_SCOPES)) {
|
|
2028
|
+
throw codeError("neutral_openclaw_binding_invalid");
|
|
2029
|
+
}
|
|
2030
|
+
return Object.freeze({
|
|
2031
|
+
contract: value.contract,
|
|
2032
|
+
region: assertIdentifier(value.region, "neutral_openclaw_binding_invalid"),
|
|
2033
|
+
principalId: assertIdentifier(value.principalId, "neutral_openclaw_binding_invalid"),
|
|
2034
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_openclaw_binding_invalid"),
|
|
2035
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_openclaw_binding_invalid"),
|
|
2036
|
+
profileId: assertIdentifier(value.profileId, "neutral_openclaw_binding_invalid"),
|
|
2037
|
+
adapterId: "openclaw",
|
|
2038
|
+
version: 2,
|
|
2039
|
+
scopes: Object.freeze(scopes),
|
|
2040
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_openclaw_binding_invalid"),
|
|
2041
|
+
...(!isLegacy ? {
|
|
2042
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_openclaw_binding_invalid"),
|
|
2043
|
+
releaseManifestSha256: assertSha256(value.releaseManifestSha256),
|
|
2044
|
+
runnerRelativePath: assertRunnerReference(value.runnerRelativePath),
|
|
2045
|
+
profileManifestPath: assertAbsolutePath(value.profileManifestPath, "neutral_openclaw_binding_invalid"),
|
|
2046
|
+
} : {}),
|
|
2047
|
+
});
|
|
2048
|
+
}
|
|
2049
|
+
|
|
2050
|
+
async function assertCurrentOpenClawInstallationBinding(store, before, binding, capability, input) {
|
|
2051
|
+
const after = await resolveActiveInstallation(store, input);
|
|
2052
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
2053
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
2054
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
2055
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
2056
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
2057
|
+
}
|
|
2058
|
+
const delegateBinding = await resolveOpenClawInstallationBinding(store, binding, after);
|
|
2059
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
2060
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
2061
|
+
|| capability.delegateId !== binding.delegateId
|
|
2062
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
2063
|
+
|| capability.adapterId !== "openclaw" || capability.version !== 2
|
|
2064
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
2065
|
+
throw codeError("neutral_openclaw_capability_binding_mismatch");
|
|
2066
|
+
}
|
|
2067
|
+
}
|
|
2068
|
+
|
|
2069
|
+
async function resolveCodexInstallationBinding(store, input, installation) {
|
|
2070
|
+
const profileId = assertIdentifier(input?.profileId, "neutral_codex_binding_invalid");
|
|
2071
|
+
const delegateId = input?.delegateId == null
|
|
2072
|
+
? null
|
|
2073
|
+
: assertIdentifier(input.delegateId, "neutral_codex_binding_invalid");
|
|
2074
|
+
let binding;
|
|
2075
|
+
try {
|
|
2076
|
+
binding = await store.resolveBinding({ profileId, delegateId });
|
|
2077
|
+
} catch {
|
|
2078
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2079
|
+
}
|
|
2080
|
+
if (binding.region !== installation.region
|
|
2081
|
+
|| binding.principalId !== installation.principalId
|
|
2082
|
+
|| binding.hostInstallationId !== installation.hostInstallationId
|
|
2083
|
+
|| !binding.adapters.includes("codex")) {
|
|
2084
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2085
|
+
}
|
|
2086
|
+
return binding;
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
function codexNeutralBindingDescriptor(input, installation, delegateBinding) {
|
|
2090
|
+
const scopes = canonicalScopes(input?.scopes || HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES);
|
|
2091
|
+
if (!(sameStrings(scopes, HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES)
|
|
2092
|
+
|| (input?.allowLegacyScopes === true
|
|
2093
|
+
&& sameStrings(scopes, HELLOLYNK_CODEX_NEUTRAL_PREVIOUS_SCOPES)))) {
|
|
2094
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2095
|
+
}
|
|
2096
|
+
if ((input?.adapterId ?? "codex") !== "codex" || Number(input?.version ?? 2) !== 2) {
|
|
2097
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2098
|
+
}
|
|
2099
|
+
return Object.freeze({
|
|
2100
|
+
contract: HELLOLYNK_CODEX_NEUTRAL_BINDING_CONTRACT,
|
|
2101
|
+
region: installation.region,
|
|
2102
|
+
principalId: installation.principalId,
|
|
2103
|
+
delegateId: delegateBinding.delegateId,
|
|
2104
|
+
hostInstallationId: installation.hostInstallationId,
|
|
2105
|
+
profileId: delegateBinding.profileId,
|
|
2106
|
+
adapterId: "codex",
|
|
2107
|
+
version: 2,
|
|
2108
|
+
scopes,
|
|
2109
|
+
runtimeRoot: assertAbsolutePath(input?.runtimeRoot, "neutral_codex_binding_invalid"),
|
|
2110
|
+
releaseRoot: assertAbsolutePath(input?.releaseRoot, "neutral_codex_binding_invalid"),
|
|
2111
|
+
releaseManifestSha256: assertSha256(input?.releaseManifestSha256),
|
|
2112
|
+
runnerRelativePath: assertRunnerReference(input?.runnerRelativePath),
|
|
2113
|
+
profileManifestPath: assertAbsolutePath(
|
|
2114
|
+
input?.profileManifestPath,
|
|
2115
|
+
"neutral_codex_launch_configuration_required",
|
|
2116
|
+
),
|
|
2117
|
+
});
|
|
2118
|
+
}
|
|
2119
|
+
|
|
2120
|
+
function publicCodexNeutralBinding(value) {
|
|
2121
|
+
assertExactKeysWithCode(value, [
|
|
2122
|
+
"adapterId", "contract", "delegateId", "hostInstallationId", "principalId",
|
|
2123
|
+
"profileId", "profileManifestPath", "region", "releaseManifestSha256", "releaseRoot", "runnerRelativePath",
|
|
2124
|
+
"runtimeRoot", "scopes", "version",
|
|
2125
|
+
], "neutral_codex_binding_invalid");
|
|
2126
|
+
if (value.contract !== HELLOLYNK_CODEX_NEUTRAL_BINDING_CONTRACT
|
|
2127
|
+
|| value.adapterId !== "codex" || Number(value.version) !== 2) {
|
|
2128
|
+
throw codeError("neutral_codex_binding_invalid");
|
|
2129
|
+
}
|
|
2130
|
+
const scopes = canonicalScopes(value.scopes);
|
|
2131
|
+
if (!sameStrings(scopes, HELLOLYNK_CODEX_NEUTRAL_EXACT_SCOPES)
|
|
2132
|
+
&& !sameStrings(scopes, HELLOLYNK_CODEX_NEUTRAL_PREVIOUS_SCOPES)) {
|
|
2133
|
+
throw codeError("neutral_codex_binding_invalid");
|
|
2134
|
+
}
|
|
2135
|
+
return Object.freeze({
|
|
2136
|
+
contract: value.contract,
|
|
2137
|
+
region: assertIdentifier(value.region, "neutral_codex_binding_invalid"),
|
|
2138
|
+
principalId: assertIdentifier(value.principalId, "neutral_codex_binding_invalid"),
|
|
2139
|
+
delegateId: assertIdentifier(value.delegateId, "neutral_codex_binding_invalid"),
|
|
2140
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "neutral_codex_binding_invalid"),
|
|
2141
|
+
profileId: assertIdentifier(value.profileId, "neutral_codex_binding_invalid"),
|
|
2142
|
+
adapterId: "codex",
|
|
2143
|
+
version: 2,
|
|
2144
|
+
scopes: Object.freeze(scopes),
|
|
2145
|
+
runtimeRoot: assertAbsolutePath(value.runtimeRoot, "neutral_codex_binding_invalid"),
|
|
2146
|
+
releaseRoot: assertAbsolutePath(value.releaseRoot, "neutral_codex_binding_invalid"),
|
|
2147
|
+
releaseManifestSha256: assertSha256(value.releaseManifestSha256),
|
|
2148
|
+
runnerRelativePath: assertRunnerReference(value.runnerRelativePath),
|
|
2149
|
+
profileManifestPath: assertAbsolutePath(
|
|
2150
|
+
value.profileManifestPath,
|
|
2151
|
+
"neutral_codex_binding_invalid",
|
|
2152
|
+
),
|
|
2153
|
+
});
|
|
2154
|
+
}
|
|
2155
|
+
|
|
2156
|
+
async function assertCurrentCodexInstallationBinding(store, before, binding, capability) {
|
|
2157
|
+
const after = await resolveActiveInstallation(store, {});
|
|
2158
|
+
if (before.region !== after.region || before.principalId !== after.principalId
|
|
2159
|
+
|| before.hostInstallationId !== after.hostInstallationId
|
|
2160
|
+
|| binding.region !== after.region || binding.principalId !== after.principalId
|
|
2161
|
+
|| binding.hostInstallationId !== after.hostInstallationId) {
|
|
2162
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2163
|
+
}
|
|
2164
|
+
// Use the delegate in the complete post-DPAPI registry snapshot. Reading
|
|
2165
|
+
// the registry a third time repeats path checks and can mix two
|
|
2166
|
+
// different snapshots; the post-read still rejects removed/rebound delegates.
|
|
2167
|
+
const matches = after.delegateBindings.filter((item) => item.profileId === binding.profileId
|
|
2168
|
+
&& item.delegateId === binding.delegateId && item.adapters.includes("codex"));
|
|
2169
|
+
if (matches.length !== 1) throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2170
|
+
const delegateBinding = matches[0];
|
|
2171
|
+
if (delegateBinding.profileId !== binding.profileId || delegateBinding.delegateId !== binding.delegateId
|
|
2172
|
+
|| capability.region !== binding.region || capability.principalId !== binding.principalId
|
|
2173
|
+
|| capability.delegateId !== binding.delegateId
|
|
2174
|
+
|| capability.hostInstallationId !== binding.hostInstallationId
|
|
2175
|
+
|| capability.adapterId !== "codex" || capability.version !== 2
|
|
2176
|
+
|| capability.state !== "active" || !sameStrings(capability.scopes, binding.scopes)) {
|
|
2177
|
+
throw codeError("neutral_codex_capability_binding_mismatch");
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
|
|
2181
|
+
function assertAbsolutePath(value, code) {
|
|
2182
|
+
const text = String(value || "");
|
|
2183
|
+
if (!text || !isAbsolute(text) || resolve(text) !== text) throw codeError(code);
|
|
2184
|
+
return text;
|
|
2185
|
+
}
|
|
2186
|
+
|
|
2187
|
+
function assertPersistentNeutralBinding(binding, storeRoot) {
|
|
2188
|
+
const pathFields = ["runtimeRoot", "releaseRoot", "profileManifestPath"];
|
|
2189
|
+
for (const field of pathFields) {
|
|
2190
|
+
if (!Object.hasOwn(binding, field)) continue;
|
|
2191
|
+
const normalized = resolve(String(binding[field] || "")).replaceAll("\\", "/").toLowerCase();
|
|
2192
|
+
if (normalized.includes("/.codex/worktrees/")
|
|
2193
|
+
|| normalized.endsWith("/.codex/worktrees")
|
|
2194
|
+
|| /(?:^|\/)[^/]*hellolynk[^/]*stage[^/]*(?:\/|$)/u.test(normalized)) {
|
|
2195
|
+
throw codeError("neutral_host_persistent_binding_path_invalid");
|
|
2196
|
+
}
|
|
2197
|
+
}
|
|
2198
|
+
if (basename(storeRoot).toLowerCase() !== "host") return binding;
|
|
2199
|
+
if (pathFields.some((field) => !Object.hasOwn(binding, field))) {
|
|
2200
|
+
throw codeError("neutral_host_persistent_binding_path_invalid");
|
|
2201
|
+
}
|
|
2202
|
+
const finalRoot = dirname(clientAccountBaseRoot(storeRoot));
|
|
2203
|
+
const releasesRoot = join(finalRoot, "releases");
|
|
2204
|
+
const releaseRelative = relative(releasesRoot, resolve(binding.releaseRoot));
|
|
2205
|
+
if (resolve(binding.runtimeRoot) !== join(storeRoot, "runtime")
|
|
2206
|
+
|| resolve(binding.profileManifestPath) !== join(storeRoot, "profile-manifest-v1.json")
|
|
2207
|
+
|| releaseRelative === "" || releaseRelative.startsWith("..") || isAbsolute(releaseRelative)) {
|
|
2208
|
+
throw codeError("neutral_host_persistent_binding_path_invalid");
|
|
2209
|
+
}
|
|
2210
|
+
return binding;
|
|
2211
|
+
}
|
|
2212
|
+
|
|
2213
|
+
function assertRunnerReference(value, code = "neutral_codex_runner_integrity_invalid") {
|
|
2214
|
+
const text = String(value || "");
|
|
2215
|
+
if (!/^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.mjs$/.test(
|
|
2216
|
+
text.replaceAll("\\", "/"),
|
|
2217
|
+
)) throw codeError(code);
|
|
2218
|
+
if (text.includes("\\") || text.split("/").includes("..")) {
|
|
2219
|
+
throw codeError(code);
|
|
2220
|
+
}
|
|
2221
|
+
return text;
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
function assertSha256(value, code = "neutral_codex_runner_integrity_invalid") {
|
|
2225
|
+
const text = String(value || "");
|
|
2226
|
+
if (!/^[a-f0-9]{64}$/.test(text)) throw codeError(code);
|
|
2227
|
+
return text;
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2230
|
+
function sameStrings(left, right) {
|
|
2231
|
+
return left.length === right.length && left.every((value, index) => value === right[index]);
|
|
2232
|
+
}
|
|
2233
|
+
|
|
2234
|
+
function createRecord(input, dependencies) {
|
|
2235
|
+
const scopes = canonicalScopes(input.scopes);
|
|
2236
|
+
const secretBuffer = Buffer.from(dependencies.random(32));
|
|
2237
|
+
if (secretBuffer.length !== 32) throw codeError("host_capability_random_invalid");
|
|
2238
|
+
const capabilityId = createCapabilityId(dependencies.random);
|
|
2239
|
+
return assertCapabilityRecord({
|
|
2240
|
+
contract: HELLOLYNK_HOST_CAPABILITY_CONTRACT,
|
|
2241
|
+
capabilityId,
|
|
2242
|
+
region: assertIdentifier(input.region, "host_capability_region_invalid"),
|
|
2243
|
+
principalId: assertIdentifier(input.principalId, "host_principal_id_invalid"),
|
|
2244
|
+
delegateId: assertIdentifier(input.delegateId, "host_delegate_id_invalid"),
|
|
2245
|
+
hostInstallationId: assertIdentifier(input.hostInstallationId, "host_installation_id_invalid"),
|
|
2246
|
+
adapterId: assertIdentifier(input.adapterId, "host_adapter_id_invalid"),
|
|
2247
|
+
version: assertPositiveInteger(input.version ?? 1, "host_capability_version_invalid"),
|
|
2248
|
+
state: "active",
|
|
2249
|
+
scopes,
|
|
2250
|
+
issuedAt: assertTimestamp(dependencies.clock()),
|
|
2251
|
+
revokedAt: null,
|
|
2252
|
+
secretBuffer,
|
|
2253
|
+
});
|
|
2254
|
+
}
|
|
2255
|
+
|
|
2256
|
+
function createCapabilityId(random) {
|
|
2257
|
+
for (let attempt = 0; attempt < 64; attempt += 1) {
|
|
2258
|
+
const bytes = Buffer.from(random(24));
|
|
2259
|
+
if (bytes.length !== 24) throw codeError("host_capability_random_invalid");
|
|
2260
|
+
const candidate = `cap_${bytes.toString("base64url")}`;
|
|
2261
|
+
if (CAPABILITY_ID_PATTERN.test(candidate) && /[A-Za-z0-9]$/.test(candidate)) return candidate;
|
|
2262
|
+
}
|
|
2263
|
+
throw codeError("host_capability_random_invalid");
|
|
2264
|
+
}
|
|
2265
|
+
|
|
2266
|
+
function serializeRecord(record) {
|
|
2267
|
+
return JSON.stringify({
|
|
2268
|
+
contract: record.contract,
|
|
2269
|
+
capabilityId: record.capabilityId,
|
|
2270
|
+
region: record.region,
|
|
2271
|
+
principalId: record.principalId,
|
|
2272
|
+
delegateId: record.delegateId,
|
|
2273
|
+
hostInstallationId: record.hostInstallationId,
|
|
2274
|
+
adapterId: record.adapterId,
|
|
2275
|
+
version: record.version,
|
|
2276
|
+
state: record.state,
|
|
2277
|
+
scopes: [...record.scopes],
|
|
2278
|
+
issuedAt: record.issuedAt,
|
|
2279
|
+
revokedAt: null,
|
|
2280
|
+
secret: record.secretBuffer.toString("base64url"),
|
|
2281
|
+
});
|
|
2282
|
+
}
|
|
2283
|
+
|
|
2284
|
+
function deserializeRecord(value) {
|
|
2285
|
+
assertExactKeys(value, [
|
|
2286
|
+
"adapterId", "capabilityId", "contract", "delegateId", "hostInstallationId",
|
|
2287
|
+
"issuedAt", "principalId", "region", "revokedAt", "scopes", "secret", "state", "version",
|
|
2288
|
+
]);
|
|
2289
|
+
if (typeof value.secret !== "string" || !/^[A-Za-z0-9_-]{43}$/.test(value.secret)) {
|
|
2290
|
+
throw codeError("host_capability_secret_invalid");
|
|
2291
|
+
}
|
|
2292
|
+
const { secret, ...publicFields } = value;
|
|
2293
|
+
return assertCapabilityRecord({
|
|
2294
|
+
...publicFields,
|
|
2295
|
+
secretBuffer: Buffer.from(value.secret, "base64url"),
|
|
2296
|
+
});
|
|
2297
|
+
}
|
|
2298
|
+
|
|
2299
|
+
function publicRecord(value) {
|
|
2300
|
+
assertExactKeys(value, [
|
|
2301
|
+
"adapterId", "capabilityId", "contract", "delegateId", "hostInstallationId",
|
|
2302
|
+
"issuedAt", "principalId", "region", "revokedAt", "scopes", "state", "version",
|
|
2303
|
+
]);
|
|
2304
|
+
if (value.contract !== HELLOLYNK_HOST_CAPABILITY_CONTRACT) throw codeError("host_capability_contract_invalid");
|
|
2305
|
+
const state = value.state;
|
|
2306
|
+
if (!['active', 'revoked'].includes(state)) throw codeError("host_capability_state_invalid");
|
|
2307
|
+
const issuedAt = assertTimestamp(value.issuedAt);
|
|
2308
|
+
const revokedAt = state === "active" && value.revokedAt === null
|
|
2309
|
+
? null : assertTimestamp(value.revokedAt);
|
|
2310
|
+
if (state === "revoked" && revokedAt < issuedAt) throw codeError("host_capability_state_invalid");
|
|
2311
|
+
return Object.freeze({
|
|
2312
|
+
contract: value.contract,
|
|
2313
|
+
capabilityId: assertCapabilityId(value.capabilityId),
|
|
2314
|
+
region: assertIdentifier(value.region, "host_capability_region_invalid"),
|
|
2315
|
+
principalId: assertIdentifier(value.principalId, "host_principal_id_invalid"),
|
|
2316
|
+
delegateId: assertIdentifier(value.delegateId, "host_delegate_id_invalid"),
|
|
2317
|
+
hostInstallationId: assertIdentifier(value.hostInstallationId, "host_installation_id_invalid"),
|
|
2318
|
+
adapterId: assertIdentifier(value.adapterId, "host_adapter_id_invalid"),
|
|
2319
|
+
version: assertPositiveInteger(value.version, "host_capability_version_invalid"),
|
|
2320
|
+
state,
|
|
2321
|
+
scopes: Object.freeze(canonicalScopes(value.scopes)),
|
|
2322
|
+
issuedAt,
|
|
2323
|
+
revokedAt,
|
|
2324
|
+
});
|
|
2325
|
+
}
|
|
2326
|
+
|
|
2327
|
+
async function resolveActiveInstallation(store, input) {
|
|
2328
|
+
const status = await store.status();
|
|
2329
|
+
const active = status.installations.filter((item) => item.state === "active");
|
|
2330
|
+
if (active.length !== 1) {
|
|
2331
|
+
throw codeError(active.length ? "host_installation_ambiguous" : "host_installation_missing");
|
|
2332
|
+
}
|
|
2333
|
+
const installation = active[0];
|
|
2334
|
+
const requestedPrincipalId = input?.principalId == null
|
|
2335
|
+
? installation.principalId
|
|
2336
|
+
: assertIdentifier(input.principalId, "host_principal_id_invalid");
|
|
2337
|
+
const requestedHostId = input?.hostInstallationId ?? input?.hostId ?? installation.hostInstallationId;
|
|
2338
|
+
if (requestedPrincipalId !== installation.principalId
|
|
2339
|
+
|| assertIdentifier(requestedHostId, "host_installation_id_invalid") !== installation.hostInstallationId) {
|
|
2340
|
+
throw codeError("host_transport_installation_owner_mismatch");
|
|
2341
|
+
}
|
|
2342
|
+
return installation;
|
|
2343
|
+
}
|
|
2344
|
+
|
|
2345
|
+
function transportCredentialRecord(input, installation) {
|
|
2346
|
+
const credentialId = assertIdentifier(input?.credentialId, "host_transport_credential_id_invalid");
|
|
2347
|
+
const secret = String(input?.secret ?? input?.credential ?? "");
|
|
2348
|
+
const match = TRANSPORT_CREDENTIAL_PATTERN.exec(secret);
|
|
2349
|
+
if (!match || match[1] !== credentialId) throw codeError("host_transport_credential_secret_invalid");
|
|
2350
|
+
const principalId = assertIdentifier(input?.principalId, "host_principal_id_invalid");
|
|
2351
|
+
const hostInstallationId = assertIdentifier(
|
|
2352
|
+
input?.hostInstallationId ?? input?.hostId,
|
|
2353
|
+
"host_installation_id_invalid",
|
|
2354
|
+
);
|
|
2355
|
+
if (principalId !== installation.principalId
|
|
2356
|
+
|| hostInstallationId !== installation.hostInstallationId) {
|
|
2357
|
+
throw codeError("host_transport_credential_owner_mismatch");
|
|
2358
|
+
}
|
|
2359
|
+
const issuedAt = normalizeTransportTimestamp(input?.issuedAt ?? input?.createdAt);
|
|
2360
|
+
const expiresAt = normalizeTransportTimestamp(input?.expiresAt);
|
|
2361
|
+
if (expiresAt <= issuedAt) throw codeError("host_transport_credential_expiry_invalid");
|
|
2362
|
+
if ((input?.state ?? "active") !== "active" || (input?.revokedAt ?? null) !== null) {
|
|
2363
|
+
throw codeError("host_transport_credential_state_invalid");
|
|
2364
|
+
}
|
|
2365
|
+
return Object.freeze({
|
|
2366
|
+
contract: HELLOLYNK_HOST_TRANSPORT_CREDENTIAL_CONTRACT,
|
|
2367
|
+
credentialId,
|
|
2368
|
+
principalId,
|
|
2369
|
+
hostInstallationId,
|
|
2370
|
+
state: "active",
|
|
2371
|
+
secret,
|
|
2372
|
+
issuedAt,
|
|
2373
|
+
expiresAt,
|
|
2374
|
+
revokedAt: null,
|
|
2375
|
+
});
|
|
2376
|
+
}
|
|
2377
|
+
|
|
2378
|
+
function deserializeTransportCredential(value, installation, now, options = {}) {
|
|
2379
|
+
assertExactKeys(value, [
|
|
2380
|
+
"contract", "credentialId", "expiresAt", "hostInstallationId", "issuedAt",
|
|
2381
|
+
"principalId", "revokedAt", "secret", "state",
|
|
2382
|
+
]);
|
|
2383
|
+
if (value.contract !== HELLOLYNK_HOST_TRANSPORT_CREDENTIAL_CONTRACT) {
|
|
2384
|
+
throw codeError("host_transport_credential_contract_invalid");
|
|
2385
|
+
}
|
|
2386
|
+
const credentialId = assertIdentifier(value.credentialId, "host_transport_credential_id_invalid");
|
|
2387
|
+
const secret = String(value.secret || "");
|
|
2388
|
+
const match = TRANSPORT_CREDENTIAL_PATTERN.exec(secret);
|
|
2389
|
+
if (!match || match[1] !== credentialId) throw codeError("host_transport_credential_secret_invalid");
|
|
2390
|
+
const principalId = assertIdentifier(value.principalId, "host_principal_id_invalid");
|
|
2391
|
+
const hostInstallationId = assertIdentifier(value.hostInstallationId, "host_installation_id_invalid");
|
|
2392
|
+
if (principalId !== installation.principalId
|
|
2393
|
+
|| hostInstallationId !== installation.hostInstallationId) {
|
|
2394
|
+
throw codeError("host_transport_credential_owner_mismatch");
|
|
2395
|
+
}
|
|
2396
|
+
const issuedAt = assertTimestamp(value.issuedAt);
|
|
2397
|
+
const expiresAt = assertTimestamp(value.expiresAt);
|
|
2398
|
+
if (expiresAt <= issuedAt) throw codeError("host_transport_credential_expiry_invalid");
|
|
2399
|
+
const state = value.state;
|
|
2400
|
+
const revokedAt = value.revokedAt === null ? null : assertTimestamp(value.revokedAt);
|
|
2401
|
+
if (!['active', 'revoked'].includes(state)
|
|
2402
|
+
|| (state === "active" && revokedAt !== null)
|
|
2403
|
+
|| (state === "revoked" && (revokedAt === null || revokedAt < issuedAt))) {
|
|
2404
|
+
throw codeError("host_transport_credential_state_invalid");
|
|
2405
|
+
}
|
|
2406
|
+
if (state !== "active") throw codeError("host_transport_credential_inactive");
|
|
2407
|
+
if (!options.allowExpired && expiresAt <= assertTimestamp(now)) {
|
|
2408
|
+
throw codeError("host_transport_credential_expired");
|
|
2409
|
+
}
|
|
2410
|
+
return Object.freeze({
|
|
2411
|
+
contract: value.contract,
|
|
2412
|
+
credentialId,
|
|
2413
|
+
principalId,
|
|
2414
|
+
hostInstallationId,
|
|
2415
|
+
state,
|
|
2416
|
+
secret,
|
|
2417
|
+
issuedAt,
|
|
2418
|
+
expiresAt,
|
|
2419
|
+
revokedAt,
|
|
2420
|
+
});
|
|
2421
|
+
}
|
|
2422
|
+
|
|
2423
|
+
function transportSessionRecord(input, installation) {
|
|
2424
|
+
const sessionId = assertTransportSessionId(input?.sessionId);
|
|
2425
|
+
const sessionSecret = String(input?.sessionSecret || "");
|
|
2426
|
+
if (!TRANSPORT_SESSION_SECRET_PATTERN.test(sessionSecret)) {
|
|
2427
|
+
throw codeError("host_transport_session_secret_invalid");
|
|
2428
|
+
}
|
|
2429
|
+
const credentialId = assertIdentifier(input?.credentialId, "host_transport_credential_id_invalid");
|
|
2430
|
+
const principalId = assertIdentifier(input?.principalId, "host_principal_id_invalid");
|
|
2431
|
+
const hostInstallationId = assertIdentifier(
|
|
2432
|
+
input?.hostInstallationId ?? input?.hostId,
|
|
2433
|
+
"host_installation_id_invalid",
|
|
2434
|
+
);
|
|
2435
|
+
if (principalId !== installation.principalId
|
|
2436
|
+
|| hostInstallationId !== installation.hostInstallationId) {
|
|
2437
|
+
throw codeError("host_transport_session_owner_mismatch");
|
|
2438
|
+
}
|
|
2439
|
+
const issuedAt = normalizeTransportTimestamp(input?.issuedAt);
|
|
2440
|
+
const expiresAt = normalizeTransportTimestamp(input?.expiresAt);
|
|
2441
|
+
if (expiresAt <= issuedAt) throw codeError("host_transport_session_expiry_invalid");
|
|
2442
|
+
return Object.freeze({
|
|
2443
|
+
contract: HELLOLYNK_HOST_TRANSPORT_SESSION_CONTRACT,
|
|
2444
|
+
sessionId,
|
|
2445
|
+
sessionSecret,
|
|
2446
|
+
credentialId,
|
|
2447
|
+
principalId,
|
|
2448
|
+
hostInstallationId,
|
|
2449
|
+
issuedAt,
|
|
2450
|
+
expiresAt,
|
|
2451
|
+
});
|
|
2452
|
+
}
|
|
2453
|
+
|
|
2454
|
+
function deserializeTransportSession(value, installation, credentialId, now) {
|
|
2455
|
+
assertExactKeys(value, [
|
|
2456
|
+
"contract", "credentialId", "expiresAt", "hostInstallationId", "issuedAt",
|
|
2457
|
+
"principalId", "sessionId", "sessionSecret",
|
|
2458
|
+
]);
|
|
2459
|
+
if (value.contract !== HELLOLYNK_HOST_TRANSPORT_SESSION_CONTRACT) {
|
|
2460
|
+
throw codeError("host_transport_session_contract_invalid");
|
|
2461
|
+
}
|
|
2462
|
+
const record = transportSessionRecord(value, installation);
|
|
2463
|
+
if (credentialId && record.credentialId !== credentialId) {
|
|
2464
|
+
throw codeError("host_transport_session_credential_mismatch");
|
|
2465
|
+
}
|
|
2466
|
+
if (record.expiresAt <= assertTimestamp(now)) throw codeError("host_transport_session_expired");
|
|
2467
|
+
return record;
|
|
2468
|
+
}
|
|
2469
|
+
|
|
2470
|
+
function clientTransportSession(value) {
|
|
2471
|
+
return Object.freeze({
|
|
2472
|
+
sessionId: value.sessionId,
|
|
2473
|
+
sessionSecret: value.sessionSecret,
|
|
2474
|
+
principalId: value.principalId,
|
|
2475
|
+
hostId: value.hostInstallationId,
|
|
2476
|
+
expiresAt: new Date(value.expiresAt).toISOString(),
|
|
2477
|
+
});
|
|
2478
|
+
}
|
|
2479
|
+
|
|
2480
|
+
function normalizeTransportTimestamp(value) {
|
|
2481
|
+
if (typeof value === "string") {
|
|
2482
|
+
const parsed = Date.parse(value);
|
|
2483
|
+
if (!Number.isFinite(parsed)) throw codeError("host_timestamp_invalid");
|
|
2484
|
+
return assertTimestamp(parsed);
|
|
2485
|
+
}
|
|
2486
|
+
return assertTimestamp(value);
|
|
2487
|
+
}
|
|
2488
|
+
|
|
2489
|
+
function assertTransportSessionId(value) {
|
|
2490
|
+
const sessionId = String(value || "");
|
|
2491
|
+
if (!TRANSPORT_SESSION_ID_PATTERN.test(sessionId)) {
|
|
2492
|
+
throw codeError("host_transport_session_id_invalid");
|
|
2493
|
+
}
|
|
2494
|
+
return sessionId;
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
function validateDeviceEnrollmentResult(value) {
|
|
2498
|
+
assertExactKeys(value, ["contract", "revision", "value"]);
|
|
2499
|
+
if (value.contract !== "hellolynk-device-enrollment-v1"
|
|
2500
|
+
|| !Number.isSafeInteger(value.revision) || value.revision < 0
|
|
2501
|
+
|| (value.value !== null && (typeof value.value !== "object" || Array.isArray(value.value)))) {
|
|
2502
|
+
throw codeError("device_enrollment_store_invalid");
|
|
2503
|
+
}
|
|
2504
|
+
return value;
|
|
2505
|
+
}
|
|
2506
|
+
|
|
2507
|
+
async function invokeWindowsStore(input) {
|
|
2508
|
+
input.signal?.throwIfAborted();
|
|
2509
|
+
const helperMetadata = await lstat(input.helperPath);
|
|
2510
|
+
if (!helperMetadata.isFile() || helperMetadata.isSymbolicLink()) throw codeError("host_protected_helper_invalid");
|
|
2511
|
+
if (!input.verifyOnly) await mkdir(input.root, { recursive: true, mode: 0o700 });
|
|
2512
|
+
const rootMetadata = await lstat(input.root);
|
|
2513
|
+
if (!rootMetadata.isDirectory() || rootMetadata.isSymbolicLink()) throw codeError("host_protected_store_root_invalid");
|
|
2514
|
+
|
|
2515
|
+
const pipeName = `HelloLynkHostProtected-${process.pid}-${randomBytes(18).toString("base64url")}`;
|
|
2516
|
+
const pipePath = `\\\\.\\pipe\\${pipeName}`;
|
|
2517
|
+
const args = [
|
|
2518
|
+
"-NoLogo", "-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-File", input.helperPath,
|
|
2519
|
+
"-Command", input.command,
|
|
2520
|
+
"-StoreRoot", input.root,
|
|
2521
|
+
"-ResultPipeName", pipeName,
|
|
2522
|
+
];
|
|
2523
|
+
if (input.capabilityId) args.push("-CapabilityId", assertCapabilityId(input.capabilityId));
|
|
2524
|
+
if (input.credentialId) args.push(
|
|
2525
|
+
"-CredentialId",
|
|
2526
|
+
assertIdentifier(input.credentialId, "host_transport_credential_id_invalid"),
|
|
2527
|
+
);
|
|
2528
|
+
if (input.sessionId) args.push("-SessionId", assertTransportSessionId(input.sessionId));
|
|
2529
|
+
if (input.principalId) args.push(
|
|
2530
|
+
"-PrincipalId",
|
|
2531
|
+
assertIdentifier(input.principalId, "host_principal_id_invalid"),
|
|
2532
|
+
);
|
|
2533
|
+
if (input.hostInstallationId) args.push(
|
|
2534
|
+
"-HostInstallationId",
|
|
2535
|
+
assertIdentifier(input.hostInstallationId, "host_installation_id_invalid"),
|
|
2536
|
+
);
|
|
2537
|
+
if (input.timestamp != null) args.push("-Timestamp", String(assertTimestamp(input.timestamp)));
|
|
2538
|
+
if (input.pathToVerify) args.push("-PathToVerify", resolve(input.pathToVerify));
|
|
2539
|
+
if (input.verifyDirectory) args.push("-VerifyDirectory");
|
|
2540
|
+
if (input.verifyOnly) args.push("-VerifyOnly");
|
|
2541
|
+
input.signal?.throwIfAborted();
|
|
2542
|
+
const child = spawn("pwsh.exe", args, {
|
|
2543
|
+
windowsHide: true,
|
|
2544
|
+
stdio: ["pipe", "pipe", "pipe"],
|
|
2545
|
+
env: sanitizeHelloLynkChildEnvironment(process.env),
|
|
2546
|
+
});
|
|
2547
|
+
const controller = new AbortController();
|
|
2548
|
+
const stdout = [];
|
|
2549
|
+
const stderr = [];
|
|
2550
|
+
child.stdout.on("data", (chunk) => stdout.push(chunk));
|
|
2551
|
+
child.stderr.on("data", (chunk) => stderr.push(chunk));
|
|
2552
|
+
const exitPromise = new Promise((resolveExit, rejectExit) => {
|
|
2553
|
+
child.on("error", rejectExit);
|
|
2554
|
+
child.on("close", resolveExit);
|
|
2555
|
+
// An early helper failure can close stdin while the parent is still writing.
|
|
2556
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) stream.on("error", rejectExit);
|
|
2557
|
+
if (input.stdin) child.stdin.end(input.stdin, "utf8");
|
|
2558
|
+
else child.stdin.end();
|
|
2559
|
+
});
|
|
2560
|
+
let timeout;
|
|
2561
|
+
let abort;
|
|
2562
|
+
try {
|
|
2563
|
+
// One budget covers helper exit AND the entire pipe read, including a
|
|
2564
|
+
// connected peer that never ends. Retain the existing 30s allowance for
|
|
2565
|
+
// PowerShell startup, the full 10s SID mutex wait and local protected I/O.
|
|
2566
|
+
const timeoutPromise = new Promise((_, rejectTimeout) => {
|
|
2567
|
+
timeout = setTimeout(() => rejectTimeout(codeError("host_protected_helper_timeout")), 30_000);
|
|
2568
|
+
timeout.unref?.();
|
|
2569
|
+
});
|
|
2570
|
+
const resultPromise = readCurrentUserPipe(pipePath, controller.signal);
|
|
2571
|
+
const completed = Promise.all([
|
|
2572
|
+
exitPromise.then((exitCode) => {
|
|
2573
|
+
if (Buffer.concat(stdout).length) throw codeError("host_protected_helper_stdout_forbidden");
|
|
2574
|
+
if (exitCode !== 0) throw codeError(publicErrorCode(Buffer.concat(stderr).toString("utf8").trim()));
|
|
2575
|
+
}),
|
|
2576
|
+
resultPromise,
|
|
2577
|
+
]);
|
|
2578
|
+
const cancelled = new Promise((_, reject) => {
|
|
2579
|
+
abort = () => reject(codeError("host_protected_result_cancelled"));
|
|
2580
|
+
if (input.signal?.aborted) abort();
|
|
2581
|
+
else input.signal?.addEventListener("abort", abort, { once: true });
|
|
2582
|
+
});
|
|
2583
|
+
const outcome = Promise.race([completed, timeoutPromise, cancelled]);
|
|
2584
|
+
const [, raw] = await outcome;
|
|
2585
|
+
try { return JSON.parse(raw); } catch { throw codeError("host_protected_result_invalid"); }
|
|
2586
|
+
} catch (error) {
|
|
2587
|
+
// A timed-out write may already have committed. Never retry it here.
|
|
2588
|
+
if (child.exitCode == null && child.signalCode == null) child.kill();
|
|
2589
|
+
throw error;
|
|
2590
|
+
} finally {
|
|
2591
|
+
clearTimeout(timeout);
|
|
2592
|
+
input.signal?.removeEventListener("abort", abort);
|
|
2593
|
+
controller.abort();
|
|
2594
|
+
for (const stream of [child.stdin, child.stdout, child.stderr]) stream.destroy();
|
|
2595
|
+
}
|
|
2596
|
+
}
|
|
2597
|
+
|
|
2598
|
+
async function readCurrentUserPipe(pipePath, signal) {
|
|
2599
|
+
while (true) {
|
|
2600
|
+
if (signal.aborted) throw codeError("host_protected_result_cancelled");
|
|
2601
|
+
try {
|
|
2602
|
+
return await new Promise((resolvePipe, rejectPipe) => {
|
|
2603
|
+
const chunks = [];
|
|
2604
|
+
let bytes = 0;
|
|
2605
|
+
let settled = false;
|
|
2606
|
+
const socket = createConnection(pipePath);
|
|
2607
|
+
const finish = (error, result) => {
|
|
2608
|
+
if (settled) return;
|
|
2609
|
+
settled = true;
|
|
2610
|
+
signal.removeEventListener("abort", abort);
|
|
2611
|
+
socket.destroy();
|
|
2612
|
+
if (error) rejectPipe(error);
|
|
2613
|
+
else resolvePipe(result);
|
|
2614
|
+
};
|
|
2615
|
+
const abort = () => finish(codeError("host_protected_result_cancelled"));
|
|
2616
|
+
signal.addEventListener("abort", abort, { once: true });
|
|
2617
|
+
socket.on("data", (chunk) => {
|
|
2618
|
+
if (settled) return;
|
|
2619
|
+
bytes += chunk.length;
|
|
2620
|
+
if (bytes > 2 * 1024 * 1024) {
|
|
2621
|
+
finish(codeError("host_protected_result_too_large"));
|
|
2622
|
+
return;
|
|
2623
|
+
}
|
|
2624
|
+
chunks.push(chunk);
|
|
2625
|
+
});
|
|
2626
|
+
socket.on("error", (error) => finish(error));
|
|
2627
|
+
socket.on("end", () => finish(null, Buffer.concat(chunks).toString("utf8")));
|
|
2628
|
+
socket.on("close", () => finish(codeError("host_protected_result_incomplete")));
|
|
2629
|
+
});
|
|
2630
|
+
} catch (error) {
|
|
2631
|
+
if (signal.aborted) throw codeError("host_protected_result_cancelled");
|
|
2632
|
+
if (error?.code !== "ENOENT") throw error;
|
|
2633
|
+
await new Promise((resolveWait) => {
|
|
2634
|
+
const finish = () => {
|
|
2635
|
+
clearTimeout(retry);
|
|
2636
|
+
signal.removeEventListener("abort", finish);
|
|
2637
|
+
resolveWait();
|
|
2638
|
+
};
|
|
2639
|
+
const retry = setTimeout(finish, 10);
|
|
2640
|
+
signal.addEventListener("abort", finish, { once: true });
|
|
2641
|
+
});
|
|
2642
|
+
}
|
|
2643
|
+
}
|
|
2644
|
+
}
|
|
2645
|
+
|
|
2646
|
+
function canonicalScopes(value) {
|
|
2647
|
+
if (!Array.isArray(value) || !value.length || value.length > 64) throw codeError("host_capability_scopes_invalid");
|
|
2648
|
+
const scopes = value.map((scope) => assertIdentifier(scope, "host_capability_scope_invalid"));
|
|
2649
|
+
const canonical = [...new Set(scopes)].sort();
|
|
2650
|
+
if (canonical.length !== scopes.length || canonical.some((scope, index) => scope !== scopes[index])) {
|
|
2651
|
+
throw codeError("host_capability_scopes_noncanonical");
|
|
2652
|
+
}
|
|
2653
|
+
return canonical;
|
|
2654
|
+
}
|
|
2655
|
+
|
|
2656
|
+
function assertCapabilityId(value) {
|
|
2657
|
+
const id = String(value || "");
|
|
2658
|
+
if (!CAPABILITY_ID_PATTERN.test(id)) throw codeError("host_capability_id_invalid");
|
|
2659
|
+
return id;
|
|
2660
|
+
}
|
|
2661
|
+
|
|
2662
|
+
function assertRoot(value) {
|
|
2663
|
+
const input = String(value || "");
|
|
2664
|
+
if (process.platform === "win32" && (!/^[A-Za-z]:[\\/]/.test(input) || input.startsWith("\\\\?\\")
|
|
2665
|
+
|| input.startsWith("\\\\") || input.includes("\0"))) {
|
|
2666
|
+
throw codeError("host_protected_store_root_invalid");
|
|
2667
|
+
}
|
|
2668
|
+
const root = resolve(input);
|
|
2669
|
+
if (!isAbsolute(root) || root.length < 4) throw codeError("host_protected_store_root_invalid");
|
|
2670
|
+
return root;
|
|
2671
|
+
}
|
|
2672
|
+
|
|
2673
|
+
function assertExactKeys(value, expected) {
|
|
2674
|
+
return assertExactKeysWithCode(value, expected, "host_capability_fields_invalid");
|
|
2675
|
+
}
|
|
2676
|
+
|
|
2677
|
+
function assertExactKeysWithCode(value, expected, code) {
|
|
2678
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) throw codeError(code);
|
|
2679
|
+
const actual = Object.keys(value).sort();
|
|
2680
|
+
const wanted = [...expected].sort();
|
|
2681
|
+
if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
|
|
2682
|
+
throw codeError(code);
|
|
2683
|
+
}
|
|
2684
|
+
}
|
|
2685
|
+
|
|
2686
|
+
function assertTimestamp(value) {
|
|
2687
|
+
const number = Number(value);
|
|
2688
|
+
if (!Number.isSafeInteger(number) || number < 0) throw codeError("host_timestamp_invalid");
|
|
2689
|
+
return number;
|
|
2690
|
+
}
|
|
2691
|
+
|
|
2692
|
+
function assertPositiveInteger(value, code) {
|
|
2693
|
+
const number = Number(value);
|
|
2694
|
+
if (!Number.isSafeInteger(number) || number < 1) throw codeError(code);
|
|
2695
|
+
return number;
|
|
2696
|
+
}
|
|
2697
|
+
|
|
2698
|
+
function assertNonNegativeInteger(value, code) {
|
|
2699
|
+
const number = Number(value);
|
|
2700
|
+
if (!Number.isSafeInteger(number) || number < 0) throw codeError(code);
|
|
2701
|
+
return number;
|
|
2702
|
+
}
|
|
2703
|
+
|
|
2704
|
+
function publicErrorCode(value) {
|
|
2705
|
+
const matches = String(value || "").match(/[a-z][a-z0-9_]{2,127}/g);
|
|
2706
|
+
return matches?.at(-1) || "host_protected_store_operation_failed";
|
|
2707
|
+
}
|
|
2708
|
+
|
|
2709
|
+
function normalizeClock(value) {
|
|
2710
|
+
if (value == null) return () => Date.now();
|
|
2711
|
+
if (typeof value === "function") return value;
|
|
2712
|
+
if (typeof value?.now === "function") return () => value.now();
|
|
2713
|
+
throw codeError("host_clock_invalid");
|
|
2714
|
+
}
|
|
2715
|
+
|
|
2716
|
+
function fail(code) { throw codeError(code); }
|
|
2717
|
+
function codeError(code) {
|
|
2718
|
+
const error = new Error(code);
|
|
2719
|
+
error.code = code;
|
|
2720
|
+
return error;
|
|
2721
|
+
}
|