@hellolynk-ai/trae-work-mcp 0.0.0-stage → 0.4.95

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +101 -2
  3. package/bin/hellolynk-client-setup.mjs +2748 -0
  4. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  5. package/hellolynk-client-pack-manifest.json +619 -0
  6. package/hellolynk-client-pack-pin.json +4 -0
  7. package/integrations/trae-work/package.json +11 -0
  8. package/integrations/trae-work/runtime/neutral-host-bootstrap.mjs +38 -0
  9. package/integrations/trae-work/runtime/neutral-host-release-entry.mjs +16 -0
  10. package/integrations/trae-work/runtime/trae-work-bootstrap.mjs +30 -0
  11. package/integrations/trae-work/server/dynamic-ui-contract.mjs +376 -0
  12. package/integrations/trae-work/server/host-lifecycle.mjs +128 -0
  13. package/integrations/trae-work/server/mcp-proxy.mjs +181 -0
  14. package/integrations/trae-work/server/mcp-server.mjs +784 -0
  15. package/integrations/trae-work/server/native-task-cdp.mjs +241 -0
  16. package/integrations/trae-work/server/native-task-worker.mjs +199 -0
  17. package/integrations/trae-work/server/neutral-host-adapter.mjs +725 -0
  18. package/integrations/trae-work/server/trae-neutral-production.mjs +97 -0
  19. package/integrations/trae-work/server/trae-ui-launch.mjs +182 -0
  20. package/integrations/trae-work/server/ui-action-gate.mjs +122 -0
  21. package/integrations/trae-work/skills/hellolynk-inbox/SKILL.md +9 -0
  22. package/package.json +61 -4
  23. package/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  24. package/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  25. package/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  26. package/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  27. package/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  28. package/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  29. package/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  30. package/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  31. package/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  32. package/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  33. package/runtime/connect-activate.mjs +837 -0
  34. package/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  35. package/runtime/core/hellolynk-client-account.mjs +180 -0
  36. package/runtime/core/hellolynk-client-core.mjs +770 -0
  37. package/runtime/core/hellolynk-client-profile.mjs +191 -0
  38. package/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  39. package/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  40. package/runtime/core/hellolynk-client-update.mjs +453 -0
  41. package/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  42. package/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  43. package/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  44. package/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  45. package/runtime/core/hellolynk-host-auth.mjs +347 -0
  46. package/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  47. package/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  48. package/runtime/core/hellolynk-host.mjs +2374 -0
  49. package/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  50. package/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  51. package/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  52. package/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  53. package/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  54. package/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  55. package/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  56. package/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  57. package/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  58. package/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  59. package/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  60. package/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  61. package/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  62. package/runtime/core/hellolynk-model-inbox.mjs +230 -0
  63. package/runtime/core/hellolynk-model-notices.cjs +60 -0
  64. package/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  65. package/runtime/core/hellolynk-native-conversations.mjs +280 -0
  66. package/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  67. package/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  68. package/runtime/core/hellolynk-native-notice.mjs +50 -0
  69. package/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  70. package/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  71. package/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  72. package/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  73. package/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  74. package/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  75. package/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  76. package/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  77. package/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  78. package/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  79. package/runtime/core/hellolynk-release-retention.mjs +277 -0
  80. package/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  81. package/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  82. package/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  83. package/runtime/neutral-host-bootstrap.mjs +38 -0
  84. package/runtime/neutral-host-release-entry.mjs +16 -0
  85. package/runtime/neutral-host-release-loader.mjs +156 -0
  86. package/runtime/neutral-host-release-manifest.json +394 -0
  87. package/runtime/neutral-host-release-pin.json +4 -0
  88. package/runtime/platform/windows/configure-agent.ps1 +281 -0
  89. package/runtime/platform/windows/credential-store.ps1 +844 -0
  90. package/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  91. package/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  92. package/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  93. package/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  94. package/runtime/trae-work-bootstrap.mjs +30 -0
  95. package/runtime/workbench/candidate-bootstrap.mjs +54 -0
  96. package/runtime/workbench/contract.mjs +1314 -0
  97. package/runtime/workbench/hellolynk-workbench-build.json +215 -0
  98. package/runtime/workbench/host-capabilities.mjs +80 -0
  99. package/runtime/workbench/i18n.mjs +1127 -0
  100. package/runtime/workbench/loopback-surface.mjs +740 -0
  101. package/runtime/workbench/mcp-app-bundle.mjs +97 -0
  102. package/runtime/workbench/service.mjs +485 -0
  103. package/runtime/workbench/state.mjs +421 -0
  104. package/runtime/workbench/transports.mjs +171 -0
  105. package/runtime/workbench/ui/app.mjs +6120 -0
  106. package/runtime/workbench/ui/connection-form.mjs +155 -0
  107. package/runtime/workbench/ui/hellolynk-mark.png +0 -0
  108. package/runtime/workbench/ui/http-transport.mjs +247 -0
  109. package/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  110. package/runtime/workbench/ui/icons/close.svg +3 -0
  111. package/runtime/workbench/ui/icons/contacts.svg +3 -0
  112. package/runtime/workbench/ui/icons/inbox.svg +3 -0
  113. package/runtime/workbench/ui/icons/refresh.svg +3 -0
  114. package/runtime/workbench/ui/icons/search.svg +3 -0
  115. package/runtime/workbench/ui/icons/security.svg +3 -0
  116. package/runtime/workbench/ui/icons/settings.svg +3 -0
  117. package/runtime/workbench/ui/index.html +139 -0
  118. package/runtime/workbench/ui/loopback-entry.mjs +26 -0
  119. package/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  120. package/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  121. package/runtime/workbench/ui/styles.css +686 -0
  122. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
  123. package/tools/hellolynk-install-client-skills.mjs +1211 -0
@@ -0,0 +1,38 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { loadNeutralHostRelease } from "./neutral-host-release-loader.mjs";
5
+
6
+ export async function runNeutralHostBootstrap(argv = process.argv.slice(2)) {
7
+ const options = parse(argv);
8
+ const release = await loadNeutralHostRelease();
9
+ if (typeof release?.run !== "function") throw codeError("neutral_trae_work_runner_integrity_invalid");
10
+ return release.run({ root: options.root });
11
+ }
12
+ function parse(argv) { if (argv.length !== 3 || argv[0] !== "run" || argv[1] !== "--root" || !path.isAbsolute(String(argv[2] || ""))) throw codeError("neutral_trae_work_launch_configuration_required"); return { root: path.resolve(argv[2]) }; }
13
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }
14
+ // Start failures and a failed Host both end here. A failed Host keeps its lock until this
15
+ // process exits on its own: a closed stderr pipe (the detached launcher may be gone) must
16
+ // not crash it before admitted work finishes.
17
+ function exitWithFailure(code) { process.stderr.on("error", () => {}); process.stderr.write(`${JSON.stringify({ ok: false, code })}\n`); process.exitCode = 1; }
18
+
19
+ // A stray rejection must not take the Host down: log a stable code and keep serving. A synchronous
20
+ // uncaught exception leaves state unknown: stop the Host (releases its lock), then exit; 15 s bounds a stuck stop.
21
+ function guardHostProcess(result) {
22
+ process.on("unhandledRejection", (reason) => writeFailure("neutral_trae_work_unhandled_rejection", reason));
23
+ process.on("uncaughtException", (error) => {
24
+ writeFailure("neutral_trae_work_uncaught_exception", error);
25
+ process.exitCode = 1;
26
+ const exit = () => process.exit(1);
27
+ setTimeout(exit, 15_000).unref?.();
28
+ Promise.resolve().then(() => result?.stop?.("uncaught_exception")).then(exit, exit);
29
+ });
30
+ return result;
31
+ }
32
+
33
+ function writeFailure(code, cause) {
34
+ if (!process.stderr.listenerCount("error")) process.stderr.on("error", () => {});
35
+ process.stderr.write(`${JSON.stringify({ ok: false, code, cause: String(cause?.code || cause?.name || "").slice(0, 80) })}\n`);
36
+ }
37
+
38
+ if (import.meta.url === pathToFileURL(process.argv[1] || "").href) runNeutralHostBootstrap().then((result) => guardHostProcess(result)?.waitUntilStopped?.() || new Promise(() => {})).catch((error) => exitWithFailure(String(error?.code || "neutral_trae_work_launch_failed")));
@@ -0,0 +1,16 @@
1
+ import fs from "node:fs/promises";
2
+ import path from "node:path";
3
+ import { createHostProtectedStore } from "../../../clients/core/hellolynk-host-protected-store.mjs";
4
+ import { createNeutralHostInstallationStore, defaultNeutralHostStoreRoot } from "../../../clients/core/hellolynk-neutral-host-installation-store.mjs";
5
+ import { startHelloLynkNeutralHostAssembly } from "../../../clients/core/hellolynk-neutral-host-assembly.mjs";
6
+
7
+ export async function createNeutralHostReleaseRuntime() { return Object.freeze({ defaultStoreRoot: (env) => defaultNeutralHostStoreRoot("win32", env), createInstallationStore: (options) => createNeutralHostInstallationStore(options), createProtectedStore: (options) => createHostProtectedStore(options), run: async ({ root }) => runProductionHost(root) }); }
8
+ async function runProductionHost(rootInput) {
9
+ const root = path.resolve(String(rootInput || "")); if (!path.isAbsolute(root) || root.length < 4) throw codeError("neutral_trae_work_launch_configuration_required");
10
+ const installationStore = createNeutralHostInstallationStore({ root }); const protectedStore = createHostProtectedStore({ root, installationStore }); const binding = await protectedStore.resolveTraeWorkNeutralBinding();
11
+ const profiles = await readProfileManifest(binding.profileManifestPath); const assembly = await startHelloLynkNeutralHostAssembly({ root, runtimeRoot: binding.runtimeRoot, profiles: profiles.map((profileFile) => ({ profileFile })) }, { installationStore, protectedStore });
12
+ // A failed Host never reaches "stopped"; end the wait so the bootstrap exits with its code.
13
+ return Object.freeze({ stop: assembly.stop, waitUntilStopped: async () => { while (assembly.host.status !== "stopped") { if (assembly.host.status === "failed") throw codeError(assembly.host.failureCode || "neutral_host_stop_failed"); await new Promise((resolve) => setTimeout(resolve, 50)); } } });
14
+ }
15
+ async function readProfileManifest(input) { const manifestPath = path.resolve(String(input || "")); if (!path.isAbsolute(manifestPath)) throw codeError("neutral_trae_work_launch_manifest_invalid"); let manifest; try { manifest = JSON.parse(await fs.readFile(manifestPath, "utf8")); } catch { throw codeError("neutral_trae_work_launch_manifest_invalid"); } if (!manifest || manifest.contract !== "hellolynk-neutral-host-profile-manifest-v1" || !Array.isArray(manifest.profiles) || !manifest.profiles.length || manifest.profiles.length > 16 || Object.keys(manifest).sort().join(",") !== "contract,profiles") throw codeError("neutral_trae_work_launch_manifest_invalid"); const profiles = manifest.profiles.map((value) => path.resolve(String(value || ""))); if (profiles.some((value, index) => !path.isAbsolute(value) || profiles.indexOf(value) !== index)) throw codeError("neutral_trae_work_launch_manifest_invalid"); return Object.freeze(profiles); }
16
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }
@@ -0,0 +1,30 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { fileURLToPath, pathToFileURL } from "node:url";
4
+ import { isWorkbenchConnectionRequired } from "../../../clients/core/hellolynk-workbench-connection.mjs";
5
+ import { resolveClientAccountRoot } from "../../../clients/core/hellolynk-client-account.mjs";
6
+ import { createHostProtectedStore } from "../../../clients/core/hellolynk-host-protected-store.mjs";
7
+ import { createNeutralHostInstallationStore, defaultNeutralHostStoreRoot } from "../../../clients/core/hellolynk-neutral-host-installation-store.mjs";
8
+ import { verifyPinnedBootstrap } from "../server/host-lifecycle.mjs";
9
+
10
+ const RELEASE_ROOT = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..", "..", "..");
11
+ export async function runTraeWorkBootstrap(argv = process.argv.slice(2), options = {}) {
12
+ const expected = parse(argv); const root = resolveClientAccountRoot("trae-work", options.storeRoot || defaultNeutralHostStoreRoot("win32", options.env || process.env));
13
+ const installationStore = createNeutralHostInstallationStore({ root }); const store = createHostProtectedStore({ root, installationStore });
14
+ let binding;
15
+ try { binding = await store.resolveTraeWorkNeutralBinding(); }
16
+ catch (error) { if (!isWorkbenchConnectionRequired(error)) throw error; }
17
+ const verification = binding || {
18
+ adapterId: "trae-work", profileId: "hellolynk-host-trae-work-v2", version: 2, scopes: [],
19
+ region: "intl", principalId: "connection-preflight", delegateId: "connection-preflight", hostInstallationId: "connection-preflight",
20
+ releaseRoot: RELEASE_ROOT, releaseManifestSha256: expected, runnerRelativePath: "runtime/neutral-host-bootstrap.mjs",
21
+ runtimeRoot: path.join(root, "runtime"), profileManifestPath: path.join(root, "profile-manifest-v1.json"),
22
+ };
23
+ if (path.resolve(verification.releaseRoot || "") !== RELEASE_ROOT || verification.releaseManifestSha256 !== expected) throw codeError("neutral_trae_work_runner_integrity_invalid");
24
+ await verifyPinnedBootstrap(verification, { storeRoot: root });
25
+ const proxy = await import(pathToFileURL(path.join(RELEASE_ROOT, "integrations", "trae-work", "server", "mcp-proxy.mjs")).href);
26
+ return proxy.runTraeWorkMcpProxy({ storeRoot: root });
27
+ }
28
+ function parse(argv) { if (argv.length !== 3 || argv[0] !== "run" || argv[1] !== "--expected-manifest-sha256" || !/^[a-f0-9]{64}$/.test(String(argv[2] || ""))) throw codeError("trae_work_bootstrap_arguments_invalid"); return argv[2]; }
29
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }
30
+ if (process.argv[1] && pathToFileURL(process.argv[1]).href === import.meta.url) runTraeWorkBootstrap().catch((error) => { process.stderr.write(`${JSON.stringify({ ok: false, error: error.code || "trae_work_bootstrap_failed" })}\n`); process.exitCode = 1; });
@@ -0,0 +1,376 @@
1
+ import crypto from "node:crypto";
2
+
3
+ export const TRAE_DYNAMIC_UI_CONTRACT = "hellolynk-trae-dynamic-ui-v1";
4
+ export const TRAE_DYNAMIC_UI_VERSION = 1;
5
+ export const TRAE_DYNAMIC_UI_RENDERER = "HelloLynkTraeLocalWebV18";
6
+
7
+ const VIEWS = new Set([
8
+ "status", "inbox", "message", "contacts", "contact", "outbox", "send", "action", "error",
9
+ ]);
10
+ const STATES = new Set(["ready", "empty", "pending", "blocked"]);
11
+ const EXPOSURES = new Set(["aggregate-only", "metadata-only", "selected-record", "selected-content"]);
12
+ const ID = /^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/;
13
+ const HELLOLYNK_ID = /^hellolynk:(intl|cn):[a-z0-9][a-z0-9._-]{2,179}$/;
14
+ const ISO_TIMESTAMP = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}(?:\.\d{3})?Z$/;
15
+ const FORBIDDEN_KEYS = new Set(["__proto__", "constructor", "prototype", "html", "innerHTML", "script"]);
16
+
17
+ export function createDynamicUiEnvelope(view, state, data, options = {}) {
18
+ if (!VIEWS.has(view)) throw codeError("trae_dynamic_ui_view_invalid");
19
+ if (!STATES.has(state)) throw codeError("trae_dynamic_ui_state_invalid");
20
+ const exposure = options.exposure || "metadata-only";
21
+ if (!EXPOSURES.has(exposure)) throw codeError("trae_dynamic_ui_exposure_invalid");
22
+ const envelope = {
23
+ contract: TRAE_DYNAMIC_UI_CONTRACT,
24
+ version: TRAE_DYNAMIC_UI_VERSION,
25
+ renderer: TRAE_DYNAMIC_UI_RENDERER,
26
+ view,
27
+ state,
28
+ exposure,
29
+ component: componentFor(view),
30
+ data: sanitizeWidgetValue({ ...data, actions: componentActions(view) }, widgetLimits(view, data, options.privateText !== true)),
31
+ };
32
+ return Object.freeze(envelope);
33
+ }
34
+
35
+ export function validateDynamicUiEnvelope(value) {
36
+ if (!isPlainObject(value)) throw codeError("trae_dynamic_ui_envelope_invalid");
37
+ assertExactKeys(value, ["contract", "version", "renderer", "view", "state", "exposure", "component", "data"]);
38
+ if (value.contract !== TRAE_DYNAMIC_UI_CONTRACT || value.version !== TRAE_DYNAMIC_UI_VERSION
39
+ || value.renderer !== TRAE_DYNAMIC_UI_RENDERER || !VIEWS.has(value.view)
40
+ || !STATES.has(value.state) || !EXPOSURES.has(value.exposure)) {
41
+ throw codeError("trae_dynamic_ui_envelope_invalid");
42
+ }
43
+ validateComponent(value.component, value.view);
44
+ sanitizeWidgetValue(value.data, widgetLimits(value.view, value.data));
45
+ return value;
46
+ }
47
+
48
+ export function sanitizeWidgetText(value, maximumBytes = 20_000) {
49
+ return normalizeWidgetText(value, maximumBytes)
50
+ .replace(/&/g, "&amp;")
51
+ .replace(/</g, "&lt;")
52
+ .replace(/>/g, "&gt;")
53
+ .replace(/"/g, "&quot;")
54
+ .replace(/'/g, "&#39;");
55
+ }
56
+
57
+ export function maskHelloLynkId(value) {
58
+ const hellolynkId = safeHelloLynkId(value);
59
+ const region = hellolynkId.split(":")[1];
60
+ const digest = crypto.createHash("sha256").update(hellolynkId).digest("hex").slice(0, 8);
61
+ return `hellolynk:${region}:masked-${digest}`;
62
+ }
63
+
64
+ export function projectInboxView(raw) {
65
+ const messages = safeArray(raw?.messages, "trae_inbox_contract_invalid");
66
+ const items = messages.map((message) => {
67
+ const item = plain(message, "trae_inbox_contract_invalid");
68
+ return {
69
+ localMessageId: safeId(item.localMessageId, "trae_inbox_contract_invalid"),
70
+ senderPrincipalDisplayName: optionalWidgetText(item.senderPrincipalDisplayName, 320),
71
+ senderDelegateDisplayName: optionalWidgetText(item.senderDelegateDisplayName, 320),
72
+ peerHelloLynkIdMasked: item.peerHelloLynkId == null ? null : maskHelloLynkId(item.peerHelloLynkId),
73
+ messageType: safeId(item.messageType, "trae_inbox_contract_invalid"),
74
+ unread: boolean(item.unread, "trae_inbox_contract_invalid"),
75
+ createdAt: safeTimestamp(item.createdAt, "trae_inbox_contract_invalid"),
76
+ updatedAt: safeTimestamp(item.updatedAt, "trae_inbox_contract_invalid"),
77
+ bodyAvailable: true,
78
+ };
79
+ });
80
+ const unreadCount = items.filter((item) => item.unread).length;
81
+ return createDynamicUiEnvelope("inbox", items.length ? "ready" : "empty", {
82
+ counts: { total: items.length, unread: unreadCount },
83
+ items,
84
+ controls: {
85
+ readMessageTool: "hellolynk_read_inbox_message",
86
+ markReadTool: "hellolynk_mark_inbox_message_read",
87
+ deleteReadTool: "hellolynk_delete_read_inbox_messages",
88
+ },
89
+ }, { exposure: "metadata-only" });
90
+ }
91
+
92
+ function componentFor(view) {
93
+ return view === "send" ? "message-form" : view === "action" ? "message-confirm"
94
+ : view === "status" || view === "error" ? "status-card" : "messenger-inbox";
95
+ }
96
+ function componentActions(view) {
97
+ if (view === "inbox") return [{ id: "refresh", label: "刷新消息", type: "tool-call", tool: "hellolynk_render_inbox", params: {} }, { id: "open-detail", label: "打开消息", type: "tool-call", tool: "hellolynk_read_inbox_message", params: {} }];
98
+ if (view === "contacts") return [{ id: "refresh-contacts", label: "刷新联系人", type: "tool-call", tool: "hellolynk_render_contacts", params: {} }];
99
+ if (view === "outbox") return [{ id: "refresh-outbox", label: "刷新发出记录", type: "tool-call", tool: "hellolynk_render_outbox", params: {} }];
100
+ return [];
101
+ }
102
+ function validateComponent(value, view) {
103
+ if (value !== componentFor(view)) throw codeError("trae_dynamic_ui_envelope_invalid");
104
+ }
105
+
106
+ // Private-only dashboard payload. It is deliberately never used as
107
+ // structuredContent: internal contract tests use this model-safe projection.
108
+ // Production TRAE exposes only open/status tools; the fixed local Web UI reads
109
+ // private data directly from the neutral Host adapter over the loopback server.
110
+ export function projectPrivateInboxDashboard(inboxRaw, contactsRaw, requestsRaw, outboxRaw) {
111
+ const messages = safeArray(inboxRaw?.messages, "trae_inbox_contract_invalid").map((item) => ({
112
+ localMessageId: safeId(item?.localMessageId, "trae_inbox_contract_invalid"),
113
+ peerHelloLynkId: safeHelloLynkId(item?.peerHelloLynkId),
114
+ senderPrincipalDisplayName: optionalWidgetText(item?.senderPrincipalDisplayName, 320),
115
+ senderDelegateDisplayName: optionalWidgetText(item?.senderDelegateDisplayName, 320),
116
+ messageType: safeId(item?.messageType, "trae_inbox_contract_invalid"),
117
+ text: checkedWidgetText(item?.text, 64 * 1024), unread: boolean(item?.unread, "trae_inbox_contract_invalid"),
118
+ createdAt: safeTimestamp(item?.createdAt, "trae_inbox_contract_invalid"),
119
+ }));
120
+ const contacts = safeArray(contactsRaw?.contacts, "trae_contacts_contract_invalid").map((item) => ({
121
+ contactId: safeId(item?.contactId, "trae_contacts_contract_invalid"), hellolynkId: safeHelloLynkId(item?.hellolynkId),
122
+ displayName: checkedWidgetText(item?.displayName, 320),
123
+ updatedAt: safeTimestamp(item?.updatedAt, "trae_contacts_contract_invalid"),
124
+ }));
125
+ const contactRequests = safeArray(requestsRaw?.requests, "trae_contact_requests_contract_invalid").map((item) => {
126
+ if (item?.humanOnly !== true || item?.modelVisibility !== "none") throw codeError("trae_contact_requests_contract_invalid");
127
+ return {
128
+ localRecordId: safeId(item?.localRecordId, "trae_contact_requests_contract_invalid"),
129
+ state: safeId(item?.state, "trae_contact_requests_contract_invalid"),
130
+ introText: item?.introText == null ? null : checkedWidgetText(item.introText, 20_000),
131
+ senderHelloLynkId: item?.sender?.hellolynkId == null ? null : safeHelloLynkId(item.sender.hellolynkId),
132
+ senderDisplayName: item?.sender?.verifiedDisplayName == null ? null : checkedWidgetText(item.sender.verifiedDisplayName, 320),
133
+ };
134
+ });
135
+ const outbox = safeArray(outboxRaw?.messages, "trae_outbox_contract_invalid").map((item) => ({
136
+ clientMessageId: safeId(item?.clientMessageId, "trae_outbox_contract_invalid"), recipientHelloLynkId: safeHelloLynkId(item?.recipientHelloLynkId),
137
+ text: checkedWidgetText(item?.text, 64 * 1024), status: safeId(item?.status, "trae_outbox_contract_invalid"),
138
+ retryAllowed: boolean(item?.retryAllowed, "trae_outbox_contract_invalid"), createdAt: safeTimestamp(item?.createdAt, "trae_outbox_contract_invalid"),
139
+ }));
140
+ return createDynamicUiEnvelope("inbox", messages.length ? "ready" : "empty", {
141
+ dashboard: { messages, contacts, contactRequests, outbox },
142
+ controls: { markReadTool: "hellolynk_mark_inbox_message_read", deleteReadTool: "hellolynk_delete_read_inbox_messages", retryTool: "hellolynk_retry_outbox_message", submitTool: "hellolynk_request_human_send", saveContactTool: "hellolynk_save_contact", deleteContactTool: "hellolynk_delete_contact" },
143
+ }, { exposure: "selected-content", privateText: true });
144
+ }
145
+
146
+ export function projectSelectedMessage(raw, localMessageId) {
147
+ const messages = safeArray(raw?.messages, "trae_inbox_contract_invalid");
148
+ const selected = messages.find((item) => item?.localMessageId === localMessageId);
149
+ if (!selected) throw codeError("host_app_record_not_found");
150
+ return createDynamicUiEnvelope("message", "ready", {
151
+ localMessageId: safeId(selected.localMessageId, "trae_inbox_contract_invalid"),
152
+ senderPrincipalDisplayName: optionalWidgetText(selected.senderPrincipalDisplayName, 320),
153
+ senderDelegateDisplayName: optionalWidgetText(selected.senderDelegateDisplayName, 320),
154
+ peerHelloLynkIdMasked: selected.peerHelloLynkId == null ? null : maskHelloLynkId(selected.peerHelloLynkId),
155
+ messageType: safeId(selected.messageType, "trae_inbox_contract_invalid"),
156
+ text: checkedWidgetText(selected.text, 64 * 1024),
157
+ unread: boolean(selected.unread, "trae_inbox_contract_invalid"),
158
+ createdAt: safeTimestamp(selected.createdAt, "trae_inbox_contract_invalid"),
159
+ controls: { markReadTool: "hellolynk_mark_inbox_message_read" },
160
+ }, { exposure: "selected-content", privateText: true });
161
+ }
162
+
163
+ export function projectSelectedMessageMetadata(raw, localMessageId) {
164
+ const messages = safeArray(raw?.messages, "trae_inbox_contract_invalid");
165
+ const selected = messages.find((item) => item?.localMessageId === localMessageId);
166
+ if (!selected) throw codeError("host_app_record_not_found");
167
+ return createDynamicUiEnvelope("message", "ready", {
168
+ localMessageId: safeId(selected.localMessageId, "trae_inbox_contract_invalid"),
169
+ senderPrincipalDisplayName: optionalWidgetText(selected.senderPrincipalDisplayName, 320),
170
+ senderDelegateDisplayName: optionalWidgetText(selected.senderDelegateDisplayName, 320),
171
+ peerHelloLynkIdMasked: selected.peerHelloLynkId == null ? null : maskHelloLynkId(selected.peerHelloLynkId),
172
+ messageType: safeId(selected.messageType, "trae_inbox_contract_invalid"),
173
+ unread: boolean(selected.unread, "trae_inbox_contract_invalid"),
174
+ createdAt: safeTimestamp(selected.createdAt, "trae_inbox_contract_invalid"),
175
+ bodyAvailableInPrivateUi: true,
176
+ bodyExcludedFromModelResult: true,
177
+ }, { exposure: "selected-record" });
178
+ }
179
+
180
+ export function projectContactsView(contactsRaw, requestsRaw) {
181
+ const contacts = safeArray(contactsRaw?.contacts, "trae_contacts_contract_invalid").map((contact) => ({
182
+ contactId: safeId(contact?.contactId, "trae_contacts_contract_invalid"),
183
+ displayName: checkedWidgetText(contact?.displayName, 320),
184
+ hellolynkIdMasked: maskHelloLynkId(contact?.hellolynkId),
185
+ updatedAt: safeTimestamp(contact?.updatedAt, "trae_contacts_contract_invalid"),
186
+ }));
187
+ const requests = safeArray(requestsRaw?.requests, "trae_contact_requests_contract_invalid");
188
+ const requestCounts = Object.create(null);
189
+ for (const request of requests) {
190
+ if (request?.modelVisibility !== "none" || request?.humanOnly !== true) {
191
+ throw codeError("trae_contact_requests_contract_invalid");
192
+ }
193
+ const state = safeId(request.state, "trae_contact_requests_contract_invalid");
194
+ requestCounts[state] = (requestCounts[state] || 0) + 1;
195
+ }
196
+ return createDynamicUiEnvelope("contacts", contacts.length || requests.length ? "ready" : "empty", {
197
+ counts: { contacts: contacts.length, contactRequests: requests.length },
198
+ contacts,
199
+ contactRequests: {
200
+ countsByState: requestCounts,
201
+ detailsWithheld: true,
202
+ reason: "host_model_visibility_none",
203
+ },
204
+ controls: {
205
+ contactDetailTool: "hellolynk_get_contact_detail",
206
+ saveContactTool: "hellolynk_save_contact",
207
+ deleteContactTool: "hellolynk_delete_contact",
208
+ },
209
+ }, { exposure: "metadata-only" });
210
+ }
211
+
212
+ export function projectContactDetail(raw, contactId) {
213
+ const contact = safeArray(raw?.contacts, "trae_contacts_contract_invalid")
214
+ .find((item) => item?.contactId === contactId);
215
+ if (!contact) throw codeError("host_app_record_not_found");
216
+ return createDynamicUiEnvelope("contact", "ready", {
217
+ contactId: safeId(contact.contactId, "trae_contacts_contract_invalid"),
218
+ displayName: checkedWidgetText(contact.displayName, 320),
219
+ hellolynkId: safeHelloLynkId(contact.hellolynkId),
220
+ updatedAt: safeTimestamp(contact.updatedAt, "trae_contacts_contract_invalid"),
221
+ controls: { saveContactTool: "hellolynk_save_contact", deleteContactTool: "hellolynk_delete_contact" },
222
+ }, { exposure: "selected-record" });
223
+ }
224
+
225
+ export function projectOutboxView(raw) {
226
+ const messages = safeArray(raw?.messages, "trae_outbox_contract_invalid").map((message) => ({
227
+ clientMessageId: safeId(message?.clientMessageId, "trae_outbox_contract_invalid"),
228
+ recipientHelloLynkIdMasked: maskHelloLynkId(message?.recipientHelloLynkId),
229
+ messageType: safeId(message?.messageType, "trae_outbox_contract_invalid"),
230
+ status: safeId(message?.status, "trae_outbox_contract_invalid"),
231
+ retryAllowed: boolean(message?.retryAllowed, "trae_outbox_contract_invalid"),
232
+ createdAt: safeTimestamp(message?.createdAt, "trae_outbox_contract_invalid"),
233
+ updatedAt: safeTimestamp(message?.updatedAt, "trae_outbox_contract_invalid"),
234
+ }));
235
+ return createDynamicUiEnvelope("outbox", messages.length ? "ready" : "empty", {
236
+ counts: {
237
+ total: messages.length,
238
+ retryable: messages.filter((item) => item.retryAllowed).length,
239
+ },
240
+ items: messages,
241
+ controls: { retryTool: "hellolynk_retry_outbox_message" },
242
+ }, { exposure: "metadata-only" });
243
+ }
244
+
245
+ export function projectSendForm(contactsRaw) {
246
+ const contacts = safeArray(contactsRaw?.contacts, "trae_contacts_contract_invalid").map((contact) => ({
247
+ contactId: safeId(contact?.contactId, "trae_contacts_contract_invalid"),
248
+ displayName: checkedWidgetText(contact?.displayName, 320),
249
+ hellolynkIdMasked: maskHelloLynkId(contact?.hellolynkId),
250
+ }));
251
+ return createDynamicUiEnvelope("send", "ready", {
252
+ contacts,
253
+ fields: [
254
+ { name: "contactId", type: "contact-select", required: false },
255
+ { name: "recipientHelloLynkId", type: "hellolynk-id", required: false },
256
+ { name: "text", type: "multiline-text", required: true, maximumLength: 20_000 },
257
+ ],
258
+ controls: {
259
+ submitTool: "hellolynk_request_human_send",
260
+ submitRequiresExplicitConfirmation: true,
261
+ hostNativeConfirmationStillRequired: true,
262
+ },
263
+ }, { exposure: "metadata-only" });
264
+ }
265
+
266
+ export function projectActionView(value, message) {
267
+ const state = String(value?.state || "");
268
+ if (!/^(pending|approved|cancelled|consuming|expired|failed|invalidated)$/.test(state)) {
269
+ throw codeError("human_action_status_invalid");
270
+ }
271
+ const createdAtMs = safeInteger(value?.createdAtMs, "human_action_status_invalid");
272
+ const expiresAtMs = safeInteger(value?.expiresAtMs, "human_action_status_invalid");
273
+ if (expiresAtMs < createdAtMs || (value?.code != null
274
+ && !/^human_confirmation_[a-z0-9_]{1,100}$/.test(String(value.code)))) {
275
+ throw codeError("human_action_status_invalid");
276
+ }
277
+ return createDynamicUiEnvelope("action", state === "pending" ? "pending" : "ready", {
278
+ message: checkedWidgetText(message, 500),
279
+ actionId: safeId(value?.actionId, "human_action_status_invalid"),
280
+ state,
281
+ createdAtMs,
282
+ expiresAtMs,
283
+ ...(typeof value?.idempotent === "boolean" ? { idempotent: value.idempotent } : {}),
284
+ ...(value?.code == null ? {} : { code: value.code }),
285
+ }, { exposure: "aggregate-only" });
286
+ }
287
+
288
+ export function projectErrorView(errorCode) {
289
+ return createDynamicUiEnvelope("error", "blocked", {
290
+ errorCode: safeId(errorCode, "trae_dynamic_ui_error_invalid"),
291
+ }, { exposure: "aggregate-only" });
292
+ }
293
+
294
+ function widgetLimits(view, data, escapeHtml = false) {
295
+ return {
296
+ maximumDepth: 8, maximumItems: 500, escapeHtml,
297
+ // The Host contract counts authorized instance routes, not unique contacts.
298
+ // Only this root array gets its 1,000-row budget, in both encode and validate.
299
+ maximumAuthorizedContacts: view === "status" && Array.isArray(data?.authorizedContacts) ? 1_000 : 500,
300
+ };
301
+ }
302
+
303
+ function sanitizeWidgetValue(value, limits, depth = 0, seen = new Set(), field = "") {
304
+ if (value == null || typeof value === "boolean") return value;
305
+ if (typeof value === "number") {
306
+ if (!Number.isSafeInteger(value)) throw codeError("trae_dynamic_ui_value_invalid");
307
+ return value;
308
+ }
309
+ if (typeof value === "string") return limits.escapeHtml
310
+ ? sanitizeWidgetText(value, 64 * 1024)
311
+ : normalizeWidgetText(value, 64 * 1024);
312
+ if (typeof value !== "object" || seen.has(value) || depth >= limits.maximumDepth) {
313
+ throw codeError("trae_dynamic_ui_value_invalid");
314
+ }
315
+ seen.add(value);
316
+ try {
317
+ if (Array.isArray(value)) {
318
+ const maximum = depth === 1 && field === "authorizedContacts" ? limits.maximumAuthorizedContacts : limits.maximumItems;
319
+ if (value.length > maximum) throw codeError("trae_dynamic_ui_value_invalid");
320
+ return value.map((item) => sanitizeWidgetValue(item, limits, depth + 1, seen));
321
+ }
322
+ if (!isPlainObject(value)) throw codeError("trae_dynamic_ui_value_invalid");
323
+ const entries = Object.entries(value);
324
+ if (entries.length > limits.maximumItems) throw codeError("trae_dynamic_ui_value_invalid");
325
+ const result = {};
326
+ for (const [key, nested] of entries) {
327
+ if (!/^[A-Za-z][A-Za-z0-9_]{0,79}$/.test(key) || FORBIDDEN_KEYS.has(key)) {
328
+ throw codeError("trae_dynamic_ui_key_invalid");
329
+ }
330
+ result[key] = sanitizeWidgetValue(nested, limits, depth + 1, seen, key);
331
+ }
332
+ return result;
333
+ } finally {
334
+ seen.delete(value);
335
+ }
336
+ }
337
+
338
+ function normalizeWidgetText(value, maximumBytes) {
339
+ if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > maximumBytes || value.includes("\0")) {
340
+ throw codeError("trae_dynamic_ui_text_invalid");
341
+ }
342
+ return value.replace(/[\u0001-\u0008\u000B\u000C\u000E-\u001F\u007F\u202A-\u202E\u2066-\u2069]/g, "");
343
+ }
344
+
345
+ function safeArray(value, code) {
346
+ if (!Array.isArray(value) || value.length > 1_000) throw codeError(code);
347
+ return value;
348
+ }
349
+ function plain(value, code) { if (!isPlainObject(value)) throw codeError(code); return value; }
350
+ function safeId(value, code) { const text = String(value || ""); if (!ID.test(text)) throw codeError(code); return text; }
351
+ function safeHelloLynkId(value) { const text = String(value || ""); if (!HELLOLYNK_ID.test(text)) throw codeError("trae_hellolynk_id_invalid"); return text; }
352
+ function safeTimestamp(value, code) {
353
+ const text = String(value || "");
354
+ if (!ISO_TIMESTAMP.test(text) || new Date(text).toISOString() !== text) throw codeError(code);
355
+ return text;
356
+ }
357
+ function safeInteger(value, code) { const number = Number(value); if (!Number.isSafeInteger(number) || number < 0) throw codeError(code); return number; }
358
+ function boolean(value, code) { if (typeof value !== "boolean") throw codeError(code); return value; }
359
+ function checkedWidgetText(value, maximumBytes) {
360
+ if (typeof value !== "string" || Buffer.byteLength(value, "utf8") > maximumBytes || value.includes("\0")) {
361
+ throw codeError("trae_dynamic_ui_text_invalid");
362
+ }
363
+ return value;
364
+ }
365
+ function optionalWidgetText(value, maximumBytes) { return value == null ? null : checkedWidgetText(value, maximumBytes); }
366
+ function assertExactKeys(value, expected) {
367
+ const actual = Object.keys(value).sort(); const wanted = [...expected].sort();
368
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
369
+ throw codeError("trae_dynamic_ui_envelope_invalid");
370
+ }
371
+ }
372
+ function isPlainObject(value) {
373
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
374
+ const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null;
375
+ }
376
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }
@@ -0,0 +1,128 @@
1
+ import crypto from "node:crypto";
2
+ import fs from "node:fs/promises";
3
+ import path from "node:path";
4
+ import { launchPinnedNeutralHostBootstrap } from "../../../runtime/core/hellolynk-neutral-host-launcher.mjs";
5
+
6
+ const RUNNER_RELATIVE = "runtime/neutral-host-bootstrap.mjs";
7
+ const LOCK_STALE_MS = 10_000;
8
+ const SAFE_RELATIVE = /^(?:[A-Za-z0-9][A-Za-z0-9._-]*\/)*[A-Za-z0-9][A-Za-z0-9._-]*\.(?:cjs|mjs|ps1)$/;
9
+ // The TRAE server entry points are part of the pinned closure. Shared Workbench
10
+ // runtime modules are pinned separately by REQUIRED_RUNTIME_RECORDS below.
11
+ const REQUIRED_RELEASE_RECORDS = Object.freeze([
12
+ "release/integrations/trae-work/package.json",
13
+ "release/integrations/trae-work/server/trae-neutral-production.mjs", "release/integrations/trae-work/server/mcp-proxy.mjs",
14
+ "release/integrations/trae-work/server/mcp-server.mjs", "release/integrations/trae-work/server/neutral-host-adapter.mjs",
15
+ "release/integrations/trae-work/server/dynamic-ui-contract.mjs",
16
+ "release/integrations/trae-work/server/ui-action-gate.mjs", "release/integrations/trae-work/server/host-lifecycle.mjs",
17
+ "release/integrations/trae-work/server/trae-ui-launch.mjs",
18
+ "release/integrations/trae-work/server/native-task-cdp.mjs", "release/integrations/trae-work/server/native-task-worker.mjs",
19
+ "release/integrations/trae-work/skills/hellolynk-inbox/SKILL.md",
20
+ ]);
21
+ const REQUIRED_RUNTIME_RECORDS = Object.freeze([
22
+ "neutral-host-bootstrap.mjs", "neutral-host-release-loader.mjs", "neutral-host-release-entry.mjs",
23
+ "bootstrap/core/hellolynk-host-protected-store.mjs", "bootstrap/core/hellolynk-neutral-host-installation-store.mjs",
24
+ "bootstrap/core/hellolynk-host-auth.mjs", "bootstrap/core/hellolynk-client-profile.mjs",
25
+ "bootstrap/core/hellolynk-platform-credential-store.mjs", "bootstrap/core/hellolynk-client-core.mjs",
26
+ "bootstrap/platform/windows/hellolynk-host-protected-store.ps1",
27
+ "core/hellolynk-local-data-migration.mjs", "core/hellolynk-local-data-protection.mjs",
28
+ "core/hellolynk-local-identity-trust.mjs",
29
+ "core/hellolynk-neutral-host-workbench-read-model.mjs",
30
+ "core/hellolynk-native-worker-lease.mjs",
31
+ "core/hellolynk-workbench-action-grants.mjs",
32
+ "platform/windows/hellolynk-local-data-key.ps1", "workbench/contract.mjs",
33
+ "workbench/candidate-bootstrap.mjs",
34
+ ]);
35
+
36
+ export async function launchTraeWorkNeutralHost(binding, options = {}) {
37
+ const launch = normalizeLaunchBinding(binding, options);
38
+ await verifyPinnedBootstrap(launch);
39
+ const lockPath = path.join(launch.runtimeRoot, "neutral-host", `${tupleDigest(launch)}.trae-work-launch.lock`);
40
+ await ensureSafeDirectory(launch.runtimeRoot, true); await ensureSafeDirectory(path.dirname(lockPath), true);
41
+ const wait = options.wait || delay; const deadline = Date.now() + bounded(options.lockTimeoutMs, 15_000, 500, 60_000);
42
+ let ownsLock = false;
43
+ while (Date.now() < deadline) {
44
+ try {
45
+ await fs.mkdir(lockPath, { mode: 0o700 });
46
+ await fs.writeFile(path.join(lockPath, "owner.json"), `${JSON.stringify({ contract: "hellolynk-trae-work-host-launch-lock-v1", pid: process.pid, createdAtMs: Date.now() })}\n`, { encoding: "utf8", flag: "wx", mode: 0o600 });
47
+ ownsLock = true; break;
48
+ } catch (error) {
49
+ if (error?.code !== "EEXIST") throw codeError("neutral_trae_work_launch_failed");
50
+ await ensureSafeDirectory(lockPath, false); await reclaimDeadLock(lockPath).catch(() => false); await wait(50);
51
+ }
52
+ }
53
+ if (!ownsLock) throw codeError("neutral_trae_work_launch_lock_timeout");
54
+ try {
55
+ await launchPinnedNeutralHostBootstrap(launch, {
56
+ storeRoot: launch.storeRoot,
57
+ ...(options.spawnRunner ? { spawnRunner: options.spawnRunner } : {}),
58
+ ...(options.bootstrapObservationMs !== undefined ? { bootstrapObservationMs: options.bootstrapObservationMs } : {}),
59
+ ...(options.bootstrapFailureObservationMs !== undefined ? { bootstrapFailureObservationMs: options.bootstrapFailureObservationMs } : {}),
60
+ ...(options.bootstrapFailureDeadlineMs !== undefined ? { bootstrapFailureDeadlineMs: options.bootstrapFailureDeadlineMs } : {}),
61
+ env: options.env || process.env,
62
+ });
63
+ return Object.freeze({ started: true, owner: "trae_work_mcp_process" });
64
+ } finally {
65
+ await fs.rm(path.join(lockPath, "owner.json"), { force: true }).catch(() => {}); await fs.rmdir(lockPath).catch(() => {});
66
+ }
67
+ }
68
+
69
+ export async function verifyPinnedBootstrap(binding, options = {}) {
70
+ const launch = normalizeLaunchBinding(binding, options);
71
+ const [pinBytes, manifestBytes] = await Promise.all([
72
+ readSafeFile(path.join(launch.releaseRoot, "runtime", "neutral-host-release-pin.json"), 16 * 1024),
73
+ readSafeFile(path.join(launch.releaseRoot, "runtime", "neutral-host-release-manifest.json"), 256 * 1024),
74
+ ]);
75
+ const pin = parseJson(pinBytes); const manifest = parseJson(manifestBytes);
76
+ if (!plain(pin) || !exactKeys(pin, ["contract", "manifestSha256"]) || pin.contract !== "hellolynk-neutral-host-release-pin-v1"
77
+ || pin.manifestSha256 !== launch.releaseManifestSha256 || sha256(manifestBytes) !== launch.releaseManifestSha256
78
+ || !plain(manifest) || !exactKeys(manifest, ["contract", "entry", "files"])
79
+ || manifest.contract !== "hellolynk-neutral-host-release-manifest-v1" || manifest.entry !== "neutral-host-release-entry.mjs"
80
+ || !Array.isArray(manifest.files) || manifest.files.length < 39 || manifest.files.length > 256) fail();
81
+ const records = new Map(); const resolvedPaths = new Map();
82
+ for (const item of manifest.files) {
83
+ if (!plain(item) || !exactKeys(item, ["path", "sha256"]) || !/^[a-f0-9]{64}$/.test(String(item.sha256 || ""))) fail();
84
+ const recordPath = String(item.path || ""); const file = resolveManifestFile(launch, recordPath); const canonical = canonicalPath(file);
85
+ if (records.has(recordPath) || resolvedPaths.has(canonical)) fail();
86
+ const bytes = await readSafeFile(file, 2 * 1024 * 1024).catch(() => fail()); if (sha256(bytes) !== item.sha256) fail();
87
+ const record = Object.freeze({ recordPath, file, bytes }); records.set(recordPath, record); resolvedPaths.set(canonical, record);
88
+ }
89
+ for (const required of [...REQUIRED_RUNTIME_RECORDS, ...REQUIRED_RELEASE_RECORDS]) if (!records.has(required)) fail();
90
+ if (!samePath(records.get("neutral-host-bootstrap.mjs").file, launch.bootstrapPath) || !records.has(manifest.entry)) fail();
91
+ verifyStaticImportClosure(records, resolvedPaths, launch.releaseRoot);
92
+ return Object.freeze({ ok: true, bootstrapPath: launch.bootstrapPath, verifiedFileCount: records.size });
93
+ }
94
+
95
+ function resolveManifestFile(launch, recordPath) {
96
+ if (recordPath.startsWith("release/")) {
97
+ if (!REQUIRED_RELEASE_RECORDS.includes(recordPath)) fail();
98
+ return insideRoot(launch.releaseRoot, recordPath.slice(8));
99
+ }
100
+ if (!SAFE_RELATIVE.test(recordPath) || recordPath.split("/").includes("..")) fail(); return insideRoot(path.join(launch.releaseRoot, "runtime"), recordPath);
101
+ }
102
+ function verifyStaticImportClosure(records, resolvedPaths, releaseRoot) {
103
+ const pattern = /(?:\bfrom\s*|\bimport\s*\(\s*|\bimport\s+)["'](\.{1,2}\/[^"']+)["']/g;
104
+ for (const record of records.values()) {
105
+ if (!record.file.endsWith(".mjs")) continue; const source = record.bytes.toString("utf8"); pattern.lastIndex = 0;
106
+ for (let match = pattern.exec(source); match; match = pattern.exec(source)) {
107
+ const imported = path.resolve(path.dirname(record.file), ...match[1].split("/"));
108
+ if (!inside(releaseRoot, imported) || !resolvedPaths.has(canonicalPath(imported))) fail();
109
+ }
110
+ }
111
+ }
112
+ function normalizeLaunchBinding(value, options) {
113
+ if (!plain(value)) throw codeError("neutral_trae_work_launch_configuration_required");
114
+ const runtimeRoot = absolute(value.runtimeRoot); const releaseRoot = absolute(value.releaseRoot); const profileManifestPath = absolute(value.profileManifestPath);
115
+ const releaseManifestSha256 = String(value.releaseManifestSha256 || "");
116
+ if (value.runnerRelativePath !== RUNNER_RELATIVE || !/^[a-f0-9]{64}$/.test(releaseManifestSha256) || path.basename(runtimeRoot).toLowerCase() !== "runtime") throw codeError("neutral_trae_work_launch_configuration_required");
117
+ const derivedStoreRoot = path.dirname(runtimeRoot); const storeRoot = options.storeRoot ? absolute(options.storeRoot) : derivedStoreRoot;
118
+ if (!samePath(storeRoot, derivedStoreRoot)) throw codeError("neutral_trae_work_launch_configuration_required");
119
+ return Object.freeze({ runtimeRoot, releaseRoot, profileManifestPath, releaseManifestSha256, storeRoot, runnerRelativePath: RUNNER_RELATIVE,
120
+ bootstrapPath: path.join(releaseRoot, ...RUNNER_RELATIVE.split("/")), region: safeId(value.region), principalId: safeId(value.principalId), delegateId: safeId(value.delegateId), hostInstallationId: safeId(value.hostInstallationId), profileId: safeId(value.profileId), adapterId: value.adapterId === "trae-work" ? "trae-work" : fail(), version: Number(value.version), scopes: Object.freeze([...(value.scopes || [])].map(String)) });
121
+ }
122
+ async function readSafeFile(file, maximum) { await assertNoReparseAncestors(file); let handle; try { handle = await fs.open(file, "r"); } catch { throw codeError("neutral_trae_work_runner_unavailable"); } try { const before = await handle.stat({ bigint: true }); if (!before.isFile() || before.nlink !== 1n || before.size > BigInt(maximum)) fail(); const bytes = await handle.readFile(); const after = await handle.stat({ bigint: true }); if (!sameIdentity(before, after)) fail(); return bytes; } catch (error) { if (/^neutral_trae_work_/.test(String(error?.code || ""))) throw error; throw codeError("neutral_trae_work_runner_integrity_invalid"); } finally { await handle.close().catch(() => {}); } }
123
+ async function assertNoReparseAncestors(target) { const resolved = path.resolve(target); const parsed = path.parse(resolved); let cursor = parsed.root; for (const segment of path.relative(parsed.root, resolved).split(path.sep).filter(Boolean)) { cursor = path.join(cursor, segment); const metadata = await fs.lstat(cursor).catch(() => { throw codeError("neutral_trae_work_runner_unavailable"); }); if (metadata.isSymbolicLink()) throw codeError("neutral_trae_work_runner_integrity_invalid"); } }
124
+ async function ensureSafeDirectory(target, create) { if (create) await fs.mkdir(target, { recursive: true, mode: 0o700 }); const metadata = await fs.lstat(target).catch(() => null); if (!metadata?.isDirectory() || metadata.isSymbolicLink()) throw codeError("neutral_trae_work_launch_configuration_required"); }
125
+ async function reclaimDeadLock(lockPath) { const metadata = await fs.lstat(lockPath); let owner; try { owner = JSON.parse(await fs.readFile(path.join(lockPath, "owner.json"), "utf8")); } catch { return false; } const pid = Number(owner?.pid); if (Date.now() - metadata.mtimeMs <= LOCK_STALE_MS || (Number.isSafeInteger(pid) && pid > 0 && processAlive(pid))) return false; await fs.rm(path.join(lockPath, "owner.json"), { force: true }); await fs.rmdir(lockPath).catch(() => {}); return true; }
126
+ function insideRoot(root, relative) { const result = path.resolve(root, ...relative.split("/")); if (!inside(root, result)) fail(); return result; }
127
+ function inside(root, target) { const relative = path.relative(path.resolve(root), path.resolve(target)); return Boolean(relative) && !relative.startsWith(`..${path.sep}`) && relative !== ".." && !path.isAbsolute(relative); }
128
+ function tupleDigest(value) { return sha256(`${value.region}\0${value.principalId}\0${value.hostInstallationId}`); } function sha256(value) { return crypto.createHash("sha256").update(value).digest("hex"); } function parseJson(value) { try { return JSON.parse(value); } catch { fail(); } } function sameIdentity(left, right) { return left.dev === right.dev && left.ino === right.ino && left.size === right.size && left.mtimeNs === right.mtimeNs && left.nlink === right.nlink; } function processAlive(pid) { try { process.kill(pid, 0); return true; } catch (error) { return error?.code === "EPERM"; } } function safeId(value) { const result = String(value || ""); if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(result)) fail(); return result; } function absolute(value) { const result = String(value || ""); if (!path.isAbsolute(result) || path.resolve(result) !== result) fail(); return result; } function samePath(left, right) { return process.platform === "win32" ? left.toLowerCase() === right.toLowerCase() : left === right; } function canonicalPath(value) { const result = path.resolve(value); return process.platform === "win32" ? result.toLowerCase() : result; } function exactKeys(value, expected) { const actual = Object.keys(value).sort(); const wanted = [...expected].sort(); return actual.length === wanted.length && actual.every((item, index) => item === wanted[index]); } function bounded(value, fallback, minimum, maximum) { const result = Number(value ?? fallback); return Number.isSafeInteger(result) && result >= minimum && result <= maximum ? result : fallback; } function plain(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function delay(ms) { return new Promise((resolve) => setTimeout(resolve, ms)); } function fail() { throw codeError("neutral_trae_work_runner_integrity_invalid"); } function codeError(code) { const error = new Error(code); error.code = code; return error; }