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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/.agents/plugins/marketplace.json +20 -0
  2. package/LICENSE +92 -0
  3. package/README.md +101 -2
  4. package/bin/hellolynk-client-setup.mjs +2748 -0
  5. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  6. package/hellolynk-client-pack-manifest.json +619 -0
  7. package/hellolynk-client-pack-pin.json +4 -0
  8. package/package.json +58 -4
  9. package/plugins/hellolynk-codex/.app.json +3 -0
  10. package/plugins/hellolynk-codex/.codex-plugin/plugin.json +38 -0
  11. package/plugins/hellolynk-codex/.mcp.json +19 -0
  12. package/plugins/hellolynk-codex/assets/hellolynk-mark.png +0 -0
  13. package/plugins/hellolynk-codex/package.json +11 -0
  14. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  15. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  16. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  17. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  18. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  19. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  20. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  21. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  22. package/plugins/hellolynk-codex/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  23. package/plugins/hellolynk-codex/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  24. package/plugins/hellolynk-codex/runtime/connect-activate.mjs +837 -0
  25. package/plugins/hellolynk-codex/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  26. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-account.mjs +180 -0
  27. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-core.mjs +770 -0
  28. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-profile.mjs +191 -0
  29. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  30. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  31. package/plugins/hellolynk-codex/runtime/core/hellolynk-client-update.mjs +453 -0
  32. package/plugins/hellolynk-codex/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  33. package/plugins/hellolynk-codex/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  34. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  35. package/plugins/hellolynk-codex/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  36. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-auth.mjs +347 -0
  37. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  38. package/plugins/hellolynk-codex/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  39. package/plugins/hellolynk-codex/runtime/core/hellolynk-host.mjs +2374 -0
  40. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  41. package/plugins/hellolynk-codex/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  42. package/plugins/hellolynk-codex/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  43. package/plugins/hellolynk-codex/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  44. package/plugins/hellolynk-codex/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  45. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  46. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  47. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  48. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  49. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  50. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  51. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  52. package/plugins/hellolynk-codex/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  53. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-inbox.mjs +230 -0
  54. package/plugins/hellolynk-codex/runtime/core/hellolynk-model-notices.cjs +60 -0
  55. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  56. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-conversations.mjs +280 -0
  57. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  58. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  59. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-notice.mjs +50 -0
  60. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  61. package/plugins/hellolynk-codex/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  62. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  63. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  64. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  65. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  66. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  67. package/plugins/hellolynk-codex/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  68. package/plugins/hellolynk-codex/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  69. package/plugins/hellolynk-codex/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  70. package/plugins/hellolynk-codex/runtime/core/hellolynk-release-retention.mjs +277 -0
  71. package/plugins/hellolynk-codex/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  72. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  73. package/plugins/hellolynk-codex/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  74. package/plugins/hellolynk-codex/runtime/neutral-host-bootstrap.mjs +64 -0
  75. package/plugins/hellolynk-codex/runtime/neutral-host-release-entry.mjs +70 -0
  76. package/plugins/hellolynk-codex/runtime/neutral-host-release-loader.mjs +156 -0
  77. package/plugins/hellolynk-codex/runtime/neutral-host-release-manifest.json +358 -0
  78. package/plugins/hellolynk-codex/runtime/neutral-host-release-pin.json +4 -0
  79. package/plugins/hellolynk-codex/runtime/platform/windows/configure-agent.ps1 +281 -0
  80. package/plugins/hellolynk-codex/runtime/platform/windows/credential-store.ps1 +844 -0
  81. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  82. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  83. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  84. package/plugins/hellolynk-codex/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  85. package/plugins/hellolynk-codex/runtime/workbench/candidate-bootstrap.mjs +54 -0
  86. package/plugins/hellolynk-codex/runtime/workbench/contract.mjs +1314 -0
  87. package/plugins/hellolynk-codex/runtime/workbench/hellolynk-workbench-build.json +215 -0
  88. package/plugins/hellolynk-codex/runtime/workbench/host-capabilities.mjs +80 -0
  89. package/plugins/hellolynk-codex/runtime/workbench/i18n.mjs +1127 -0
  90. package/plugins/hellolynk-codex/runtime/workbench/loopback-surface.mjs +740 -0
  91. package/plugins/hellolynk-codex/runtime/workbench/mcp-app-bundle.mjs +97 -0
  92. package/plugins/hellolynk-codex/runtime/workbench/service.mjs +485 -0
  93. package/plugins/hellolynk-codex/runtime/workbench/state.mjs +421 -0
  94. package/plugins/hellolynk-codex/runtime/workbench/transports.mjs +171 -0
  95. package/plugins/hellolynk-codex/runtime/workbench/ui/app.mjs +6120 -0
  96. package/plugins/hellolynk-codex/runtime/workbench/ui/connection-form.mjs +155 -0
  97. package/plugins/hellolynk-codex/runtime/workbench/ui/hellolynk-mark.png +0 -0
  98. package/plugins/hellolynk-codex/runtime/workbench/ui/http-transport.mjs +247 -0
  99. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  100. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/close.svg +3 -0
  101. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/contacts.svg +3 -0
  102. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/inbox.svg +3 -0
  103. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/refresh.svg +3 -0
  104. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/search.svg +3 -0
  105. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/security.svg +3 -0
  106. package/plugins/hellolynk-codex/runtime/workbench/ui/icons/settings.svg +3 -0
  107. package/plugins/hellolynk-codex/runtime/workbench/ui/index.html +139 -0
  108. package/plugins/hellolynk-codex/runtime/workbench/ui/loopback-entry.mjs +26 -0
  109. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  110. package/plugins/hellolynk-codex/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  111. package/plugins/hellolynk-codex/runtime/workbench/ui/styles.css +686 -0
  112. package/plugins/hellolynk-codex/server/codex-neutral-production.mjs +520 -0
  113. package/plugins/hellolynk-codex/server/mcp-proxy.mjs +306 -0
  114. package/plugins/hellolynk-codex/server/mcp-server.mjs +1193 -0
  115. package/plugins/hellolynk-codex/server/native-conversation-directory.mjs +33 -0
  116. package/plugins/hellolynk-codex/server/native-reply-routes.mjs +32 -0
  117. package/plugins/hellolynk-codex/server/native-task-client.mjs +382 -0
  118. package/plugins/hellolynk-codex/server/native-task-runtime.mjs +8 -0
  119. package/plugins/hellolynk-codex/server/neutral-host-adapter.mjs +649 -0
  120. package/plugins/hellolynk-codex/server/workbench-runtime.mjs +180 -0
  121. package/plugins/hellolynk-codex/skills/hellolynk-open/SKILL.md +11 -0
  122. package/plugins/hellolynk-codex/skills/hellolynk-open/agents/openai.yaml +4 -0
  123. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
@@ -0,0 +1,2374 @@
1
+ import { nativeConversationId, nativeConversationTitle, nativeConversationQuery, nativeDirectoryResult } from "./hellolynk-native-conversations.mjs";
2
+ import { modelInboxCaller } from "./hellolynk-model-inbox.mjs";
3
+ import crypto from "node:crypto";
4
+ import { projectInboxReadState } from "./hellolynk-inbox-read-state.mjs";
5
+ import { LOCAL_IDENTITY_PROOF_PATH, validateLocalIdentityProofRequest, signLocalIdentityProof } from "./hellolynk-local-identity-trust.mjs";
6
+ import { LOCAL_PEER_EXCHANGE_PATH, validateLocalPeerRequest, signLocalPeerResult } from "./hellolynk-local-peer-transport.mjs";
7
+ import {
8
+ asRawBody,
9
+ assertCapabilityRecord,
10
+ assertHostGeneration,
11
+ assertIdentifier,
12
+ assertMethod,
13
+ assertPathname,
14
+ assertRequestFresh,
15
+ createNonceReplayGuard,
16
+ hostRequestAuthMatches,
17
+ HELLOLYNK_HOST_AUTH_HEADERS,
18
+ HELLOLYNK_HOST_REQUEST_MAX_SKEW_MS,
19
+ } from "./hellolynk-host-auth.mjs";
20
+ import {
21
+ HELLOLYNK_WORKBENCH_RESULT_CONTRACT,
22
+ HELLOLYNK_WORKBENCH_SCHEMA_VERSION,
23
+ HELLOLYNK_WORKBENCH_COMMAND_TYPES,
24
+ validateWorkbenchCommand,
25
+ validateWorkbenchCommandResult,
26
+ validateWorkbenchReadRequest,
27
+ validateWorkbenchSnapshot,
28
+ } from "../workbench/contract.mjs";
29
+ import {
30
+ createHelloLynkWorkbenchActionGrantRegistry,
31
+ rejectConsumedHelloLynkWorkbenchActionGrant,
32
+ revokeHelloLynkWorkbenchActionGrants,
33
+ } from "./hellolynk-workbench-action-grants.mjs";
34
+
35
+ export const HELLOLYNK_HOST_CONTRACT = "hellolynk-host-v2";
36
+ export const HELLOLYNK_HOST_ATTACHMENT_HEARTBEAT_MS = 10_000;
37
+ export const HELLOLYNK_HOST_ATTACHMENT_LEASE_MS = 30_000;
38
+ export const HELLOLYNK_HOST_IDLE_SHUTDOWN_GRACE_MS = 10_000;
39
+
40
+ export const HELLOLYNK_HOST_SCOPES = Object.freeze({
41
+ statusRead: "status:read",
42
+ attachmentsManage: "attachments:manage",
43
+ uiLaunch: "ui:launch",
44
+ agentMessagesSend: "agent-messages:send",
45
+ humanUiSurface: "surface:human-ui",
46
+ appInboxRead: "app-data:inbox-read",
47
+ appInboxManage: "app-data:inbox-manage",
48
+ appContactsRead: "app-data:contacts-read",
49
+ appContactsManage: "app-data:contacts-manage",
50
+ appAuthorizedMessaging: "app-data:authorized-messaging",
51
+ appContactRequestsRead: "app-data:contact-requests-read",
52
+ appOutboxRead: "app-data:outbox-read",
53
+ appOutboxRetry: "app-data:outbox-retry",
54
+ appHumanSendRequest: "app-data:human-send-request",
55
+ appChangesRead: "app-data:changes-read",
56
+ });
57
+
58
+ export const HELLOLYNK_HOST_PATHS = Object.freeze({
59
+ health: "/host/v1/health",
60
+ status: "/host/v1/status",
61
+ localIdentityProof: LOCAL_IDENTITY_PROOF_PATH,
62
+ localPeerExchange: LOCAL_PEER_EXCHANGE_PATH,
63
+ attachmentAttach: "/host/v1/attachments",
64
+ attachmentHeartbeat: "/host/v1/attachments/heartbeat",
65
+ attachmentDetach: "/host/v1/attachments/detach",
66
+ agentMessages: "/host/v1/agent-messages",
67
+ appInboxMessages: "/host/v1/app/inbox/messages",
68
+ appInboxUnreadCount: "/host/v1/app/inbox/unread-count",
69
+ appInboxMarkRead: "/host/v1/app/inbox/mark-read",
70
+ appInboxDeleteRead: "/host/v1/app/inbox/delete-read",
71
+ appContacts: "/host/v1/app/contacts",
72
+ appContactsUpsert: "/host/v1/app/contacts/upsert",
73
+ appContactsRemove: "/host/v1/app/contacts/remove",
74
+ appContactAutonomousSendAuthorization: "/host/v1/app/contacts/autonomous-send-authorization",
75
+ appAuthorizedContacts: "/host/v1/app/authorized-contacts",
76
+ appModelInbox: "/host/v1/app/model-inbox",
77
+ appAuthorizedInboxMessages: "/host/v1/app/authorized-inbox/messages",
78
+ appAuthorizedInboxRead: "/host/v1/app/authorized-inbox/read",
79
+ appAuthorizedInboxActivate: "/host/v1/app/authorized-inbox/activate",
80
+ appAuthorizedInboxPending: "/host/v1/app/authorized-inbox/pending",
81
+ appAuthorizedInboxClaim: "/host/v1/app/authorized-inbox/claim",
82
+ appAuthorizedInboxFinish: "/host/v1/app/authorized-inbox/finish",
83
+ appAuthorizedInboxBatchReserve: "/host/v1/app/authorized-inbox/batch/reserve",
84
+ appAuthorizedInboxBatchOpen: "/host/v1/app/authorized-inbox/batch/open",
85
+ appAuthorizedInboxBatchAttempt: "/host/v1/app/authorized-inbox/batch/attempt",
86
+ appAuthorizedInboxBatchConfirm: "/host/v1/app/authorized-inbox/batch/confirm",
87
+ appAuthorizedInboxBatchCancel: "/host/v1/app/authorized-inbox/batch/cancel",
88
+ appAuthorizedInboxBatchExpire: "/host/v1/app/authorized-inbox/batch/expire",
89
+ appAuthorizedInboxBatchClosed: "/host/v1/app/authorized-inbox/batch/closed",
90
+ appAuthorizedContactRequests: "/host/v1/app/authorized-contact-requests",
91
+ appAuthorizedContactRequestRead: "/host/v1/app/authorized-contact-requests/read",
92
+ appContactRequests: "/host/v1/app/contact-requests",
93
+ appOutboxMessages: "/host/v1/app/outbox/messages",
94
+ appOutboxRetryGrant: "/host/v1/app/outbox/retry-grants",
95
+ appOutboxRetry: "/host/v1/app/outbox/retry",
96
+ appHumanSendRequests: "/host/v1/app/human-actions/send-requests",
97
+ appHumanSendRequestStatus: "/host/v1/app/human-actions/send-requests/status",
98
+ appAuthorizedSend: "/host/v1/app/authorized-actions/send-text",
99
+ appAuthorizedReply: "/host/v1/app/authorized-actions/reply-text",
100
+ appChangesSnapshot: "/host/v1/app/changes/snapshot",
101
+ appChangesSubscribe: "/host/v1/app/changes/subscribe",
102
+ appWorkbenchSnapshot: "/host/v1/app/workbench/snapshot",
103
+ appWorkbenchActionGrant: "/host/v1/app/workbench/action-grants",
104
+ appWorkbenchCommand: "/host/v1/app/workbench/commands",
105
+ });
106
+
107
+ const WORKBENCH_SNAPSHOT_SCOPES = Object.freeze([
108
+ HELLOLYNK_HOST_SCOPES.appInboxRead,
109
+ HELLOLYNK_HOST_SCOPES.appContactsRead,
110
+ HELLOLYNK_HOST_SCOPES.appContactRequestsRead,
111
+ HELLOLYNK_HOST_SCOPES.appOutboxRead,
112
+ HELLOLYNK_HOST_SCOPES.appChangesRead,
113
+ ]);
114
+ const WORKBENCH_COMMAND_SCOPES = Object.freeze({
115
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.requestSend]: HELLOLYNK_HOST_SCOPES.appHumanSendRequest,
116
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.getSendStatus]: HELLOLYNK_HOST_SCOPES.appHumanSendRequest,
117
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.markRead]: HELLOLYNK_HOST_SCOPES.appInboxManage,
118
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.markAllRead]: HELLOLYNK_HOST_SCOPES.appInboxManage,
119
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.setAutoRead]: HELLOLYNK_HOST_SCOPES.appInboxManage,
120
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteRead]: HELLOLYNK_HOST_SCOPES.appInboxManage,
121
+ // Local history management requires both existing mutation authorities plus
122
+ // the human surface and the exact per-command grant; retry alone is insufficient.
123
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteSent]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
124
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.hideThread]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
125
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.restoreThread]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
126
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.clearThread]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
127
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteThread]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
128
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteArchivedThreads]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
129
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.configureHistory]: [HELLOLYNK_HOST_SCOPES.appInboxManage, HELLOLYNK_HOST_SCOPES.appOutboxRetry],
130
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.requestRetry]: HELLOLYNK_HOST_SCOPES.appOutboxRetry,
131
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.resendDeletedReply]: HELLOLYNK_HOST_SCOPES.appOutboxRetry,
132
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.saveContact]: HELLOLYNK_HOST_SCOPES.appContactsManage,
133
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.renameContact]: HELLOLYNK_HOST_SCOPES.appContactsManage,
134
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteContact]: HELLOLYNK_HOST_SCOPES.appContactsManage,
135
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.listContactInstances]: HELLOLYNK_HOST_SCOPES.appContactsRead,
136
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.saveContactInstance]: HELLOLYNK_HOST_SCOPES.appContactsManage,
137
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.renameContactInstance]: HELLOLYNK_HOST_SCOPES.appContactsManage,
138
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.deleteContactInstance]: HELLOLYNK_HOST_SCOPES.appContactsManage,
139
+ "contact-request.clear-resolved": HELLOLYNK_HOST_SCOPES.appContactsManage,
140
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.recoverContactRequestDecision]: HELLOLYNK_HOST_SCOPES.appContactsManage,
141
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.respondContactRequest]: HELLOLYNK_HOST_SCOPES.appContactsManage,
142
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.cancelContactRequest]: HELLOLYNK_HOST_SCOPES.appContactsManage,
143
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.searchPrincipal]: HELLOLYNK_HOST_SCOPES.appContactsManage,
144
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.sendContactRequest]: HELLOLYNK_HOST_SCOPES.appContactsManage,
145
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.setContactAuthorization]: HELLOLYNK_HOST_SCOPES.appContactsManage,
146
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.endRelationship]: HELLOLYNK_HOST_SCOPES.appContactsManage,
147
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.removeBlock]: HELLOLYNK_HOST_SCOPES.appContactsManage,
148
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.openAccount]: HELLOLYNK_HOST_SCOPES.uiLaunch,
149
+ });
150
+ const WORKBENCH_READ_ONLY_COMMANDS = new Set([
151
+ HELLOLYNK_WORKBENCH_COMMAND_TYPES.getSendStatus,
152
+ HELLOLYNK_WORKBENCH_COMMAND_TYPES.listContactInstances,
153
+ HELLOLYNK_WORKBENCH_COMMAND_TYPES.searchPrincipal,
154
+ ]);
155
+
156
+ const DEFAULT_MAX_BODY_BYTES = 64 * 1024;
157
+ const OUTBOX_RETRY_GRANT_TTL_MS = 90_000;
158
+ const OUTBOX_RETRY_GRANT_CAPACITY = 512;
159
+ const OUTBOX_RETRY_GRANT_PATTERN = /^hretry_[A-Za-z0-9_-]{43}$/;
160
+ const HELLOLYNK_ID_PATTERN = /^hellolynk:(intl|cn):[a-z0-9][a-z0-9._-]{2,179}$/;
161
+ const APPLICATION_ID_PATTERN = /^[A-Za-z0-9][A-Za-z0-9._:-]{2,198}$/;
162
+ const CONTACT_REQUEST_STATES = new Set(["pending", "accepted", "rejected", "cancelled", "blocked", "expired"]);
163
+ const RESERVED_MESSAGE_IDENTITY_KEYS = new Set([
164
+ "adapterid",
165
+ "agentid",
166
+ "capabilityid",
167
+ "delegateid",
168
+ "from",
169
+ "hostid",
170
+ "principalid",
171
+ "sender",
172
+ "senderagentid",
173
+ "senderdelegateid",
174
+ "senderdeviceid",
175
+ "senderhostid",
176
+ "senderid",
177
+ "senderidentity",
178
+ "senderidentitykeyid",
179
+ "senderhellolynkid",
180
+ "senderprincipalid",
181
+ ]);
182
+
183
+ const ROUTES = new Map([
184
+ [`POST ${HELLOLYNK_HOST_PATHS.localPeerExchange}`, {
185
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
186
+ body: "localPeerExchange",
187
+ action: "localPeerExchange",
188
+ }],
189
+ [`POST ${HELLOLYNK_HOST_PATHS.localIdentityProof}`, {
190
+ scope: HELLOLYNK_HOST_SCOPES.appContactsRead,
191
+ body: "localIdentityProof",
192
+ action: "localIdentityProof",
193
+ }],
194
+ [`GET ${HELLOLYNK_HOST_PATHS.status}`, {
195
+ scope: HELLOLYNK_HOST_SCOPES.statusRead,
196
+ body: "empty",
197
+ action: "status",
198
+ }],
199
+ [`POST ${HELLOLYNK_HOST_PATHS.attachmentAttach}`, {
200
+ scope: HELLOLYNK_HOST_SCOPES.attachmentsManage,
201
+ body: "attachment",
202
+ action: "attach",
203
+ }],
204
+ [`POST ${HELLOLYNK_HOST_PATHS.attachmentHeartbeat}`, {
205
+ scope: HELLOLYNK_HOST_SCOPES.attachmentsManage,
206
+ body: "attachment",
207
+ action: "heartbeat",
208
+ }],
209
+ [`POST ${HELLOLYNK_HOST_PATHS.attachmentDetach}`, {
210
+ scope: HELLOLYNK_HOST_SCOPES.attachmentsManage,
211
+ body: "attachment",
212
+ action: "detach",
213
+ }],
214
+ [`POST ${HELLOLYNK_HOST_PATHS.agentMessages}`, {
215
+ scope: HELLOLYNK_HOST_SCOPES.agentMessagesSend,
216
+ body: "agentMessage",
217
+ action: "agentMessage",
218
+ }],
219
+ [`GET ${HELLOLYNK_HOST_PATHS.appInboxMessages}`, {
220
+ scope: HELLOLYNK_HOST_SCOPES.appInboxRead,
221
+ body: "empty",
222
+ action: "appInboxMessages",
223
+ }],
224
+ [`GET ${HELLOLYNK_HOST_PATHS.appInboxUnreadCount}`, {
225
+ scope: HELLOLYNK_HOST_SCOPES.appInboxRead,
226
+ body: "empty",
227
+ action: "appInboxUnreadCount",
228
+ }],
229
+ [`POST ${HELLOLYNK_HOST_PATHS.appInboxMarkRead}`, {
230
+ scope: HELLOLYNK_HOST_SCOPES.appInboxManage,
231
+ body: "appInboxId",
232
+ action: "appInboxMarkRead",
233
+ }],
234
+ [`POST ${HELLOLYNK_HOST_PATHS.appInboxDeleteRead}`, {
235
+ scope: HELLOLYNK_HOST_SCOPES.appInboxManage,
236
+ body: "appInboxIds",
237
+ action: "appInboxDeleteRead",
238
+ }],
239
+ [`GET ${HELLOLYNK_HOST_PATHS.appContacts}`, {
240
+ scope: HELLOLYNK_HOST_SCOPES.appContactsRead,
241
+ body: "empty",
242
+ action: "appContactsList",
243
+ }],
244
+ [`POST ${HELLOLYNK_HOST_PATHS.appContactsUpsert}`, {
245
+ scope: HELLOLYNK_HOST_SCOPES.appContactsManage,
246
+ body: "appContactUpsert",
247
+ action: "appContactsUpsert",
248
+ }],
249
+ [`POST ${HELLOLYNK_HOST_PATHS.appContactsRemove}`, {
250
+ scope: HELLOLYNK_HOST_SCOPES.appContactsManage,
251
+ body: "appContactRemove",
252
+ action: "appContactsRemove",
253
+ }],
254
+ [`POST ${HELLOLYNK_HOST_PATHS.appContactAutonomousSendAuthorization}`, {
255
+ scope: HELLOLYNK_HOST_SCOPES.appContactsManage,
256
+ body: "appContactAutonomousSendAuthorization",
257
+ action: "appContactAutonomousSendAuthorization",
258
+ }],
259
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedContacts}`, {
260
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging, capabilityVersion: 2,
261
+ body: "appNativeConversationQuery", action: "appAuthorizedContactsList",
262
+ }],
263
+ [`GET ${HELLOLYNK_HOST_PATHS.appAuthorizedContacts}`, {
264
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
265
+ capabilityVersion: 2,
266
+ body: "empty",
267
+ action: "appAuthorizedContactsList",
268
+ }],
269
+ [`POST ${HELLOLYNK_HOST_PATHS.appModelInbox}`, {
270
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging, capabilityVersion: 2,
271
+ body: "appModelInbox", action: "appModelInbox",
272
+ }],
273
+ [`GET ${HELLOLYNK_HOST_PATHS.appAuthorizedInboxMessages}`, {
274
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
275
+ capabilityVersion: 2,
276
+ body: "empty",
277
+ action: "appAuthorizedInboxMessages",
278
+ }],
279
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedInboxMessages}`, {
280
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
281
+ capabilityVersion: 2,
282
+ body: "appAuthorizedInboxPreview",
283
+ action: "appAuthorizedInboxMessages",
284
+ }],
285
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedInboxActivate}`, {
286
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
287
+ capabilityVersion: 2,
288
+ body: "empty",
289
+ action: "appAuthorizedInboxActivate",
290
+ }],
291
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedInboxRead}`, {
292
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
293
+ capabilityVersion: 2,
294
+ body: "appAuthorizedInboxRead",
295
+ action: "appAuthorizedInboxRead",
296
+ }],
297
+ ...[ ["GET", "appAuthorizedInboxPending", "empty"],
298
+ ["POST", "appAuthorizedInboxClaim", "appAuthorizedInboxClaim"],
299
+ ["POST", "appAuthorizedInboxFinish", "appAuthorizedInboxFinish"]
300
+ ].map(([method, action, body]) => [`${method} ${HELLOLYNK_HOST_PATHS[action]}`, {
301
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging, capabilityVersion: 2, body, action,
302
+ }]),
303
+ ...["appAuthorizedInboxBatchReserve", "appAuthorizedInboxBatchOpen", "appAuthorizedInboxBatchAttempt", "appAuthorizedInboxBatchConfirm", "appAuthorizedInboxBatchCancel", "appAuthorizedInboxBatchExpire", "appAuthorizedInboxBatchClosed"].map(action => [`POST ${HELLOLYNK_HOST_PATHS[action]}`, {
304
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging, capabilityVersion: 2, body: action, action,
305
+ }]),
306
+ // GET returns the first page for model tools. A receiver scans further pages
307
+ // with its own cursor, or rechecks one message, without any shared state.
308
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedInboxPending}`, {
309
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging, capabilityVersion: 2,
310
+ body: "appAuthorizedInboxPendingQuery", action: "appAuthorizedInboxPendingQuery",
311
+ }],
312
+ [`GET ${HELLOLYNK_HOST_PATHS.appAuthorizedContactRequests}`, {
313
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
314
+ capabilityVersion: 2,
315
+ body: "empty",
316
+ action: "appAuthorizedContactRequestsList",
317
+ }],
318
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedContactRequestRead}`, {
319
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
320
+ capabilityVersion: 2,
321
+ body: "appAuthorizedContactRequestRead",
322
+ action: "appAuthorizedContactRequestRead",
323
+ }],
324
+ [`GET ${HELLOLYNK_HOST_PATHS.appContactRequests}`, {
325
+ scope: HELLOLYNK_HOST_SCOPES.appContactRequestsRead,
326
+ body: "empty",
327
+ action: "appContactRequestsList",
328
+ }],
329
+ [`GET ${HELLOLYNK_HOST_PATHS.appOutboxMessages}`, {
330
+ scope: HELLOLYNK_HOST_SCOPES.appOutboxRead,
331
+ body: "empty",
332
+ action: "appOutboxMessages",
333
+ }],
334
+ [`POST ${HELLOLYNK_HOST_PATHS.appOutboxRetryGrant}`, {
335
+ scope: HELLOLYNK_HOST_SCOPES.appOutboxRetry,
336
+ body: "appOutboxRetryGrant",
337
+ action: "appOutboxRetryGrant",
338
+ }],
339
+ [`POST ${HELLOLYNK_HOST_PATHS.appOutboxRetry}`, {
340
+ scope: HELLOLYNK_HOST_SCOPES.appOutboxRetry,
341
+ body: "appOutboxRetry",
342
+ action: "appOutboxRetry",
343
+ }],
344
+ [`POST ${HELLOLYNK_HOST_PATHS.appHumanSendRequests}`, {
345
+ scope: HELLOLYNK_HOST_SCOPES.appHumanSendRequest,
346
+ capabilityVersion: 2,
347
+ body: "appHumanSendRequest",
348
+ action: "appHumanSendRequest",
349
+ }],
350
+ [`POST ${HELLOLYNK_HOST_PATHS.appHumanSendRequestStatus}`, {
351
+ scope: HELLOLYNK_HOST_SCOPES.appHumanSendRequest,
352
+ capabilityVersion: 2,
353
+ body: "appHumanSendRequestStatus",
354
+ action: "appHumanSendRequestStatus",
355
+ }],
356
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedSend}`, {
357
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
358
+ capabilityVersion: 2,
359
+ body: "appAuthorizedSend",
360
+ action: "appAuthorizedSend",
361
+ }],
362
+ [`POST ${HELLOLYNK_HOST_PATHS.appAuthorizedReply}`, {
363
+ scope: HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging,
364
+ capabilityVersion: 2,
365
+ body: "appAuthorizedReply",
366
+ action: "appAuthorizedReply",
367
+ }],
368
+ [`GET ${HELLOLYNK_HOST_PATHS.appChangesSnapshot}`, {
369
+ scope: HELLOLYNK_HOST_SCOPES.appChangesRead,
370
+ body: "empty",
371
+ action: "appChangesSnapshot",
372
+ }],
373
+ [`POST ${HELLOLYNK_HOST_PATHS.appChangesSubscribe}`, {
374
+ scope: HELLOLYNK_HOST_SCOPES.appChangesRead,
375
+ body: "appChangesSubscribe",
376
+ action: "appChangesSubscribe",
377
+ }],
378
+ [`POST ${HELLOLYNK_HOST_PATHS.appWorkbenchSnapshot}`, {
379
+ scope: HELLOLYNK_HOST_SCOPES.appInboxRead,
380
+ scopes: WORKBENCH_SNAPSHOT_SCOPES,
381
+ body: "appWorkbenchRead",
382
+ action: "appWorkbenchSnapshot",
383
+ }],
384
+ [`POST ${HELLOLYNK_HOST_PATHS.appWorkbenchActionGrant}`, {
385
+ scope: HELLOLYNK_HOST_SCOPES.appInboxRead,
386
+ scopes: WORKBENCH_SNAPSHOT_SCOPES,
387
+ capabilityVersion: 2,
388
+ body: "appWorkbenchActionGrant",
389
+ action: "appWorkbenchActionGrant",
390
+ }],
391
+ [`POST ${HELLOLYNK_HOST_PATHS.appWorkbenchCommand}`, {
392
+ scope: HELLOLYNK_HOST_SCOPES.appInboxRead,
393
+ scopes: WORKBENCH_SNAPSHOT_SCOPES,
394
+ capabilityVersion: 2,
395
+ body: "appWorkbenchCommand",
396
+ action: "appWorkbenchCommand",
397
+ }],
398
+ ]);
399
+
400
+ export function createHelloLynkHostCore(options = {}) {
401
+ if (typeof options.capabilityResolver !== "function") {
402
+ throw codeError("host_capability_resolver_required");
403
+ }
404
+ const capabilityResolver = options.capabilityResolver;
405
+ const legacyCompatEnabled = options.legacyCompatEnabled === true;
406
+ const handlers = normalizeHandlers(options.actionHandlers);
407
+ const clock = normalizeClock(options.clock);
408
+ const hostGeneration = assertHostGeneration(options.hostGeneration || randomGeneration());
409
+ const maxBodyBytes = normalizeLimit(options.maxBodyBytes, DEFAULT_MAX_BODY_BYTES, 1_024, 1_048_576);
410
+ const maxSkewMs = normalizeLimit(
411
+ options.maxSkewMs,
412
+ HELLOLYNK_HOST_REQUEST_MAX_SKEW_MS,
413
+ 1_000,
414
+ 300_000,
415
+ );
416
+ const minimumNonceTtlMs = (2 * maxSkewMs) + 1;
417
+ const nonceTtlMs = normalizeLimit(
418
+ options.nonceTtlMs,
419
+ Math.max(90_000, minimumNonceTtlMs),
420
+ 1_000,
421
+ 1_800_001,
422
+ );
423
+ if (nonceTtlMs < minimumNonceTtlMs) throw codeError("host_nonce_ttl_too_short");
424
+ const replayGuard = createNonceReplayGuard({
425
+ clock,
426
+ ttlMs: nonceTtlMs,
427
+ maxEntries: options.maxNonceEntries,
428
+ maxEntriesPerCapability: options.maxNoncesPerCapability,
429
+ });
430
+ const capabilityBindings = new Map();
431
+ const attachments = new Map();
432
+ const outboxRetryGrants = new Map();
433
+ const workbenchActionGrants = createHelloLynkWorkbenchActionGrantRegistry({
434
+ clock,
435
+ ...(options.workbenchActionGrantTtlMs == null
436
+ ? {}
437
+ : { ttlMs: options.workbenchActionGrantTtlMs }),
438
+ ...(options.workbenchActionGrantCapacity == null
439
+ ? {}
440
+ : { capacity: options.workbenchActionGrantCapacity }),
441
+ });
442
+ let lifecycleState = "running";
443
+ let idleShutdownAt = null;
444
+ let hasEverAttached = false;
445
+ let shutdownPromise = null;
446
+
447
+ async function dispatch(request = {}) {
448
+ let mayReadModelNotices = false;
449
+ try {
450
+ const method = assertMethod(request.method);
451
+ const pathname = assertPathname(request.pathname);
452
+ const rawBody = asRawBody(request.rawBody);
453
+ if (rawBody.length > maxBodyBytes) throw codeError("host_request_too_large");
454
+
455
+ if (method === "GET" && pathname === HELLOLYNK_HOST_PATHS.health) {
456
+ if (rawBody.length !== 0) throw codeError("host_request_body_forbidden");
457
+ return response(200, publicHealth());
458
+ }
459
+ if (isHumanOrRootPath(pathname)) throw codeError("host_adapter_route_forbidden");
460
+
461
+ const route = ROUTES.get(`${method} ${pathname}`);
462
+ if (!route) throw codeError("host_route_not_found");
463
+ if (!legacyCompatEnabled && isLegacySendRoute(pathname)) throw codeError("host_route_not_found");
464
+ if (lifecycleState !== "running") throw codeError("host_not_running");
465
+
466
+ const authenticated = await authenticateRequest({ method, pathname, rawBody, headers: request.headers });
467
+ if (route.capabilityVersion != null
468
+ && authenticated.capability.version !== route.capabilityVersion) {
469
+ throw codeError("host_request_unauthorized");
470
+ }
471
+ if (route.action.startsWith("app")
472
+ && !authenticated.capability.scopes.includes(HELLOLYNK_HOST_SCOPES.humanUiSurface)) {
473
+ throw codeError("host_capability_human_surface_required");
474
+ }
475
+ const requiredScopes = route.scopes || [route.scope];
476
+ if (requiredScopes.some((scope) => !authenticated.capability.scopes.includes(scope))) {
477
+ throw codeError("host_capability_scope_denied");
478
+ }
479
+ mayReadModelNotices = (route.action === "status" || route.action.startsWith("appAuthorized")
480
+ || route.action === "appInboxUnreadCount")
481
+ && authenticated.capability.scopes.includes(HELLOLYNK_HOST_SCOPES.appAuthorizedMessaging);
482
+ const body = parseAndValidateBody(route.body, rawBody);
483
+ if (route.action === "appWorkbenchActionGrant" || route.action === "appWorkbenchCommand") {
484
+ const commandScope = WORKBENCH_COMMAND_SCOPES[body.request.command];
485
+ if (!commandScope || (Array.isArray(commandScope) ? commandScope : [commandScope])
486
+ .some((scope) => !authenticated.capability.scopes.includes(scope))) {
487
+ throw codeError("host_capability_scope_denied");
488
+ }
489
+ if ((body.request.command === HELLOLYNK_WORKBENCH_COMMAND_TYPES.searchPrincipal
490
+ || body.request.command === HELLOLYNK_WORKBENCH_COMMAND_TYPES.sendContactRequest)
491
+ && !authenticated.capability.scopes.includes(HELLOLYNK_HOST_SCOPES.humanUiSurface)) {
492
+ throw codeError("host_capability_human_surface_required");
493
+ }
494
+ if (route.action === "appWorkbenchActionGrant"
495
+ && body.request.command === HELLOLYNK_WORKBENCH_COMMAND_TYPES.getSendStatus) {
496
+ throw codeError("host_app_workbench_command_invalid");
497
+ }
498
+ }
499
+ const prepared = prepareAction(route.action, authenticated.capability, body);
500
+
501
+ // Consuming the nonce is deliberately last: unknown/inactive capabilities,
502
+ // bad HMAC, stale timestamps, wrong generation/scope/delegate, and invalid
503
+ // bodies cannot pollute the bounded replay cache or trigger an action.
504
+ if (!replayGuard.consume(
505
+ authenticated.capability.capabilityId,
506
+ authenticated.nonce,
507
+ authenticated.timestampMs,
508
+ )) {
509
+ if (route.action === "appWorkbenchCommand") {
510
+ if (prepared?.workbenchActionGrantConsumed) {
511
+ rejectConsumedHelloLynkWorkbenchActionGrant(
512
+ workbenchActionGrants,
513
+ body.actionGrant,
514
+ );
515
+ }
516
+ if (prepared?.workbenchActionGrantConsumed
517
+ || prepared?.workbenchActionGrantFailureOutcome) {
518
+ return response(200, workbenchActionGrantFailure(
519
+ body.request,
520
+ prepared.workbenchActionGrantFailureOutcome || "known-failed",
521
+ ));
522
+ }
523
+ }
524
+ throw codeError("host_request_replayed");
525
+ }
526
+ return await withModelNotices(await executeAction(route.action, authenticated.capability, body, prepared));
527
+ } catch (error) {
528
+ return withModelNotices(errorResponse(error));
529
+ }
530
+
531
+ async function withModelNotices(result) {
532
+ if (!mayReadModelNotices || result.status === 401
533
+ || typeof options.readModelNotices !== "function") return result;
534
+ try {
535
+ const state = await options.readModelNotices();
536
+ return { ...result, body: { ...result.body, modelNotices: {
537
+ ...(Number.isSafeInteger(state?.pendingContactRequests) && state.pendingContactRequests >= 0
538
+ ? { pendingContactRequests: state.pendingContactRequests } : {}),
539
+ localStorageFull: state?.localStorageFull === true,
540
+ } } };
541
+ } catch { return result; } // Notices must never replace the actual action outcome.
542
+ }
543
+ }
544
+
545
+ async function authenticateRequest({ method, pathname, rawBody, headers }) {
546
+ const capabilityId = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.capabilityId);
547
+ const adapterId = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.adapterId);
548
+ const generation = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.hostGeneration);
549
+ const timestampValue = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.timestampMs);
550
+ const nonce = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.nonce);
551
+ const proof = requiredHeader(headers, HELLOLYNK_HOST_AUTH_HEADERS.proof);
552
+
553
+ const capability = await resolvePinnedCapability(capabilityId);
554
+ if (capability.adapterId !== adapterId || generation !== hostGeneration) {
555
+ throw codeError("host_request_unauthorized");
556
+ }
557
+ const timestampMs = parseTimestampHeader(timestampValue);
558
+ try {
559
+ assertRequestFresh(timestampMs, clock.now(), maxSkewMs);
560
+ } catch {
561
+ throw codeError("host_request_unauthorized");
562
+ }
563
+ if (!hostRequestAuthMatches({
564
+ secretBuffer: capability.secretBuffer,
565
+ capabilityId,
566
+ adapterId,
567
+ method,
568
+ pathname,
569
+ hostGeneration,
570
+ timestampMs,
571
+ nonce,
572
+ rawBody,
573
+ proof,
574
+ })) {
575
+ throw codeError("host_request_unauthorized");
576
+ }
577
+ return { capability, timestampMs, nonce };
578
+ }
579
+
580
+ async function resolvePinnedCapability(capabilityId) {
581
+ if (capabilityBindings.get(capabilityId)?.state === "invalidated") {
582
+ throw codeError("host_request_unauthorized");
583
+ }
584
+ let capability;
585
+ try {
586
+ capability = assertCapabilityRecord(await capabilityResolver(capabilityId));
587
+ if (capability.capabilityId !== capabilityId) throw codeError("host_capability_id_mismatch");
588
+ } catch {
589
+ if (capabilityBindings.has(capabilityId)) {
590
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
591
+ hostGeneration,
592
+ capabilityId,
593
+ });
594
+ capabilityBindings.set(capabilityId, Object.freeze({ state: "invalidated" }));
595
+ }
596
+ throw codeError("host_request_unauthorized");
597
+ }
598
+ const tuple = immutableCapabilityTuple(capability);
599
+ const current = capabilityBindings.get(capabilityId);
600
+ if (current?.state === "invalidated") throw codeError("host_request_unauthorized");
601
+ if (!current) {
602
+ capabilityBindings.set(capabilityId, Object.freeze({ state: "active", tuple }));
603
+ } else if (!immutableCapabilityTupleMatches(current.tuple, tuple)) {
604
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
605
+ hostGeneration,
606
+ capabilityId,
607
+ });
608
+ capabilityBindings.set(capabilityId, Object.freeze({ state: "invalidated" }));
609
+ throw codeError("host_request_unauthorized");
610
+ }
611
+ return capability;
612
+ }
613
+
614
+ function prepareAction(action, capability, body) {
615
+ const now = clock.now();
616
+ if (["localIdentityProof", "localPeerExchange"].includes(action) && (body.target.delegateId !== capability.delegateId
617
+ || body.target.hostInstallationId !== capability.hostInstallationId
618
+ || body.owner.principalId !== capability.principalId)) throw codeError("host_request_unauthorized");
619
+ if (action === "heartbeat" || action === "detach") {
620
+ return exactActiveAttachment(capability, body.adapterInstanceId, now);
621
+ }
622
+ if (action === "appHumanSendRequest" || action === "appHumanSendRequestStatus") {
623
+ return { attachment: exactActiveAttachment(capability, body.adapterInstanceId, now).attachment };
624
+ }
625
+ if (action === "appOutboxRetryGrant") {
626
+ return { attachment: exactActiveAttachment(capability, body.adapterInstanceId, now).attachment };
627
+ }
628
+ if (action === "appWorkbenchActionGrant") {
629
+ return { attachment: exactActiveAttachment(capability, body.adapterInstanceId, now).attachment };
630
+ }
631
+ if (action === "appWorkbenchCommand") {
632
+ const grantRequired = !WORKBENCH_READ_ONLY_COMMANDS.has(body.request.command);
633
+ let workbenchActionGrantConsumed = false;
634
+ if (grantRequired || body.actionGrant != null) {
635
+ try {
636
+ workbenchActionGrants.consume(workbenchActionGrantBinding(
637
+ capability,
638
+ body.adapterInstanceId,
639
+ body.request,
640
+ body.actionGrant,
641
+ ));
642
+ workbenchActionGrantConsumed = true;
643
+ } catch (error) {
644
+ return {
645
+ workbenchActionGrantFailureOutcome:
646
+ error?.outcome === "unknown" ? "unknown" : "known-failed",
647
+ };
648
+ }
649
+ }
650
+ let attachment;
651
+ try {
652
+ attachment = exactActiveAttachment(capability, body.adapterInstanceId, now).attachment;
653
+ } catch (error) {
654
+ if (grantRequired || body.actionGrant != null) {
655
+ if (workbenchActionGrantConsumed) {
656
+ rejectConsumedHelloLynkWorkbenchActionGrant(
657
+ workbenchActionGrants,
658
+ body.actionGrant,
659
+ );
660
+ }
661
+ return { workbenchActionGrantFailureOutcome: "known-failed" };
662
+ }
663
+ throw error;
664
+ }
665
+ return {
666
+ attachment,
667
+ workbenchActionGrantConsumed,
668
+ workbenchRetryAuthorized:
669
+ [HELLOLYNK_WORKBENCH_COMMAND_TYPES.requestRetry, HELLOLYNK_WORKBENCH_COMMAND_TYPES.resendDeletedReply].includes(body.request.command),
670
+ };
671
+ }
672
+ if (action === "appOutboxRetry") {
673
+ const { attachment } = exactActiveAttachment(capability, body.adapterInstanceId, now);
674
+ pruneOutboxRetryGrants(now);
675
+ const digest = crypto.createHash("sha256").update(body.retryGrant, "utf8").digest("hex");
676
+ const grant = outboxRetryGrants.get(digest);
677
+ if (!grant || grant.expiresAt <= now
678
+ || grant.capabilityId !== capability.capabilityId
679
+ || grant.adapterId !== capability.adapterId
680
+ || grant.delegateId !== capability.delegateId
681
+ || grant.adapterInstanceId !== body.adapterInstanceId
682
+ || grant.authorityEpoch !== attachment.authorityEpoch
683
+ || grant.clientMessageId !== body.clientMessageId) {
684
+ throw codeError("host_app_outbox_retry_grant_invalid");
685
+ }
686
+ return { retryGrantDigest: digest, grant, attachment };
687
+ }
688
+ return null;
689
+ }
690
+
691
+ async function executeAction(action, capability, body, prepared) {
692
+ const now = clock.now();
693
+ if (action === "localPeerExchange") {
694
+ const result = await invokeHandler(handlers.localPeerExchange,
695
+ publicActionContext(capability, { hostGeneration, request: body }));
696
+ await resolvePinnedCapability(capability.capabilityId);
697
+ return response(200, signLocalPeerResult({ request: body, hostGeneration,
698
+ capabilityId: capability.capabilityId, result, secretBuffer: capability.secretBuffer }));
699
+ }
700
+ if (action === "localIdentityProof") {
701
+ const identity = await invokeHandler(handlers.localIdentityProof,
702
+ publicActionContext(capability, { hostGeneration, request: body }));
703
+ // Re-resolve after the callback: disconnect/revocation during proof
704
+ // calculation must not leave a successfully signed stale response.
705
+ await resolvePinnedCapability(capability.capabilityId);
706
+ return response(200, signLocalIdentityProof({ request: body, hostGeneration,
707
+ capabilityId: capability.capabilityId, identity, secretBuffer: capability.secretBuffer }));
708
+ }
709
+ if (action === "status") {
710
+ await invokeHandler(handlers.status, publicCapability(capability));
711
+ return response(200, {
712
+ ok: true,
713
+ contractId: HELLOLYNK_HOST_CONTRACT,
714
+ hostGeneration,
715
+ status: lifecycleState,
716
+ attachmentCount: activeAttachmentCount(now),
717
+ idleShutdownAt,
718
+ adapter: publicCapability(capability),
719
+ });
720
+ }
721
+ if (action === "attach") {
722
+ const key = attachmentKey(capability.capabilityId, body.adapterInstanceId);
723
+ const existing = attachments.get(key);
724
+ if (existing && existing.expiresAt <= now) {
725
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
726
+ hostGeneration,
727
+ capabilityId: capability.capabilityId,
728
+ adapterInstanceId: body.adapterInstanceId,
729
+ });
730
+ }
731
+ const attachedAt = existing?.attachedAt ?? now;
732
+ const attachment = Object.freeze({
733
+ authorityEpoch: existing && existing.expiresAt > now ? existing.authorityEpoch : Symbol("attachment"),
734
+ adapterInstanceId: body.adapterInstanceId,
735
+ capabilityId: capability.capabilityId,
736
+ adapterId: capability.adapterId,
737
+ delegateId: capability.delegateId,
738
+ capabilityVersion: capability.version,
739
+ attachedAt,
740
+ lastHeartbeatAt: now,
741
+ expiresAt: now + HELLOLYNK_HOST_ATTACHMENT_LEASE_MS,
742
+ });
743
+ attachments.set(key, attachment);
744
+ hasEverAttached = true;
745
+ idleShutdownAt = null;
746
+ await invokeHandler(existing ? handlers.heartbeat : handlers.attach,
747
+ publicActionContext(capability, { hostGeneration, adapterInstanceId: body.adapterInstanceId }),
748
+ );
749
+ return response(200, attachmentResponse(attachment, Boolean(existing)));
750
+ }
751
+ if (action === "heartbeat") {
752
+ const attachment = Object.freeze({
753
+ ...prepared.attachment,
754
+ lastHeartbeatAt: now,
755
+ expiresAt: now + HELLOLYNK_HOST_ATTACHMENT_LEASE_MS,
756
+ });
757
+ attachments.set(prepared.key, attachment);
758
+ idleShutdownAt = null;
759
+ await invokeHandler(handlers.heartbeat,
760
+ publicActionContext(capability, { hostGeneration, adapterInstanceId: body.adapterInstanceId }),
761
+ );
762
+ return response(200, attachmentResponse(attachment, false));
763
+ }
764
+ if (action === "detach") {
765
+ attachments.delete(prepared.key);
766
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
767
+ hostGeneration,
768
+ capabilityId: capability.capabilityId,
769
+ adapterInstanceId: body.adapterInstanceId,
770
+ });
771
+ scheduleIdleShutdownIfEmpty(now);
772
+ await invokeHandler(handlers.detach,
773
+ publicActionContext(capability, {
774
+ hostGeneration,
775
+ capabilityId: capability.capabilityId,
776
+ adapterInstanceId: body.adapterInstanceId,
777
+ }),
778
+ );
779
+ return response(200, {
780
+ ok: true,
781
+ detached: true,
782
+ adapterInstanceId: body.adapterInstanceId,
783
+ attachmentCount: activeAttachmentCount(now),
784
+ idleShutdownAt,
785
+ });
786
+ }
787
+ if (action === "agentMessage") {
788
+ await invokeHandler(
789
+ handlers.agentMessage,
790
+ publicActionContext(capability, { hostGeneration, message: body }),
791
+ );
792
+ return response(200, { ok: true, accepted: true });
793
+ }
794
+ if (action === "appOutboxRetryGrant") {
795
+ const issued = issueOutboxRetryGrant(
796
+ capability,
797
+ prepared.attachment,
798
+ body.clientMessageId,
799
+ now,
800
+ );
801
+ return response(200, { ok: true, retryGrant: issued.retryGrant, expiresAt: issued.expiresAt });
802
+ }
803
+ if (action === "appWorkbenchActionGrant") {
804
+ await invokeApplicationHandler(
805
+ handlers.appWorkbenchActionGrant,
806
+ publicActionContext(capability, {
807
+ hostGeneration,
808
+ capabilityId: capability.capabilityId,
809
+ adapterInstanceId: prepared.attachment.adapterInstanceId,
810
+ request: body.request,
811
+ }),
812
+ );
813
+ const currentCapability = await resolvePinnedCapability(capability.capabilityId);
814
+ if (lifecycleState !== "running") throw codeError("host_not_running");
815
+ const currentAttachment = exactActiveAttachment(
816
+ currentCapability, prepared.attachment.adapterInstanceId, clock.now(),
817
+ ).attachment;
818
+ if (currentAttachment.authorityEpoch !== prepared.attachment.authorityEpoch) {
819
+ throw codeError("host_request_unauthorized");
820
+ }
821
+ // The handler and capability lookup may outlive this attachment. Keep the
822
+ // final lifetime check and token issuance synchronous so reattachment
823
+ // cannot give an older request fresh authority.
824
+ const issued = workbenchActionGrants.issue(workbenchActionGrantBinding(
825
+ currentCapability,
826
+ prepared.attachment.adapterInstanceId,
827
+ body.request,
828
+ ));
829
+ return response(200, {
830
+ ok: true,
831
+ actionGrant: issued.actionGrant,
832
+ expiresAtMs: issued.expiresAtMs,
833
+ });
834
+ }
835
+ if (action === "appWorkbenchCommand" && prepared?.workbenchActionGrantFailureOutcome) {
836
+ return response(200, workbenchActionGrantFailure(
837
+ body.request,
838
+ prepared.workbenchActionGrantFailureOutcome,
839
+ ));
840
+ }
841
+ if (action.startsWith("app")) {
842
+ const applicationRequest = (action === "appHumanSendRequest"
843
+ || action === "appHumanSendRequestStatus"
844
+ || action === "appWorkbenchCommand") ? body.request : body;
845
+ if (action === "appOutboxRetry") {
846
+ outboxRetryGrants.delete(prepared.retryGrantDigest);
847
+ }
848
+ const result = await invokeApplicationHandler(
849
+ handlers[action],
850
+ publicActionContext(capability, {
851
+ hostGeneration,
852
+ ...((action === "appHumanSendRequest" || action === "appHumanSendRequestStatus"
853
+ || action === "appWorkbenchCommand")
854
+ ? { capabilityId: capability.capabilityId }
855
+ : {}),
856
+ ...(prepared?.attachment ? { adapterInstanceId: prepared.attachment.adapterInstanceId } : {}),
857
+ ...(prepared?.workbenchRetryAuthorized ? { workbenchRetryAuthorized: true } : {}),
858
+ request: applicationRequest,
859
+ ...((action === "appHumanSendRequest" || action === "appOutboxRetry"
860
+ || (action === "appWorkbenchCommand"
861
+ && [HELLOLYNK_WORKBENCH_COMMAND_TYPES.requestSend, HELLOLYNK_WORKBENCH_COMMAND_TYPES.requestRetry]
862
+ .includes(applicationRequest.command))) ? {
863
+ nativeAuthority: createNativeAttachmentAuthority(capability, prepared.attachment),
864
+ } : {}),
865
+ ...((action === "appModelInbox" || action === "appAuthorizedContactsList" || action === "appAuthorizedSend" || action.startsWith("appAuthorizedInbox")
866
+ || action === "appAuthorizedContactRequestsList"
867
+ || action === "appAuthorizedContactRequestRead") ? {
868
+ // A queued/durable admission must not rely on authentication taken
869
+ // before it waited. Reuse pinned identity and current revocation.
870
+ revalidateCapability: () => resolvePinnedCapability(capability.capabilityId),
871
+ } : {}),
872
+ }),
873
+ );
874
+ return response(
875
+ action === "appHumanSendRequest" ? 202 : 200,
876
+ applicationResponse(action, result, applicationRequest),
877
+ );
878
+ }
879
+ throw codeError("host_action_invalid");
880
+ }
881
+
882
+ function issueOutboxRetryGrant(capability, attachment, clientMessageId, now) {
883
+ pruneOutboxRetryGrants(now);
884
+ if (outboxRetryGrants.size >= OUTBOX_RETRY_GRANT_CAPACITY) {
885
+ throw codeError("host_app_outbox_retry_grant_capacity_exceeded");
886
+ }
887
+ const retryGrant = `hretry_${crypto.randomBytes(32).toString("base64url")}`;
888
+ if (!OUTBOX_RETRY_GRANT_PATTERN.test(retryGrant)) {
889
+ throw codeError("host_app_outbox_retry_grant_generation_failed");
890
+ }
891
+ const digest = crypto.createHash("sha256").update(retryGrant, "utf8").digest("hex");
892
+ const expiresAt = now + OUTBOX_RETRY_GRANT_TTL_MS;
893
+ outboxRetryGrants.set(digest, Object.freeze({
894
+ capabilityId: capability.capabilityId,
895
+ adapterId: capability.adapterId,
896
+ delegateId: capability.delegateId,
897
+ adapterInstanceId: attachment.adapterInstanceId,
898
+ authorityEpoch: attachment.authorityEpoch,
899
+ clientMessageId,
900
+ expiresAt,
901
+ }));
902
+ return { retryGrant, digest, expiresAt };
903
+ }
904
+
905
+ function workbenchActionGrantBinding(capability, adapterInstanceId, command, actionGrant) {
906
+ const targetDigest = crypto.createHash("sha256")
907
+ .update(JSON.stringify({ command: command.command, payload: command.payload }), "utf8")
908
+ .digest("hex");
909
+ return {
910
+ hostGeneration,
911
+ principalId: capability.principalId,
912
+ capabilityId: capability.capabilityId,
913
+ adapterId: capability.adapterId,
914
+ delegateId: capability.delegateId,
915
+ adapterInstanceId,
916
+ tool: command.command,
917
+ target: `workbench:${targetDigest}`,
918
+ // The registry canonicalizes this complete, already validated DTO. Thus
919
+ // requestId, contract/schema, idempotency, revision, tool and payload are
920
+ // all cryptographically bound without retaining the raw command.
921
+ payload: command,
922
+ expectedRevision: command.expectedRevision,
923
+ idempotencyKey: command.idempotencyKey,
924
+ ...(actionGrant === undefined ? {} : { actionGrant }),
925
+ };
926
+ }
927
+
928
+ async function runMaintenance() {
929
+ if (lifecycleState !== "running") return publicHealth();
930
+ const now = clock.now();
931
+ let lastExpiredAt = null;
932
+ for (const [key, attachment] of attachments) {
933
+ if (attachment.expiresAt <= now) {
934
+ attachments.delete(key);
935
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
936
+ hostGeneration,
937
+ capabilityId: attachment.capabilityId,
938
+ adapterInstanceId: attachment.adapterInstanceId,
939
+ });
940
+ lastExpiredAt = Math.max(lastExpiredAt ?? 0, attachment.expiresAt);
941
+ }
942
+ }
943
+ if (attachments.size === 0 && hasEverAttached && idleShutdownAt == null) {
944
+ idleShutdownAt = (lastExpiredAt ?? now) + HELLOLYNK_HOST_IDLE_SHUTDOWN_GRACE_MS;
945
+ }
946
+ if (idleShutdownAt != null && now >= idleShutdownAt) await shutDownForIdle();
947
+ replayGuard.prune(now);
948
+ pruneOutboxRetryGrants(now);
949
+ try {
950
+ workbenchActionGrants.prune();
951
+ } catch (error) {
952
+ // The registry has already cleared every grant before reporting a clock
953
+ // rollback. Maintenance must remain stable because its process timer is
954
+ // fire-and-forget; issuance/consumption still surface their fail-closed
955
+ // errors to the authenticated request that triggered them.
956
+ if (error?.code !== "host_workbench_action_grant_clock_invalid") throw error;
957
+ }
958
+ return publicHealth();
959
+ }
960
+
961
+ function pruneOutboxRetryGrants(now = clock.now()) {
962
+ for (const [digest, grant] of outboxRetryGrants) {
963
+ if (grant.expiresAt <= now) outboxRetryGrants.delete(digest);
964
+ }
965
+ }
966
+
967
+ async function shutDownForIdle() {
968
+ if (shutdownPromise) return shutdownPromise;
969
+ lifecycleState = "stopping";
970
+ shutdownPromise = Promise.resolve()
971
+ .then(() => invokeHandler(handlers.idleShutdown, {
972
+ contractId: HELLOLYNK_HOST_CONTRACT,
973
+ hostGeneration,
974
+ reason: "no_adapter_attachments",
975
+ }))
976
+ .finally(() => {
977
+ lifecycleState = "stopped";
978
+ });
979
+ return shutdownPromise;
980
+ }
981
+
982
+ function activeAttachmentCount(now = clock.now()) {
983
+ let count = 0;
984
+ for (const attachment of attachments.values()) {
985
+ if (attachment.expiresAt > now) count += 1;
986
+ }
987
+ return count;
988
+ }
989
+
990
+ function connectedClients(now = clock.now()) {
991
+ const types = new Set();
992
+ for (const attachment of attachments.values()) {
993
+ if (attachment.expiresAt > now) types.add(workbenchClientType(attachment.adapterId));
994
+ }
995
+ return Object.freeze([...types]
996
+ .sort((left, right) => left.localeCompare(right, "en"))
997
+ .map((clientType) => Object.freeze({
998
+ clientId: clientType,
999
+ clientType,
1000
+ state: "connected",
1001
+ })));
1002
+ }
1003
+
1004
+ function scheduleIdleShutdownIfEmpty(now) {
1005
+ if (attachments.size === 0 && hasEverAttached && idleShutdownAt == null) {
1006
+ idleShutdownAt = now + HELLOLYNK_HOST_IDLE_SHUTDOWN_GRACE_MS;
1007
+ }
1008
+ }
1009
+
1010
+ function exactActiveAttachment(capability, adapterInstanceId, now) {
1011
+ const key = attachmentKey(capability.capabilityId, adapterInstanceId);
1012
+ const attachment = attachments.get(key);
1013
+ if (!attachment || !attachmentMatchesCapability(attachment, capability) || attachment.expiresAt <= now) {
1014
+ if (attachment) {
1015
+ attachments.delete(key);
1016
+ revokeHelloLynkWorkbenchActionGrants(workbenchActionGrants, {
1017
+ hostGeneration,
1018
+ capabilityId: capability.capabilityId,
1019
+ adapterInstanceId,
1020
+ });
1021
+ }
1022
+ throw codeError("host_attachment_not_found");
1023
+ }
1024
+ return { key, attachment };
1025
+ }
1026
+
1027
+ function publicHealth() {
1028
+ return {
1029
+ ok: lifecycleState === "running",
1030
+ contractId: HELLOLYNK_HOST_CONTRACT,
1031
+ hostGeneration,
1032
+ status: lifecycleState,
1033
+ };
1034
+ }
1035
+
1036
+ function createNativeAttachmentAuthority(capability, original) {
1037
+ function assertCurrent() {
1038
+ if (lifecycleState !== "running" || capabilityBindings.get(capability.capabilityId)?.state !== "active") {
1039
+ throw codeError("host_request_unauthorized");
1040
+ }
1041
+ const current = attachments.get(attachmentKey(capability.capabilityId, original.adapterInstanceId));
1042
+ if (!current || !attachmentMatchesCapability(current, capability)
1043
+ || current.expiresAt <= clock.now() || current.authorityEpoch !== original.authorityEpoch) {
1044
+ throw codeError("host_request_unauthorized");
1045
+ }
1046
+ }
1047
+ return Object.freeze({
1048
+ assertCurrent,
1049
+ async revalidate() {
1050
+ await resolvePinnedCapability(capability.capabilityId);
1051
+ assertCurrent();
1052
+ },
1053
+ });
1054
+ }
1055
+
1056
+ // Internal native-action guard; never serialized in an application response.
1057
+ // A detach/re-attach of the same adapter name is a new authority lifetime.
1058
+ async function captureNativeAuthority(context) {
1059
+ async function currentAttachment() {
1060
+ if (context.hostGeneration !== hostGeneration || lifecycleState !== "running") {
1061
+ throw codeError("host_request_unauthorized");
1062
+ }
1063
+ const capability = await resolvePinnedCapability(context.capabilityId);
1064
+ for (const field of ["principalId", "delegateId", "hostInstallationId", "adapterId"]) {
1065
+ if (capability[field] !== context[field]) throw codeError("host_request_unauthorized");
1066
+ }
1067
+ return exactActiveAttachment(capability, context.adapterInstanceId, clock.now()).attachment;
1068
+ }
1069
+ const original = await currentAttachment();
1070
+ return async () => {
1071
+ const current = await currentAttachment();
1072
+ if (current.authorityEpoch !== original.authorityEpoch) throw codeError("host_request_unauthorized");
1073
+ };
1074
+ }
1075
+
1076
+ return Object.freeze({
1077
+ dispatch,
1078
+ runMaintenance,
1079
+ publicHealth,
1080
+ captureNativeAuthority,
1081
+ get hostGeneration() {
1082
+ return hostGeneration;
1083
+ },
1084
+ get attachmentCount() {
1085
+ return activeAttachmentCount();
1086
+ },
1087
+ connectedClients,
1088
+ get idleShutdownAt() {
1089
+ return idleShutdownAt;
1090
+ },
1091
+ });
1092
+ }
1093
+
1094
+ function parseAndValidateBody(kind, rawBody) {
1095
+ if (kind === "empty") {
1096
+ if (rawBody.length !== 0) throw codeError("host_request_body_forbidden");
1097
+ return null;
1098
+ }
1099
+ let body;
1100
+ try {
1101
+ body = JSON.parse(rawBody.toString("utf8"));
1102
+ } catch {
1103
+ throw codeError("host_request_json_invalid");
1104
+ }
1105
+ if (!isPlainObject(body)) throw codeError("host_request_body_invalid");
1106
+ if (kind === "localIdentityProof") return validateLocalIdentityProofRequest(body);
1107
+ if (kind === "localPeerExchange") return validateLocalPeerRequest(body);
1108
+ if (kind === "attachment") {
1109
+ assertExactKeys(body, ["adapterInstanceId"]);
1110
+ return { adapterInstanceId: assertIdentifier(body.adapterInstanceId, "host_adapter_instance_id_invalid") };
1111
+ }
1112
+ if (kind === "agentMessage") {
1113
+ assertNoSenderIdentity(body);
1114
+ if (Object.keys(body).length === 0) throw codeError("host_agent_message_invalid");
1115
+ }
1116
+ if (kind === "appModelInbox") {
1117
+ assertExactKeys(body, ["operation", "scope", "query", ...(Object.hasOwn(body, "caller") ? ["caller"] : [])]);
1118
+ if (body.scope !== "current") throw codeError("model_inbox_scope_invalid");
1119
+ if (!isPlainObject(body.query)) throw codeError("host_request_body_invalid");
1120
+ const kinds = { list: "appAuthorizedInboxPreview", read: "appAuthorizedInboxRead",
1121
+ pending: "appAuthorizedInboxPendingQuery", claim: "appAuthorizedInboxClaim",
1122
+ assertReply: "appAuthorizedInboxRead" };
1123
+ let query;
1124
+ if (["count", "list", "pending"].includes(body.operation) && Object.keys(body.query).length === 0) query = {};
1125
+ else if (Object.hasOwn(kinds, body.operation)) query = parseAndValidateBody(kinds[body.operation], Buffer.from(JSON.stringify(body.query)));
1126
+ else throw codeError("host_request_body_invalid");
1127
+ const caller = modelInboxCaller(body.caller);
1128
+ return { operation: body.operation, scope: body.scope, query, caller };
1129
+ }
1130
+ if (kind === "appInboxId") {
1131
+ assertExactKeys(body, ["localMessageId"]);
1132
+ return { localMessageId: assertApplicationId(body.localMessageId, "host_app_local_message_id_invalid") };
1133
+ }
1134
+ if (kind === "appAuthorizedInboxClaim" && Object.hasOwn(body, "batchId")) {
1135
+ assertExactKeys(body, ["batchId"]);
1136
+ return { batchId: assertIdentifier(body.batchId, "host_request_body_invalid") };
1137
+ }
1138
+ if (kind.startsWith("appAuthorizedInboxBatch")) {
1139
+ const operation = kind.slice("appAuthorizedInboxBatch".length);
1140
+ const fields = operation === "Reserve" ? ["target", "candidates", ...(Object.hasOwn(body, "batchId") ? ["batchId"] : [])]
1141
+ : operation === "Expire" ? ["batchId", "candidates"]
1142
+ : operation === "Attempt" ? ["batchId", ...["attemptId", "nativeThreadId"].filter(key => Object.hasOwn(body, key))]
1143
+ : operation === "Open" ? ["target", "state"].filter(key => Object.hasOwn(body, key))
1144
+ : operation === "Confirm" && Object.hasOwn(body, "nativeThreadId") ? ["batchId", "nativeThreadId"] : ["batchId"];
1145
+ assertExactKeys(body, fields);
1146
+ const result = {};
1147
+ for (const field of fields) {
1148
+ if (field === "candidates") {
1149
+ if (!Array.isArray(body.candidates) || body.candidates.length > 1000) throw codeError("host_request_body_invalid");
1150
+ result.candidates = body.candidates.map(id => assertApplicationId(id, "host_app_local_message_id_invalid"));
1151
+ } else if (field === "state") {
1152
+ if (!["reserved", "delivered"].includes(body.state)) throw codeError("host_request_body_invalid");
1153
+ result.state = body.state;
1154
+ } else result[field] = assertIdentifier(body[field], "host_request_body_invalid");
1155
+ }
1156
+ return result;
1157
+ }
1158
+ if (kind === "appAuthorizedInboxRead" || kind === "appAuthorizedInboxClaim") {
1159
+ assertExactKeys(body, ["localMessageId", "conversationKey"]);
1160
+ return {
1161
+ localMessageId: assertApplicationId(body.localMessageId, "host_app_local_message_id_invalid"),
1162
+ conversationKey: assertIdentifier(body.conversationKey, "host_app_conversation_key_invalid"),
1163
+ };
1164
+ }
1165
+ if (kind === "appAuthorizedInboxPendingQuery") {
1166
+ if (Object.hasOwn(body, "cursor")) {
1167
+ assertExactKeys(body, ["cursor"]);
1168
+ if (!Number.isSafeInteger(body.cursor) || body.cursor < 0) throw codeError("host_request_body_invalid");
1169
+ return { cursor: body.cursor };
1170
+ }
1171
+ assertExactKeys(body, ["localMessageId", "conversationKey"]);
1172
+ return {
1173
+ localMessageId: assertApplicationId(body.localMessageId, "host_app_local_message_id_invalid"),
1174
+ conversationKey: assertIdentifier(body.conversationKey, "host_app_conversation_key_invalid"),
1175
+ };
1176
+ }
1177
+ if (kind === "appAuthorizedInboxFinish") {
1178
+ assertExactKeys(body, ["localMessageId", "conversationKey", "claimId", "outcome"]);
1179
+ if (!["completed", "unknown"].includes(body.outcome)) throw codeError("host_request_body_invalid");
1180
+ return {
1181
+ localMessageId: assertApplicationId(body.localMessageId, "host_app_local_message_id_invalid"),
1182
+ conversationKey: assertIdentifier(body.conversationKey, "host_app_conversation_key_invalid"),
1183
+ claimId: assertIdentifier(body.claimId, "host_request_body_invalid"),
1184
+ outcome: body.outcome,
1185
+ };
1186
+ }
1187
+ if (kind === "appAuthorizedContactRequestRead") {
1188
+ assertExactKeys(body, ["localRecordId", "conversationKey"]);
1189
+ return {
1190
+ localRecordId: assertApplicationId(body.localRecordId, "host_app_local_record_id_invalid"),
1191
+ conversationKey: assertIdentifier(body.conversationKey, "host_app_conversation_key_invalid"),
1192
+ };
1193
+ }
1194
+ if (kind === "appInboxIds") {
1195
+ assertExactKeys(body, ["localMessageIds"]);
1196
+ return { localMessageIds: assertApplicationIdArray(body.localMessageIds, "host_app_local_message_ids_invalid") };
1197
+ }
1198
+ if (kind === "appContactUpsert") {
1199
+ assertAllowedKeys(body, ["contactId", "hellolynkId", "displayName"]);
1200
+ if (!Object.hasOwn(body, "hellolynkId") || !Object.hasOwn(body, "displayName")) {
1201
+ throw codeError("host_request_body_invalid");
1202
+ }
1203
+ return {
1204
+ ...(body.contactId == null ? {} : {
1205
+ contactId: assertApplicationId(body.contactId, "host_app_contact_id_invalid"),
1206
+ }),
1207
+ hellolynkId: assertHelloLynkId(body.hellolynkId, "host_app_contact_hellolynk_id_invalid"),
1208
+ displayName: assertApplicationDisplayName(body.displayName, "host_app_contact_display_name_invalid"),
1209
+ };
1210
+ }
1211
+ if (kind === "appContactRemove") {
1212
+ assertExactKeys(body, ["contactId"]);
1213
+ return { contactId: assertApplicationId(body.contactId, "host_app_contact_id_invalid") };
1214
+ }
1215
+ if (kind === "appContactAutonomousSendAuthorization") {
1216
+ assertExactKeys(body, ["contactId", "recipientDelegateId", "enabled"]);
1217
+ if (typeof body.enabled !== "boolean") throw codeError("host_request_body_invalid");
1218
+ return {
1219
+ contactId: assertApplicationId(body.contactId, "host_app_contact_id_invalid"),
1220
+ recipientDelegateId: assertIdentifier(body.recipientDelegateId, "host_app_recipient_delegate_invalid"),
1221
+ enabled: body.enabled,
1222
+ };
1223
+ }
1224
+ if (kind === "appOutboxRetryGrant") {
1225
+ assertExactKeys(body, ["adapterInstanceId", "clientMessageId"]);
1226
+ return {
1227
+ adapterInstanceId: assertIdentifier(body.adapterInstanceId, "host_adapter_instance_id_invalid"),
1228
+ clientMessageId: assertApplicationId(body.clientMessageId, "host_app_client_message_id_invalid"),
1229
+ };
1230
+ }
1231
+ if (kind === "appOutboxRetry") {
1232
+ assertExactKeys(body, ["adapterInstanceId", "clientMessageId", "retryGrant"]);
1233
+ const retryGrant = String(body.retryGrant || "");
1234
+ if (!OUTBOX_RETRY_GRANT_PATTERN.test(retryGrant)) {
1235
+ throw codeError("host_app_outbox_retry_grant_required");
1236
+ }
1237
+ return {
1238
+ adapterInstanceId: assertIdentifier(body.adapterInstanceId, "host_adapter_instance_id_invalid"),
1239
+ clientMessageId: assertApplicationId(body.clientMessageId, "host_app_client_message_id_invalid"),
1240
+ retryGrant,
1241
+ };
1242
+ }
1243
+ if (kind === "appHumanSendRequest") {
1244
+ assertExactKeys(body, ["adapterInstanceId", "request"]);
1245
+ if (!isPlainObject(body.request)) throw codeError("host_request_body_invalid");
1246
+ assertExactKeys(body.request, ["clientMessageId", "recipientHelloLynkId", "recipientDelegateId", "text"]);
1247
+ return {
1248
+ adapterInstanceId: assertIdentifier(body.adapterInstanceId, "host_adapter_instance_id_invalid"),
1249
+ request: {
1250
+ clientMessageId: assertApplicationId(body.request.clientMessageId, "host_app_client_message_id_invalid"),
1251
+ recipientHelloLynkId: assertHelloLynkId(body.request.recipientHelloLynkId, "host_app_recipient_hellolynk_id_invalid"),
1252
+ recipientDelegateId: assertIdentifier(body.request.recipientDelegateId, "host_app_recipient_delegate_invalid"),
1253
+ // The native human-confirmation broker has a deliberately smaller input
1254
+ // boundary. Admit the same UTF-8 byte budget here so every client gets a
1255
+ // recoverable validation response before a native child is started.
1256
+ text: assertApplicationString(body.request.text, "host_app_human_send_text_too_large", 20 * 1024),
1257
+ },
1258
+ };
1259
+ }
1260
+ if (kind === "appHumanSendRequestStatus") {
1261
+ assertExactKeys(body, ["adapterInstanceId", "request"]);
1262
+ if (!isPlainObject(body.request)) throw codeError("host_request_body_invalid");
1263
+ assertExactKeys(body.request, ["actionId"]);
1264
+ return {
1265
+ adapterInstanceId: assertIdentifier(body.adapterInstanceId, "host_adapter_instance_id_invalid"),
1266
+ request: { actionId: assertApplicationId(body.request.actionId, "host_app_action_id_invalid") },
1267
+ };
1268
+ }
1269
+ if (kind === "appNativeConversationQuery") {
1270
+ const { contactId, recipientDelegateId, ...query } = body;
1271
+ if (Object.hasOwn(query, "exactTitle")) throw codeError("native_directory_query_invalid");
1272
+ return { contactId: assertApplicationId(contactId, "host_app_contact_id_invalid"),
1273
+ recipientDelegateId: assertIdentifier(recipientDelegateId, "host_app_recipient_delegate_invalid"),
1274
+ ...nativeConversationQuery(query) };
1275
+ }
1276
+ if (kind === "appAuthorizedSend") {
1277
+ const targets = ["targetConversationId", "targetConversationTitle"].filter(key => Object.hasOwn(body, key));
1278
+ assertExactKeys(body, ["clientMessageId", "contactId", "recipientDelegateId", "text", ...targets]);
1279
+ if (targets.length > 1) throw codeError("host_request_body_invalid");
1280
+ return {
1281
+ ...(Object.hasOwn(body, "targetConversationId") ? { targetConversationId: nativeConversationId(body.targetConversationId) } : {}),
1282
+ ...(Object.hasOwn(body, "targetConversationTitle") ? { targetConversationTitle: nativeConversationTitle(body.targetConversationTitle) } : {}),
1283
+ clientMessageId: assertApplicationId(body.clientMessageId, "host_app_client_message_id_invalid"),
1284
+ contactId: assertApplicationId(body.contactId, "host_app_contact_id_invalid"),
1285
+ recipientDelegateId: assertIdentifier(body.recipientDelegateId, "host_app_recipient_delegate_invalid"),
1286
+ text: assertApplicationString(body.text, "host_app_text_invalid", 64 * 1024),
1287
+ };
1288
+ }
1289
+ if (kind === "appAuthorizedReply") {
1290
+ assertExactKeys(body, ["localMessageId", "conversationKey", "clientMessageId", "text"]);
1291
+ return {
1292
+ localMessageId: assertApplicationId(body.localMessageId, "host_app_local_message_id_invalid"),
1293
+ conversationKey: assertIdentifier(body.conversationKey, "host_app_conversation_key_invalid"),
1294
+ clientMessageId: assertApplicationId(body.clientMessageId, "host_app_client_message_id_invalid"),
1295
+ text: assertApplicationString(body.text, "host_app_text_invalid", 64 * 1024),
1296
+ };
1297
+ }
1298
+ if (kind === "appChangesSubscribe") {
1299
+ assertExactKeys(body, ["afterCursor"]);
1300
+ const afterCursor = Number(body.afterCursor);
1301
+ if (!Number.isSafeInteger(afterCursor) || afterCursor < 0) {
1302
+ throw codeError("host_app_change_cursor_invalid");
1303
+ }
1304
+ return { afterCursor };
1305
+ }
1306
+ if (kind === "appAuthorizedInboxPreview") {
1307
+ // An explicit older history page names the last message already listed.
1308
+ if (Object.keys(body).length === 1 && Object.hasOwn(body, "before")) {
1309
+ return { before: assertApplicationId(body.before, "host_app_local_message_id_invalid") };
1310
+ }
1311
+ assertExactKeys(body, ["unreadOnly", "limit"]);
1312
+ if (body.unreadOnly !== true || !Number.isSafeInteger(body.limit) || body.limit < 1 || body.limit > 3) {
1313
+ throw codeError("host_request_body_invalid");
1314
+ }
1315
+ return { unreadOnly: true, limit: body.limit };
1316
+ }
1317
+ if (kind === "appWorkbenchRead") {
1318
+ try {
1319
+ return validateWorkbenchReadRequest(body);
1320
+ } catch {
1321
+ throw codeError("host_app_workbench_read_invalid");
1322
+ }
1323
+ }
1324
+ if (kind === "appWorkbenchActionGrant") {
1325
+ assertExactKeys(body, ["adapterInstanceId", "request"]);
1326
+ if (!isPlainObject(body.request)) throw codeError("host_request_body_invalid");
1327
+ const adapterInstanceId = assertIdentifier(
1328
+ body.adapterInstanceId,
1329
+ "host_adapter_instance_id_invalid",
1330
+ );
1331
+ try {
1332
+ const request = validateWorkbenchCommand(body.request);
1333
+ assertWorkbenchRawSafetyCodeForbidden(request);
1334
+ return {
1335
+ adapterInstanceId,
1336
+ request,
1337
+ };
1338
+ } catch {
1339
+ throw codeError("host_app_workbench_command_invalid");
1340
+ }
1341
+ }
1342
+ if (kind === "appWorkbenchCommand") {
1343
+ assertAllowedKeys(body, ["actionGrant", "adapterInstanceId", "request"]);
1344
+ if (!Object.hasOwn(body, "adapterInstanceId") || !Object.hasOwn(body, "request")) {
1345
+ throw codeError("host_app_workbench_command_invalid");
1346
+ }
1347
+ if (!isPlainObject(body.request)) throw codeError("host_request_body_invalid");
1348
+ const adapterInstanceId = assertIdentifier(
1349
+ body.adapterInstanceId,
1350
+ "host_adapter_instance_id_invalid",
1351
+ );
1352
+ try {
1353
+ const request = validateWorkbenchCommand(body.request);
1354
+ assertWorkbenchRawSafetyCodeForbidden(request);
1355
+ const readOnlyStatus = request.command === HELLOLYNK_WORKBENCH_COMMAND_TYPES.getSendStatus;
1356
+ if ((!readOnlyStatus && !Object.hasOwn(body, "actionGrant"))
1357
+ || (readOnlyStatus && Object.hasOwn(body, "actionGrant") && body.actionGrant != null)) {
1358
+ throw codeError("host_app_workbench_command_invalid");
1359
+ }
1360
+ return {
1361
+ adapterInstanceId,
1362
+ request,
1363
+ ...(Object.hasOwn(body, "actionGrant") ? { actionGrant: body.actionGrant } : {}),
1364
+ };
1365
+ } catch {
1366
+ throw codeError("host_app_workbench_command_invalid");
1367
+ }
1368
+ }
1369
+ return body;
1370
+ }
1371
+
1372
+ function assertWorkbenchRawSafetyCodeForbidden(command) {
1373
+ if (command.command === HELLOLYNK_WORKBENCH_COMMAND_TYPES.saveContact
1374
+ && Object.hasOwn(command.payload, "safetyCode")) {
1375
+ throw codeError("host_app_workbench_command_invalid");
1376
+ }
1377
+ }
1378
+
1379
+ function assertNoSenderIdentity(value, seen = new Set()) {
1380
+ if (value == null || typeof value !== "object") return;
1381
+ if (seen.has(value)) throw codeError("host_agent_message_invalid");
1382
+ seen.add(value);
1383
+ if (Array.isArray(value)) {
1384
+ for (const item of value) assertNoSenderIdentity(item, seen);
1385
+ return;
1386
+ }
1387
+ if (!isPlainObject(value)) throw codeError("host_agent_message_invalid");
1388
+ for (const [key, nested] of Object.entries(value)) {
1389
+ const normalized = key.toLowerCase().replace(/[^a-z0-9]/g, "");
1390
+ if (RESERVED_MESSAGE_IDENTITY_KEYS.has(normalized)) {
1391
+ throw codeError("host_agent_sender_identity_forbidden");
1392
+ }
1393
+ assertNoSenderIdentity(nested, seen);
1394
+ }
1395
+ }
1396
+
1397
+ function normalizeHandlers(value = {}) {
1398
+ const handlers = value && typeof value === "object" ? value : {};
1399
+ return Object.freeze({
1400
+ status: functionOrDefault(handlers.status, () => null),
1401
+ localIdentityProof: functionOrDefault(handlers.localIdentityProof, unavailableApplicationHandler),
1402
+ localPeerExchange: functionOrDefault(handlers.localPeerExchange, unavailableApplicationHandler),
1403
+ attach: functionOrDefault(handlers.attach, () => null),
1404
+ heartbeat: functionOrDefault(handlers.heartbeat, () => null),
1405
+ detach: functionOrDefault(handlers.detach, () => null),
1406
+ agentMessage: functionOrDefault(handlers.agentMessage, () => ({ accepted: true })),
1407
+ appInboxMessages: functionOrDefault(handlers.appInboxMessages, unavailableApplicationHandler),
1408
+ appInboxUnreadCount: functionOrDefault(handlers.appInboxUnreadCount, unavailableApplicationHandler),
1409
+ appInboxMarkRead: functionOrDefault(handlers.appInboxMarkRead, unavailableApplicationHandler),
1410
+ appInboxDeleteRead: functionOrDefault(handlers.appInboxDeleteRead, unavailableApplicationHandler),
1411
+ appContactsList: functionOrDefault(handlers.appContactsList, unavailableApplicationHandler),
1412
+ appContactsUpsert: functionOrDefault(handlers.appContactsUpsert, unavailableApplicationHandler),
1413
+ appContactsRemove: functionOrDefault(handlers.appContactsRemove, unavailableApplicationHandler),
1414
+ appContactAutonomousSendAuthorization: functionOrDefault(
1415
+ handlers.appContactAutonomousSendAuthorization,
1416
+ unavailableApplicationHandler,
1417
+ ),
1418
+ appAuthorizedContactsList: functionOrDefault(
1419
+ handlers.appAuthorizedContactsList,
1420
+ unavailableApplicationHandler,
1421
+ ),
1422
+ appModelInbox: functionOrDefault(handlers.appModelInbox, unavailableApplicationHandler),
1423
+ appAuthorizedInboxMessages: functionOrDefault(
1424
+ handlers.appAuthorizedInboxMessages,
1425
+ unavailableApplicationHandler,
1426
+ ),
1427
+ appAuthorizedInboxActivate: functionOrDefault(
1428
+ handlers.appAuthorizedInboxActivate,
1429
+ unavailableApplicationHandler,
1430
+ ),
1431
+ appAuthorizedInboxRead: functionOrDefault(
1432
+ handlers.appAuthorizedInboxRead,
1433
+ unavailableApplicationHandler,
1434
+ ),
1435
+ ...Object.fromEntries(["appAuthorizedInboxPending", "appAuthorizedInboxPendingQuery", "appAuthorizedInboxClaim", "appAuthorizedInboxFinish", "appAuthorizedInboxBatchReserve", "appAuthorizedInboxBatchOpen", "appAuthorizedInboxBatchAttempt", "appAuthorizedInboxBatchConfirm", "appAuthorizedInboxBatchCancel", "appAuthorizedInboxBatchExpire", "appAuthorizedInboxBatchClosed"]
1436
+ .map((name) => [name, functionOrDefault(handlers[name], unavailableApplicationHandler)])),
1437
+ appAuthorizedContactRequestsList: functionOrDefault(
1438
+ handlers.appAuthorizedContactRequestsList,
1439
+ unavailableApplicationHandler,
1440
+ ),
1441
+ appAuthorizedContactRequestRead: functionOrDefault(
1442
+ handlers.appAuthorizedContactRequestRead,
1443
+ unavailableApplicationHandler,
1444
+ ),
1445
+ appAuthorizedSend: functionOrDefault(handlers.appAuthorizedSend, unavailableApplicationHandler),
1446
+ appAuthorizedReply: functionOrDefault(handlers.appAuthorizedReply, unavailableApplicationHandler),
1447
+ appContactRequestsList: functionOrDefault(handlers.appContactRequestsList, unavailableApplicationHandler),
1448
+ appOutboxMessages: functionOrDefault(handlers.appOutboxMessages, unavailableApplicationHandler),
1449
+ appOutboxRetry: functionOrDefault(handlers.appOutboxRetry, unavailableApplicationHandler),
1450
+ appHumanSendRequest: functionOrDefault(handlers.appHumanSendRequest, unavailableNativeBrokerHandler),
1451
+ appHumanSendRequestStatus: functionOrDefault(handlers.appHumanSendRequestStatus, unavailableNativeBrokerHandler),
1452
+ appChangesSnapshot: functionOrDefault(handlers.appChangesSnapshot, unavailableApplicationHandler),
1453
+ appChangesSubscribe: functionOrDefault(handlers.appChangesSubscribe, unavailableApplicationHandler),
1454
+ appWorkbenchSnapshot: functionOrDefault(handlers.appWorkbenchSnapshot, unavailableApplicationHandler),
1455
+ appWorkbenchActionGrant: functionOrDefault(handlers.appWorkbenchActionGrant, () => null),
1456
+ appWorkbenchCommand: functionOrDefault(handlers.appWorkbenchCommand, unavailableApplicationHandler),
1457
+ idleShutdown: functionOrDefault(handlers.idleShutdown, () => null),
1458
+ });
1459
+ }
1460
+
1461
+ function immutableCapabilityTuple(capability) {
1462
+ return Object.freeze({
1463
+ contract: capability.contract,
1464
+ capabilityId: capability.capabilityId,
1465
+ region: capability.region,
1466
+ principalId: capability.principalId,
1467
+ delegateId: capability.delegateId,
1468
+ hostInstallationId: capability.hostInstallationId,
1469
+ adapterId: capability.adapterId,
1470
+ version: capability.version,
1471
+ state: capability.state,
1472
+ scopes: Object.freeze([...capability.scopes]),
1473
+ issuedAt: capability.issuedAt,
1474
+ revokedAt: capability.revokedAt,
1475
+ secretDigest: crypto.createHash("sha256").update(capability.secretBuffer).digest("hex"),
1476
+ });
1477
+ }
1478
+
1479
+ function immutableCapabilityTupleMatches(left, right) {
1480
+ return left.contract === right.contract
1481
+ && left.capabilityId === right.capabilityId
1482
+ && left.region === right.region
1483
+ && left.principalId === right.principalId
1484
+ && left.delegateId === right.delegateId
1485
+ && left.hostInstallationId === right.hostInstallationId
1486
+ && left.adapterId === right.adapterId
1487
+ && left.version === right.version
1488
+ && left.state === right.state
1489
+ && left.issuedAt === right.issuedAt
1490
+ && left.revokedAt === right.revokedAt
1491
+ && left.secretDigest === right.secretDigest
1492
+ && left.scopes.length === right.scopes.length
1493
+ && left.scopes.every((scope, index) => scope === right.scopes[index]);
1494
+ }
1495
+
1496
+ function publicCapability(capability) {
1497
+ return {
1498
+ region: capability.region,
1499
+ principalId: capability.principalId,
1500
+ hostInstallationId: capability.hostInstallationId,
1501
+ adapterId: capability.adapterId,
1502
+ delegateId: capability.delegateId,
1503
+ capabilityVersion: capability.version,
1504
+ scopes: [...capability.scopes],
1505
+ };
1506
+ }
1507
+
1508
+ function publicActionContext(capability, fields) {
1509
+ return Object.freeze({
1510
+ ...fields,
1511
+ contractId: HELLOLYNK_HOST_CONTRACT,
1512
+ hostGeneration: fields.hostGeneration,
1513
+ region: capability.region,
1514
+ principalId: capability.principalId,
1515
+ hostInstallationId: capability.hostInstallationId,
1516
+ adapterId: capability.adapterId,
1517
+ delegateId: capability.delegateId,
1518
+ capabilityVersion: capability.version,
1519
+ });
1520
+ }
1521
+
1522
+ function attachmentResponse(attachment, alreadyAttached) {
1523
+ return {
1524
+ ok: true,
1525
+ attached: true,
1526
+ alreadyAttached,
1527
+ adapterInstanceId: attachment.adapterInstanceId,
1528
+ adapterId: attachment.adapterId,
1529
+ delegateId: attachment.delegateId,
1530
+ heartbeatAfterMs: HELLOLYNK_HOST_ATTACHMENT_HEARTBEAT_MS,
1531
+ leaseExpiresAt: attachment.expiresAt,
1532
+ };
1533
+ }
1534
+
1535
+ function attachmentMatchesCapability(attachment, capability) {
1536
+ return attachment.capabilityId === capability.capabilityId
1537
+ && attachment.adapterId === capability.adapterId
1538
+ && attachment.delegateId === capability.delegateId
1539
+ && attachment.capabilityVersion === capability.version;
1540
+ }
1541
+
1542
+ function attachmentKey(capabilityId, adapterInstanceId) {
1543
+ return `${capabilityId}\0${adapterInstanceId}`;
1544
+ }
1545
+
1546
+ function workbenchClientType(adapterId) {
1547
+ const value = String(adapterId || "").toLowerCase();
1548
+ if (value === "codex") return "codex";
1549
+ if (value === "cursor") return "cursor";
1550
+ if (value === "dsh") return "dsh";
1551
+ if (value === "claude-code") return "claude-code";
1552
+ if (value === "openclaw") return "openclaw";
1553
+ if (["trae-work", "trae_work", "traework"].includes(value)) return "trae-work";
1554
+ if (["workbuddy", "work-buddy"].includes(value)) return "workbuddy";
1555
+ return "unknown";
1556
+ }
1557
+
1558
+ function requiredHeader(headers, name) {
1559
+ let value;
1560
+ if (typeof headers?.get === "function") value = headers.get(name);
1561
+ else if (headers instanceof Map) value = headers.get(name) ?? headers.get(name.toLowerCase());
1562
+ else if (headers && typeof headers === "object") {
1563
+ const matching = Object.keys(headers).filter((key) => key.toLowerCase() === name);
1564
+ if (matching.length !== 1) throw codeError("host_request_unauthorized");
1565
+ value = headers[matching[0]];
1566
+ }
1567
+ if (Array.isArray(value) || typeof value !== "string" || value.length === 0 || value.includes(",")) {
1568
+ throw codeError("host_request_unauthorized");
1569
+ }
1570
+ return value;
1571
+ }
1572
+
1573
+ function parseTimestampHeader(value) {
1574
+ if (!/^(?:0|[1-9][0-9]{0,15})$/.test(value)) throw codeError("host_request_unauthorized");
1575
+ const timestamp = Number(value);
1576
+ if (!Number.isSafeInteger(timestamp)) throw codeError("host_request_unauthorized");
1577
+ return timestamp;
1578
+ }
1579
+
1580
+ function isHumanOrRootPath(pathname) {
1581
+ return pathname === "/human-api"
1582
+ || pathname.startsWith("/human-api/")
1583
+ || pathname === "/root"
1584
+ || pathname.startsWith("/root/");
1585
+ }
1586
+
1587
+ async function invokeHandler(handler, input) {
1588
+ try {
1589
+ return await handler(input);
1590
+ } catch {
1591
+ throw codeError("host_action_failed");
1592
+ }
1593
+ }
1594
+
1595
+ const applicationDeliveryErrorStatus = Object.freeze({
1596
+ e2ee_peer_identity_pin_mismatch: 409,
1597
+ recipient_e2ee_device_offline: 503,
1598
+ e2ee_prekey_claim_rate_limited: 429,
1599
+ relay_request_body_too_large: 413,
1600
+ });
1601
+
1602
+ async function invokeApplicationHandler(handler, input) {
1603
+ try {
1604
+ return await handler(input);
1605
+ } catch (error) {
1606
+ const code = safeErrorCode(error);
1607
+ if (Object.hasOwn(applicationDeliveryErrorStatus, code) || /^native_(?:directory|target|conversation|content)_/.test(code)
1608
+ || code.startsWith("model_inbox_") || code.startsWith("host_app_") || code.startsWith("human_confirmation_")
1609
+ || code === "host_request_unauthorized") {
1610
+ throw codeError(code);
1611
+ }
1612
+ const workbenchCode = mapWorkbenchApplicationError(error?.code);
1613
+ if (workbenchCode) throw codeError(workbenchCode);
1614
+ throw codeError("host_app_action_failed");
1615
+ }
1616
+ }
1617
+
1618
+ function mapWorkbenchApplicationError(value) {
1619
+ const code = String(value || "");
1620
+ if (!/^neutral_host_workbench_[a-z0-9_]{1,100}$/.test(code)) return null;
1621
+ if (code === "neutral_host_workbench_snapshot_busy") return "host_app_workbench_snapshot_busy";
1622
+ if (code === "neutral_host_workbench_thread_not_found"
1623
+ || code === "neutral_host_workbench_thread_not_in_page") {
1624
+ return "host_app_workbench_thread_not_found";
1625
+ }
1626
+ if (code === "neutral_host_workbench_cursor_invalid"
1627
+ || code === "neutral_host_workbench_thread_cursor_invalid"
1628
+ || code === "neutral_host_workbench_message_cursor_invalid") {
1629
+ return "host_app_workbench_cursor_invalid";
1630
+ }
1631
+ if (code === "neutral_host_workbench_message_cursor_thread_required"
1632
+ || code === "neutral_host_workbench_filter_invalid"
1633
+ || code === "neutral_host_workbench_query_invalid"
1634
+ || code === "neutral_host_workbench_read_request_invalid") {
1635
+ return "host_app_workbench_read_invalid";
1636
+ }
1637
+ return "host_app_workbench_snapshot_failed";
1638
+ }
1639
+
1640
+ function applicationResponse(action, value, requestBody = null) {
1641
+ if (!isPlainObject(value)) throw codeError("host_app_response_invalid");
1642
+ if (action === "appModelInbox") {
1643
+ const operation = requestBody.operation;
1644
+ if (operation === "count") {
1645
+ assertExactKeys(value, ["unreadCount", "humanUnreadCount", "aiPendingCount", "scope"]);
1646
+ if (value.scope !== requestBody.scope || value.unreadCount !== value.humanUnreadCount) throw codeError("host_app_response_invalid");
1647
+ return { ok: true, scope: value.scope, unreadCount: assertNonNegativeInteger(value.unreadCount),
1648
+ humanUnreadCount: assertNonNegativeInteger(value.humanUnreadCount), aiPendingCount: assertNonNegativeInteger(value.aiPendingCount) };
1649
+ }
1650
+ if (operation === "assertReply") {
1651
+ assertExactKeys(value, ["allowed"]);
1652
+ if (value.allowed !== true) throw codeError("host_app_response_invalid");
1653
+ return { ok: true, allowed: true };
1654
+ }
1655
+ if (operation === "pending") {
1656
+ return applicationResponse("appAuthorizedInboxPendingQuery", { messages: value.messages.map(({ receiptSeq, ...message }) => message),
1657
+ nextCursor: value.nextCursor ?? null }, {});
1658
+ }
1659
+ const actions = { list: "appAuthorizedInboxMessages", read: "appAuthorizedInboxRead", claim: "appAuthorizedInboxClaim" };
1660
+ return applicationResponse(actions[operation], value, requestBody.query);
1661
+ }
1662
+ if (action === "appInboxMessages") {
1663
+ assertExactKeys(value, ["messages"]);
1664
+ if (!Array.isArray(value.messages) || value.messages.length > 1_000) {
1665
+ throw codeError("host_app_response_invalid");
1666
+ }
1667
+ return { ok: true, messages: value.messages.map(validateInboxMessageDto) };
1668
+ }
1669
+ if (action === "appInboxUnreadCount") {
1670
+ assertExactKeys(value, ["unreadCount"]);
1671
+ return { ok: true, unreadCount: assertNonNegativeInteger(value.unreadCount) };
1672
+ }
1673
+ if (action === "appInboxMarkRead") {
1674
+ assertExactKeys(value, ["localMessageId", "unread"]);
1675
+ if (value.unread !== false) throw codeError("host_app_response_invalid");
1676
+ return {
1677
+ ok: true,
1678
+ localMessageId: assertApplicationId(value.localMessageId, "host_app_response_invalid"),
1679
+ unread: false,
1680
+ };
1681
+ }
1682
+ if (action === "appInboxDeleteRead") {
1683
+ assertExactKeys(value, ["deletedCount", "deletedIds"]);
1684
+ const deletedIds = assertApplicationIdArray(value.deletedIds, "host_app_response_invalid", 0);
1685
+ if (assertNonNegativeInteger(value.deletedCount) !== deletedIds.length) {
1686
+ throw codeError("host_app_response_invalid");
1687
+ }
1688
+ return { ok: true, deletedCount: deletedIds.length, deletedIds };
1689
+ }
1690
+ if (action === "appContactsList") {
1691
+ assertExactKeys(value, ["contacts"]);
1692
+ if (!Array.isArray(value.contacts) || value.contacts.length > 1_000) {
1693
+ throw codeError("host_app_response_invalid");
1694
+ }
1695
+ return { ok: true, contacts: value.contacts.map(validateContactDto) };
1696
+ }
1697
+ if (action === "appContactsUpsert") {
1698
+ assertExactKeys(value, ["contact"]);
1699
+ return { ok: true, contact: validateContactMutationDto(value.contact) };
1700
+ }
1701
+ if (action === "appContactsRemove") {
1702
+ assertExactKeys(value, ["contactId", "removed"]);
1703
+ if (typeof value.removed !== "boolean") throw codeError("host_app_response_invalid");
1704
+ return {
1705
+ ok: true,
1706
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
1707
+ removed: value.removed,
1708
+ };
1709
+ }
1710
+ if (action === "appContactAutonomousSendAuthorization") {
1711
+ assertExactKeys(value, ["contact"]);
1712
+ return { ok: true, contact: validateContactAuthorizationMutationDto(value.contact) };
1713
+ }
1714
+ if (action === "appAuthorizedContactsList") {
1715
+ assertExactKeys(value, ["contacts", ...(Object.hasOwn(value, "nativeConversations") ? ["nativeConversations"] : [])]);
1716
+ if (!Array.isArray(value.contacts) || value.contacts.length > 1_000) {
1717
+ throw codeError("host_app_response_invalid");
1718
+ }
1719
+ return { ok: true, contacts: value.contacts.map(validateAuthorizedContactDto),
1720
+ ...(value.nativeConversations ? { nativeConversations: nativeDirectoryResult(value.nativeConversations) } : {}) };
1721
+ }
1722
+ if (action === "appAuthorizedInboxMessages" || action === "appAuthorizedInboxPending") {
1723
+ assertExactKeys(value, ["messages"]);
1724
+ if (!Array.isArray(value.messages) || value.messages.length > 1_000) {
1725
+ throw codeError("host_app_response_invalid");
1726
+ }
1727
+ return { ok: true, messages: value.messages.map(validateAuthorizedInboxMetadataDto) };
1728
+ }
1729
+ if (action === "appAuthorizedInboxPendingQuery") {
1730
+ assertExactKeys(value, ["messages", "nextCursor"]);
1731
+ const target = requestBody?.localMessageId != null;
1732
+ if (!Array.isArray(value.messages) || value.messages.length > (target ? 1 : 1_000)
1733
+ || (value.nextCursor !== null && (target || !Number.isSafeInteger(value.nextCursor) || value.nextCursor <= 0))) {
1734
+ throw codeError("host_app_response_invalid");
1735
+ }
1736
+ const messages = value.messages.map(validateAuthorizedInboxMetadataDto);
1737
+ if (target && messages.some((item) => item.localMessageId !== requestBody.localMessageId
1738
+ || item.conversationKey !== requestBody.conversationKey)) throw codeError("host_app_response_invalid");
1739
+ return { ok: true, messages, nextCursor: value.nextCursor };
1740
+ }
1741
+ if (action === "appAuthorizedInboxActivate") {
1742
+ assertExactKeys(value, ["active"]);
1743
+ if (value.active !== true) throw codeError("host_app_response_invalid");
1744
+ return { ok: true, active: true };
1745
+ }
1746
+ if (action === "appAuthorizedInboxRead") {
1747
+ assertExactKeys(value, ["message"]);
1748
+ return { ok: true, message: validateAuthorizedInboxMessageDto(value.message) };
1749
+ }
1750
+ if (action.startsWith("appAuthorizedInboxBatch")) {
1751
+ const operation = action.slice("appAuthorizedInboxBatch".length);
1752
+ if (operation === "Open") {
1753
+ assertExactKeys(value, ["batches"]);
1754
+ if (!Array.isArray(value.batches)) throw codeError("host_app_response_invalid");
1755
+ return { ok: true, batches: value.batches.map(item => validateInboxBatchDto(item)) };
1756
+ }
1757
+ if (operation === "Reserve" && value.status === "empty") {
1758
+ assertExactKeys(value, ["status"]); return { ok: true, status: "empty" };
1759
+ }
1760
+ return { ok: true, ...validateInboxBatchDto(value, operation) };
1761
+ }
1762
+ if (action === "appAuthorizedInboxClaim" && requestBody?.batchId) {
1763
+ assertExactKeys(value, ["claimed", "messages", "expiredCount", "notice"]);
1764
+ if (!Array.isArray(value.messages) || typeof value.claimed !== "boolean" || value.claimed !== (value.messages.length > 0)
1765
+ || !Number.isSafeInteger(value.expiredCount) || value.expiredCount < 0 || (value.notice !== null && typeof value.notice !== "string")) throw codeError("host_app_response_invalid");
1766
+ const messages = value.messages.map(validateAuthorizedInboxMessageDto);
1767
+ if (messages.reduce((n, m) => n + Array.from(m.text).length, 0) > 20000) throw codeError("host_app_response_invalid");
1768
+ return { ok: true, ...value, messages };
1769
+ }
1770
+ if (action === "appAuthorizedInboxClaim") {
1771
+ assertExactKeys(value, ["claimed", "claimId", "message"]);
1772
+ if (value.claimed === false && value.claimId === null && value.message === null) {
1773
+ return { ok: true, claimed: false, claimId: null, message: null };
1774
+ }
1775
+ if (value.claimed !== true) throw codeError("host_app_response_invalid");
1776
+ return { ok: true, claimed: true,
1777
+ claimId: assertIdentifier(value.claimId, "host_app_response_invalid"),
1778
+ message: validateAuthorizedInboxMessageDto(value.message) };
1779
+ }
1780
+ if (action === "appAuthorizedInboxFinish") {
1781
+ assertExactKeys(value, ["claimId", "outcome"]);
1782
+ if (!["completed", "unknown"].includes(value.outcome)) throw codeError("host_app_response_invalid");
1783
+ return { ok: true, claimId: assertIdentifier(value.claimId, "host_app_response_invalid"), outcome: value.outcome };
1784
+ }
1785
+ if (action === "appAuthorizedContactRequestsList") {
1786
+ assertExactKeys(value, ["requests"]);
1787
+ if (!Array.isArray(value.requests) || value.requests.length > 1_000) {
1788
+ throw codeError("host_app_response_invalid");
1789
+ }
1790
+ return { ok: true, requests: value.requests.map(validateAuthorizedContactRequestMetadataDto) };
1791
+ }
1792
+ if (action === "appAuthorizedContactRequestRead") {
1793
+ assertExactKeys(value, ["request"]);
1794
+ return { ok: true, request: validateAuthorizedContactRequestDto(value.request) };
1795
+ }
1796
+ if (action === "appContactRequestsList") {
1797
+ assertExactKeys(value, ["requests"]);
1798
+ if (!Array.isArray(value.requests) || value.requests.length > 1_000) {
1799
+ throw codeError("host_app_response_invalid");
1800
+ }
1801
+ return { ok: true, requests: value.requests.map(validateContactRequestDto) };
1802
+ }
1803
+ if (action === "appOutboxMessages") {
1804
+ assertExactKeys(value, ["messages"]);
1805
+ if (!Array.isArray(value.messages) || value.messages.length > 1_000) {
1806
+ throw codeError("host_app_response_invalid");
1807
+ }
1808
+ return { ok: true, messages: value.messages.map(validateOutboxMessageDto) };
1809
+ }
1810
+ if (action === "appOutboxRetry") {
1811
+ assertExactKeys(value, ["message"]);
1812
+ return { ok: true, message: validateOutboxMutationDto(value.message) };
1813
+ }
1814
+ if (action === "appHumanSendRequest" || action === "appHumanSendRequestStatus") {
1815
+ assertAllowedKeys(value, [
1816
+ "actionId", "code", "contract", "createdAtMs", "expiresAtMs", "idempotent", "state",
1817
+ ]);
1818
+ for (const required of ["actionId", "createdAtMs", "expiresAtMs", "state"]) {
1819
+ if (!Object.hasOwn(value, required)) throw codeError("host_app_response_invalid");
1820
+ }
1821
+ if (Object.hasOwn(value, "idempotent") && typeof value.idempotent !== "boolean") {
1822
+ throw codeError("host_app_response_invalid");
1823
+ }
1824
+ if (Object.hasOwn(value, "code") && value.code != null
1825
+ && !/^human_confirmation_[a-z0-9_]{1,100}$/.test(String(value.code))) {
1826
+ throw codeError("host_app_response_invalid");
1827
+ }
1828
+ const createdAtMs = assertNonNegativeInteger(value.createdAtMs);
1829
+ const expiresAtMs = assertNonNegativeInteger(value.expiresAtMs);
1830
+ if (expiresAtMs < createdAtMs) throw codeError("host_app_response_invalid");
1831
+ return {
1832
+ ok: true,
1833
+ actionId: assertApplicationId(value.actionId, "host_app_response_invalid"),
1834
+ state: assertHumanActionState(value.state),
1835
+ ...(value.code == null ? {} : { code: value.code }),
1836
+ createdAtMs,
1837
+ expiresAtMs,
1838
+ ...(Object.hasOwn(value, "idempotent") ? { idempotent: value.idempotent } : {}),
1839
+ };
1840
+ }
1841
+ if (action === "appAuthorizedSend" || action === "appAuthorizedReply") {
1842
+ assertExactKeys(value, ["accepted", "authorization", "clientMessageId", "contactId", "status"]);
1843
+ if (value.accepted !== true || value.authorization !== "contact_delegate_allowlist") {
1844
+ throw codeError("host_app_response_invalid");
1845
+ }
1846
+ return {
1847
+ ok: true,
1848
+ accepted: true,
1849
+ authorization: value.authorization,
1850
+ clientMessageId: assertApplicationId(value.clientMessageId, "host_app_response_invalid"),
1851
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
1852
+ status: assertApplicationId(value.status, "host_app_response_invalid"),
1853
+ };
1854
+ }
1855
+ if (action === "appChangesSnapshot") return validateChangeSnapshot(value);
1856
+ if (action === "appChangesSubscribe") return validateChangeSubscription(value);
1857
+ if (action === "appWorkbenchSnapshot") {
1858
+ assertExactKeys(value, ["snapshot"]);
1859
+ return { ok: true, snapshot: validateWorkbenchSnapshot(value.snapshot) };
1860
+ }
1861
+ if (action === "appWorkbenchCommand") {
1862
+ return validateWorkbenchCommandResult(value, requestBody?.command);
1863
+ }
1864
+ throw codeError("host_app_response_invalid");
1865
+ }
1866
+
1867
+ function validateInboxMessageDto(value) {
1868
+ assertExactKeys(value, [
1869
+ "localMessageId", "clientMessageId", "peerPrincipalId", "peerHelloLynkId", "senderDelegateId",
1870
+ "senderPrincipalDisplayName", "senderDelegateDisplayName", "messageType", "text", "unread",
1871
+ "createdAt", "updatedAt", "conversationKey", "localDelegateId", "peerDelegateId",
1872
+ ]);
1873
+ if (typeof value.unread !== "boolean") throw codeError("host_app_response_invalid");
1874
+ return {
1875
+ localMessageId: assertApplicationId(value.localMessageId, "host_app_response_invalid"),
1876
+ clientMessageId: assertApplicationId(value.clientMessageId, "host_app_response_invalid"),
1877
+ peerPrincipalId: optionalApplicationIdentifier(value.peerPrincipalId),
1878
+ peerHelloLynkId: value.peerHelloLynkId == null ? null : assertHelloLynkId(value.peerHelloLynkId, "host_app_response_invalid"),
1879
+ senderDelegateId: assertIdentifier(value.senderDelegateId, "host_app_response_invalid"),
1880
+ conversationKey: optionalApplicationIdentifier(value.conversationKey),
1881
+ localDelegateId: optionalApplicationIdentifier(value.localDelegateId),
1882
+ peerDelegateId: optionalApplicationIdentifier(value.peerDelegateId),
1883
+ senderPrincipalDisplayName: optionalApplicationString(value.senderPrincipalDisplayName, 80),
1884
+ senderDelegateDisplayName: optionalApplicationString(value.senderDelegateDisplayName, 80),
1885
+ messageType: assertIdentifier(value.messageType, "host_app_response_invalid"),
1886
+ text: assertApplicationString(value.text, "host_app_response_invalid", 64 * 1024, true),
1887
+ unread: value.unread,
1888
+ createdAt: assertIsoTimestamp(value.createdAt),
1889
+ updatedAt: assertIsoTimestamp(value.updatedAt),
1890
+ };
1891
+ }
1892
+
1893
+ function validateContactDto(value) {
1894
+ assertExactKeys(value, [
1895
+ "contactId", "hellolynkId", "displayName",
1896
+ "autonomousSendAuthorized", "createdAt", "updatedAt",
1897
+ ]);
1898
+ if (typeof value.autonomousSendAuthorized !== "boolean") throw codeError("host_app_response_invalid");
1899
+ return {
1900
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
1901
+ hellolynkId: assertHelloLynkId(value.hellolynkId, "host_app_response_invalid"),
1902
+ displayName: assertApplicationDisplayName(value.displayName, "host_app_response_invalid"),
1903
+ autonomousSendAuthorized: value.autonomousSendAuthorized,
1904
+ createdAt: assertIsoTimestamp(value.createdAt),
1905
+ updatedAt: assertIsoTimestamp(value.updatedAt),
1906
+ };
1907
+ }
1908
+
1909
+ function validateContactAuthorizationMutationDto(value) {
1910
+ assertExactKeys(value, ["contactId", "autonomousSendAuthorized", "updatedAt"]);
1911
+ if (typeof value.autonomousSendAuthorized !== "boolean") throw codeError("host_app_response_invalid");
1912
+ return {
1913
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
1914
+ autonomousSendAuthorized: value.autonomousSendAuthorized,
1915
+ updatedAt: assertIsoTimestamp(value.updatedAt),
1916
+ };
1917
+ }
1918
+
1919
+ function validateAuthorizedContactDto(value) {
1920
+ assertExactKeys(value, ["contactId", "displayName", "recipientDelegateId", "instanceDisplayName"]);
1921
+ return {
1922
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
1923
+ displayName: assertApplicationDisplayName(value.displayName, "host_app_response_invalid"),
1924
+ recipientDelegateId: assertIdentifier(value.recipientDelegateId, "host_app_response_invalid"),
1925
+ instanceDisplayName: assertApplicationDisplayName(value.instanceDisplayName, "host_app_response_invalid"),
1926
+ };
1927
+ }
1928
+
1929
+ function validateInboxBatchDto(value, operation = "") {
1930
+ const extra = operation === "Attempt" ? ["attempted"] : operation === "Cancel" ? ["cancelled"] : [];
1931
+ assertExactKeys(value, ["batchId", "target", "state", "count", "expiredCount", "full", "highWater", "oldestReceipt", "attempts", ...extra]);
1932
+ for (const field of ["count", "expiredCount", "highWater", "oldestReceipt", "attempts"])
1933
+ if (!Number.isSafeInteger(value[field]) || value[field] < 0) throw codeError("host_app_response_invalid");
1934
+ if (!["reserved", "delivered", "closed"].includes(value.state) || typeof value.full !== "boolean" || value.attempts > 2
1935
+ || extra.some(field => typeof value[field] !== "boolean")) throw codeError("host_app_response_invalid");
1936
+ assertIdentifier(value.batchId, "host_app_response_invalid"); assertIdentifier(value.target, "host_app_response_invalid");
1937
+ return value;
1938
+ }
1939
+
1940
+ function validateAuthorizedInboxMetadataDto(value) {
1941
+ const hasReply = Object.hasOwn(value, "replyToClientMessageId");
1942
+ const hasTarget = Object.hasOwn(value, "targetConversationId");
1943
+ const hasInstanceName = Object.hasOwn(value, "peerInstanceDisplayName");
1944
+ const hasExpiry = Object.hasOwn(value, "automaticDeliveryState");
1945
+ if (hasExpiry && value.automaticDeliveryState !== "expired") throw codeError("host_app_response_invalid");
1946
+ assertExactKeys(value, ["localMessageId", "contactId", "displayName", "unread", "createdAt", "conversationKey", "localDelegateId", "peerDelegateId", "wakeEligible", ...(hasReply ? ["replyToClientMessageId"] : []), ...(hasTarget ? ["targetConversationId"] : []), ...(hasInstanceName ? ["peerInstanceDisplayName"] : []), ...(hasExpiry ? ["automaticDeliveryState"] : [])]);
1947
+ if (typeof value.unread !== "boolean" || typeof value.wakeEligible !== "boolean") throw codeError("host_app_response_invalid");
1948
+ return {
1949
+ localMessageId: assertApplicationId(value.localMessageId, "host_app_response_invalid"),
1950
+ contactId: value.contactId === null ? null : assertApplicationId(value.contactId, "host_app_response_invalid"),
1951
+ displayName: assertApplicationDisplayName(value.displayName, "host_app_response_invalid"),
1952
+ conversationKey: assertIdentifier(value.conversationKey, "host_app_response_invalid"),
1953
+ localDelegateId: assertIdentifier(value.localDelegateId, "host_app_response_invalid"),
1954
+ peerDelegateId: assertIdentifier(value.peerDelegateId, "host_app_response_invalid"),
1955
+ ...(hasExpiry ? { automaticDeliveryState: "expired" } : {}),
1956
+ ...(hasInstanceName ? { peerInstanceDisplayName: assertApplicationDisplayName(value.peerInstanceDisplayName, "host_app_response_invalid") } : {}),
1957
+ ...(hasReply ? { replyToClientMessageId: assertApplicationId(value.replyToClientMessageId, "host_app_response_invalid") } : {}),
1958
+ ...(hasTarget ? { targetConversationId: nativeConversationId(value.targetConversationId) } : {}),
1959
+ unread: value.unread,
1960
+ wakeEligible: value.wakeEligible,
1961
+ createdAt: assertIsoTimestamp(value.createdAt),
1962
+ };
1963
+ }
1964
+
1965
+ function validateAuthorizedInboxMessageDto(value) {
1966
+ const {
1967
+ text, peerPrincipalId, source, externalContentIsUntrustedData, readDoesNotMarkRead, humanReadAutomatically, ...metadata
1968
+ } = value;
1969
+ if (source !== "hellolynk_external_message"
1970
+ || externalContentIsUntrustedData !== true
1971
+ || !projectInboxReadState(value)) {
1972
+ throw codeError("host_app_response_invalid");
1973
+ }
1974
+ return {
1975
+ ...validateAuthorizedInboxMetadataDto(metadata),
1976
+ peerPrincipalId: assertIdentifier(peerPrincipalId, "host_app_response_invalid"),
1977
+ text: assertApplicationString(text, "host_app_response_invalid", 64 * 1024, true),
1978
+ source,
1979
+ externalContentIsUntrustedData: true,
1980
+ readDoesNotMarkRead,
1981
+ ...(humanReadAutomatically === true ? { humanReadAutomatically: true } : {}),
1982
+ };
1983
+ }
1984
+
1985
+ function validateAuthorizedContactRequestMetadataDto(value) {
1986
+ assertExactKeys(value, [
1987
+ "localRecordId", "conversationKey", "localDelegateId", "sender", "state", "receivedAt",
1988
+ ]);
1989
+ assertExactKeys(value.sender, ["hellolynkId", "verifiedDisplayName"]);
1990
+ const state = assertIdentifier(value.state, "host_app_response_invalid");
1991
+ if (!CONTACT_REQUEST_STATES.has(state)) throw codeError("host_app_response_invalid");
1992
+ return {
1993
+ localRecordId: assertApplicationId(value.localRecordId, "host_app_response_invalid"),
1994
+ conversationKey: assertIdentifier(value.conversationKey, "host_app_response_invalid"),
1995
+ localDelegateId: assertIdentifier(value.localDelegateId, "host_app_response_invalid"),
1996
+ sender: {
1997
+ hellolynkId: assertHelloLynkId(value.sender.hellolynkId, "host_app_response_invalid"),
1998
+ verifiedDisplayName: optionalApplicationString(value.sender.verifiedDisplayName, 320),
1999
+ },
2000
+ state,
2001
+ receivedAt: assertIsoTimestamp(value.receivedAt),
2002
+ };
2003
+ }
2004
+
2005
+ function validateAuthorizedContactRequestDto(value) {
2006
+ const {
2007
+ introText, source, externalContentIsUntrustedData, readDoesNotMarkRead, ...metadata
2008
+ } = value;
2009
+ if (source !== "hellolynk_contact_request"
2010
+ || externalContentIsUntrustedData !== true
2011
+ || readDoesNotMarkRead !== true) {
2012
+ throw codeError("host_app_response_invalid");
2013
+ }
2014
+ return {
2015
+ ...validateAuthorizedContactRequestMetadataDto(metadata),
2016
+ introText: assertApplicationString(introText, "host_app_response_invalid", 256, true),
2017
+ source,
2018
+ externalContentIsUntrustedData: true,
2019
+ readDoesNotMarkRead: true,
2020
+ };
2021
+ }
2022
+
2023
+ function validateContactMutationDto(value) {
2024
+ assertExactKeys(value, ["contactId", "hellolynkId", "displayName", "updatedAt"]);
2025
+ return {
2026
+ contactId: assertApplicationId(value.contactId, "host_app_response_invalid"),
2027
+ hellolynkId: assertHelloLynkId(value.hellolynkId, "host_app_response_invalid"),
2028
+ displayName: assertApplicationDisplayName(value.displayName, "host_app_response_invalid"),
2029
+ updatedAt: assertIsoTimestamp(value.updatedAt),
2030
+ };
2031
+ }
2032
+
2033
+ function validateOutboxMessageDto(value) {
2034
+ assertExactKeys(value, [
2035
+ "localMessageId", "clientMessageId", "recipientHelloLynkId", "messageType", "text", "textSha256",
2036
+ "status", "retryAllowed", "createdAt", "updatedAt",
2037
+ ]);
2038
+ if (typeof value.retryAllowed !== "boolean" || !/^[a-f0-9]{64}$/.test(String(value.textSha256 || ""))) {
2039
+ throw codeError("host_app_response_invalid");
2040
+ }
2041
+ return {
2042
+ localMessageId: assertApplicationId(value.localMessageId, "host_app_response_invalid"),
2043
+ clientMessageId: assertApplicationId(value.clientMessageId, "host_app_response_invalid"),
2044
+ recipientHelloLynkId: assertHelloLynkId(value.recipientHelloLynkId, "host_app_response_invalid"),
2045
+ messageType: assertIdentifier(value.messageType, "host_app_response_invalid"),
2046
+ text: assertApplicationString(value.text, "host_app_response_invalid", 64 * 1024, true),
2047
+ textSha256: value.textSha256,
2048
+ status: assertIdentifier(value.status, "host_app_response_invalid"),
2049
+ retryAllowed: value.retryAllowed,
2050
+ createdAt: assertIsoTimestamp(value.createdAt),
2051
+ updatedAt: assertIsoTimestamp(value.updatedAt),
2052
+ };
2053
+ }
2054
+
2055
+ function validateOutboxMutationDto(value) {
2056
+ assertExactKeys(value, ["clientMessageId", "status", "retryAllowed", "updatedAt"]);
2057
+ if (typeof value.retryAllowed !== "boolean") throw codeError("host_app_response_invalid");
2058
+ return {
2059
+ clientMessageId: assertApplicationId(value.clientMessageId, "host_app_response_invalid"),
2060
+ status: assertIdentifier(value.status, "host_app_response_invalid"),
2061
+ retryAllowed: value.retryAllowed,
2062
+ updatedAt: assertIsoTimestamp(value.updatedAt),
2063
+ };
2064
+ }
2065
+
2066
+ function validateContactRequestDto(value) {
2067
+ const legacyKeys = [
2068
+ "actionReady", "humanOnly", "introText", "introWeight", "localRecordId",
2069
+ "modelVisibility", "receivedAt", "sender", "serverContactRequestId",
2070
+ "serverUpdatedAt", "state",
2071
+ ];
2072
+ const decisionKeys = ["decisionOutcome", "decisionAction"];
2073
+ const hasDecisionOutcome = Object.hasOwn(value, "decisionOutcome");
2074
+ const hasDecisionAction = Object.hasOwn(value, "decisionAction");
2075
+ if (hasDecisionOutcome !== hasDecisionAction) throw codeError("host_app_response_invalid");
2076
+ assertExactKeys(value, hasDecisionOutcome ? [...legacyKeys, ...decisionKeys] : legacyKeys);
2077
+ assertExactKeys(value.sender, ["hellolynkId", "verifiedDisplayName"]);
2078
+ if (value.humanOnly !== true || value.modelVisibility !== "none"
2079
+ || typeof value.actionReady !== "boolean") {
2080
+ throw codeError("host_app_response_invalid");
2081
+ }
2082
+ const introWeight = assertNonNegativeInteger(value.introWeight);
2083
+ if (!Number.isSafeInteger(value.introWeight) || introWeight > 40) throw codeError("host_app_response_invalid");
2084
+ const serverContactRequestId = optionalApplicationIdentifier(value.serverContactRequestId);
2085
+ const state = assertIdentifier(value.state, "host_app_response_invalid");
2086
+ const decisionOutcome = hasDecisionOutcome ? value.decisionOutcome : null;
2087
+ const decisionAction = hasDecisionAction ? value.decisionAction : null;
2088
+ const actionReady = hasDecisionOutcome ? value.actionReady : false;
2089
+ if (!CONTACT_REQUEST_STATES.has(state)
2090
+ || !new Set([null, "unknown", "recovery-required"]).has(decisionOutcome)
2091
+ || (decisionAction != null && !new Set(["accept", "reject", "block"]).has(decisionAction))
2092
+ || (actionReady && (serverContactRequestId == null
2093
+ || state !== "pending"
2094
+ || decisionOutcome != null
2095
+ || decisionAction != null))
2096
+ || (state !== "pending" && actionReady)
2097
+ || ((decisionOutcome === "unknown") !== (decisionAction != null))
2098
+ || (decisionOutcome != null && (state !== "pending" || actionReady))) {
2099
+ throw codeError("host_app_response_invalid");
2100
+ }
2101
+ return {
2102
+ humanOnly: true,
2103
+ modelVisibility: "none",
2104
+ localRecordId: assertApplicationId(value.localRecordId, "host_app_response_invalid"),
2105
+ serverContactRequestId,
2106
+ state,
2107
+ actionReady,
2108
+ decisionOutcome,
2109
+ decisionAction,
2110
+ introText: assertApplicationString(value.introText, "host_app_response_invalid", 256, true),
2111
+ introWeight,
2112
+ sender: {
2113
+ hellolynkId: assertHelloLynkId(value.sender.hellolynkId, "host_app_response_invalid"),
2114
+ verifiedDisplayName: optionalApplicationString(value.sender.verifiedDisplayName, 320),
2115
+ },
2116
+ receivedAt: assertIsoTimestamp(value.receivedAt),
2117
+ serverUpdatedAt: value.serverUpdatedAt == null ? null : assertIsoTimestamp(value.serverUpdatedAt),
2118
+ };
2119
+ }
2120
+
2121
+ function validateChangeSnapshot(value) {
2122
+ assertExactKeys(value, ["cursor", "inbox", "contacts", "outbox", "modelVisibility"]);
2123
+ if (value.modelVisibility !== "none") throw codeError("host_app_response_invalid");
2124
+ assertExactKeys(value.inbox, ["total", "unread"]);
2125
+ assertExactKeys(value.contacts, ["total"]);
2126
+ assertExactKeys(value.outbox, ["total", "retryEligible"]);
2127
+ return {
2128
+ ok: true,
2129
+ cursor: assertNonNegativeInteger(value.cursor),
2130
+ inbox: {
2131
+ total: assertNonNegativeInteger(value.inbox.total),
2132
+ unread: assertNonNegativeInteger(value.inbox.unread),
2133
+ },
2134
+ contacts: { total: assertNonNegativeInteger(value.contacts.total) },
2135
+ outbox: {
2136
+ total: assertNonNegativeInteger(value.outbox.total),
2137
+ retryEligible: assertNonNegativeInteger(value.outbox.retryEligible),
2138
+ },
2139
+ modelVisibility: "none",
2140
+ };
2141
+ }
2142
+
2143
+ function validateChangeSubscription(value) {
2144
+ assertExactKeys(value, ["cursor", "snapshotRequired", "events"]);
2145
+ if (typeof value.snapshotRequired !== "boolean" || !Array.isArray(value.events) || value.events.length > 256) {
2146
+ throw codeError("host_app_response_invalid");
2147
+ }
2148
+ return {
2149
+ ok: true,
2150
+ cursor: assertNonNegativeInteger(value.cursor),
2151
+ snapshotRequired: value.snapshotRequired,
2152
+ events: value.events.map((event) => {
2153
+ assertExactKeys(event, ["cursor", "type", "entityId", "changedAt", "modelVisibility"]);
2154
+ if (event.modelVisibility !== "none") throw codeError("host_app_response_invalid");
2155
+ return {
2156
+ cursor: assertNonNegativeInteger(event.cursor),
2157
+ type: assertIdentifier(event.type, "host_app_response_invalid"),
2158
+ entityId: assertApplicationId(event.entityId, "host_app_response_invalid"),
2159
+ changedAt: assertIsoTimestamp(event.changedAt),
2160
+ modelVisibility: "none",
2161
+ };
2162
+ }),
2163
+ };
2164
+ }
2165
+
2166
+ function response(status, body) {
2167
+ return { status, body };
2168
+ }
2169
+
2170
+ function workbenchActionGrantFailure(command, outcome) {
2171
+ const acceptedOutcome = outcome === "unknown" ? "unknown" : "known-failed";
2172
+ return validateWorkbenchCommandResult({
2173
+ contractId: HELLOLYNK_WORKBENCH_RESULT_CONTRACT,
2174
+ schemaVersion: HELLOLYNK_WORKBENCH_SCHEMA_VERSION,
2175
+ ok: false,
2176
+ requestId: command.requestId,
2177
+ revision: null,
2178
+ changeCursor: null,
2179
+ error: {
2180
+ code: "host_workbench_action_grant_invalid",
2181
+ retryable: false,
2182
+ outcome: acceptedOutcome,
2183
+ retryAfterMs: null,
2184
+ currentRevision: null,
2185
+ },
2186
+ }, command.command);
2187
+ }
2188
+
2189
+ function errorResponse(error) {
2190
+ const code = safeErrorCode(error);
2191
+ return response(errorStatus(code), { ok: false, error: code });
2192
+ }
2193
+
2194
+ function errorStatus(code) {
2195
+ if (Object.hasOwn(applicationDeliveryErrorStatus, code)) return applicationDeliveryErrorStatus[code];
2196
+ if (code === "native_directory_own_ai_only" || code === "native_target_not_authorized") return 403;
2197
+ if (code === "native_directory_unavailable" || code === "native_target_unsupported_or_unavailable") return 503;
2198
+ if (["native_target_unavailable", "native_target_title_not_unique", "native_target_title_required"].includes(code)) return 409;
2199
+ if (code === "host_route_not_found") return 404;
2200
+ if (code === "host_app_record_not_found") return 404;
2201
+ if (code === "host_app_workbench_thread_not_found") return 404;
2202
+ if (code === "human_confirmation_not_found") return 404;
2203
+ if (code === "host_adapter_route_forbidden" || code === "host_capability_scope_denied"
2204
+ || code === "host_capability_human_surface_required"
2205
+ || code === "host_app_authorization_required") return 403;
2206
+ if (code === "host_request_unauthorized") return 401;
2207
+ if (code === "host_request_replayed" || code === "host_attachment_not_found"
2208
+ || code === "host_attachment_ambiguous" || code === "host_app_state_conflict"
2209
+ || code === "host_app_send_failed"
2210
+ || code === "host_app_reply_target_deleted"
2211
+ || code === "host_app_contact_relationship_not_active"
2212
+ || code === "human_confirmation_pending_conflict"
2213
+ || code === "human_confirmation_host_generation_mismatch"
2214
+ || code === "human_confirmation_authority_invalidated"
2215
+ || code === "human_confirmation_binding_mismatch") return 409;
2216
+ if (code === "human_confirmation_timed_out") return 408;
2217
+ if (code === "human_confirmation_rate_limited") return 429;
2218
+ if (code === "host_request_too_large" || code === "host_app_human_send_text_too_large") return 413;
2219
+ if (code === "host_not_running" || code === "host_app_unavailable"
2220
+ || code === "host_app_workbench_snapshot_busy"
2221
+ || code === "host_app_native_broker_unavailable"
2222
+ || code === "host_app_relationship_status_unavailable"
2223
+ || code === "host_workbench_action_grant_capacity_exceeded"
2224
+ || code === "human_confirmation_ledger_unavailable"
2225
+ || code === "human_confirmation_broker_closed") return 503;
2226
+ if (code.startsWith("host_action_") || code === "host_app_action_failed"
2227
+ || code === "host_app_response_invalid" || code === "host_app_workbench_snapshot_failed"
2228
+ || code === "host_workbench_action_grant_binding_invalid"
2229
+ || code === "host_workbench_action_grant_clock_invalid"
2230
+ || code === "host_workbench_action_grant_generation_failed"
2231
+ || code === "host_workbench_action_grant_random_source_invalid"
2232
+ || code === "host_workbench_action_grant_registry_invalid") return 500;
2233
+ if (code.startsWith("human_confirmation_")) {
2234
+ if (/_(?:invalid|malformed)$/.test(code)
2235
+ || code === "human_confirmation_cancelled"
2236
+ || code === "human_confirmation_proof_invalid") return 400;
2237
+ return 500;
2238
+ }
2239
+ return 400;
2240
+ }
2241
+
2242
+ function safeErrorCode(error) {
2243
+ const code = String(error?.code || "host_action_failed");
2244
+ return Object.hasOwn(applicationDeliveryErrorStatus, code) || /^(?:host|human_confirmation|native_directory|native_target|native_conversation|native_content|model_inbox)_[a-z0-9_]{1,100}$/.test(code)
2245
+ ? code : "host_action_failed";
2246
+ }
2247
+
2248
+ function assertExactKeys(value, expected) {
2249
+ const actual = Object.keys(value).sort();
2250
+ const wanted = [...expected].sort();
2251
+ if (actual.length !== wanted.length || actual.some((key, index) => key !== wanted[index])) {
2252
+ throw codeError("host_request_body_invalid");
2253
+ }
2254
+ }
2255
+
2256
+ function assertAllowedKeys(value, allowed) {
2257
+ const actual = Object.keys(value);
2258
+ if (actual.some((key) => !allowed.includes(key))) throw codeError("host_request_body_invalid");
2259
+ }
2260
+
2261
+ function assertApplicationIdArray(value, code, minimumLength = 1) {
2262
+ if (!Array.isArray(value) || value.length < minimumLength || value.length > 500) throw codeError(code);
2263
+ const identifiers = value.map((item) => assertApplicationId(item, code));
2264
+ if (new Set(identifiers).size !== identifiers.length) throw codeError(code);
2265
+ return identifiers;
2266
+ }
2267
+
2268
+ function assertApplicationId(value, code) {
2269
+ const identifier = String(value || "");
2270
+ if (!APPLICATION_ID_PATTERN.test(identifier)) throw codeError(code);
2271
+ return identifier;
2272
+ }
2273
+
2274
+ function assertApplicationString(value, code, maximumBytes, allowEmpty = false) {
2275
+ if (typeof value !== "string" || (!allowEmpty && value.length === 0)
2276
+ || Buffer.byteLength(value, "utf8") > maximumBytes || value.includes("\u0000")) {
2277
+ throw codeError(code);
2278
+ }
2279
+ return value;
2280
+ }
2281
+
2282
+ function assertApplicationDisplayName(value, code) {
2283
+ // The local contact store permits 80 Unicode code points, up to 320 UTF-8 bytes.
2284
+ const name = assertApplicationString(value, code, 80 * 4);
2285
+ if ([...name].length > 80) throw codeError(code);
2286
+ return name;
2287
+ }
2288
+
2289
+ function optionalApplicationString(value, maximumBytes) {
2290
+ if (value == null) return null;
2291
+ return assertApplicationString(value, "host_app_response_invalid", maximumBytes, true);
2292
+ }
2293
+
2294
+ function optionalApplicationIdentifier(value) {
2295
+ if (value == null) return null;
2296
+ return assertIdentifier(value, "host_app_response_invalid");
2297
+ }
2298
+
2299
+ function assertHelloLynkId(value, code) {
2300
+ const hellolynkId = String(value || "");
2301
+ if (!HELLOLYNK_ID_PATTERN.test(hellolynkId)) throw codeError(code);
2302
+ return hellolynkId;
2303
+ }
2304
+
2305
+ function assertIsoTimestamp(value) {
2306
+ if (typeof value !== "string") throw codeError("host_app_response_invalid");
2307
+ const timestamp = Date.parse(value);
2308
+ if (!Number.isFinite(timestamp) || new Date(timestamp).toISOString() !== value) {
2309
+ throw codeError("host_app_response_invalid");
2310
+ }
2311
+ return value;
2312
+ }
2313
+
2314
+ function assertNonNegativeInteger(value) {
2315
+ const number = Number(value);
2316
+ if (!Number.isSafeInteger(number) || number < 0) throw codeError("host_app_response_invalid");
2317
+ return number;
2318
+ }
2319
+
2320
+ function assertHumanActionState(value) {
2321
+ const state = String(value || "");
2322
+ if (!new Set(["approved", "cancelled", "consuming", "expired", "failed", "invalidated", "pending"])
2323
+ .has(state)) throw codeError("host_app_response_invalid");
2324
+ return state;
2325
+ }
2326
+
2327
+ function isPlainObject(value) {
2328
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
2329
+ const prototype = Object.getPrototypeOf(value);
2330
+ return prototype === Object.prototype || prototype === null;
2331
+ }
2332
+
2333
+ function normalizeClock(value) {
2334
+ if (value == null) return Object.freeze({ now: () => Date.now() });
2335
+ if (typeof value === "function") return Object.freeze({ now: value });
2336
+ if (typeof value?.now === "function") return Object.freeze({ now: () => value.now() });
2337
+ throw codeError("host_clock_invalid");
2338
+ }
2339
+
2340
+ function normalizeLimit(value, fallback, minimum, maximum) {
2341
+ const normalized = Number(value ?? fallback);
2342
+ if (!Number.isSafeInteger(normalized) || normalized < minimum || normalized > maximum) {
2343
+ throw codeError("host_limit_invalid");
2344
+ }
2345
+ return normalized;
2346
+ }
2347
+
2348
+ function functionOrDefault(value, fallback) {
2349
+ if (value == null) return fallback;
2350
+ if (typeof value !== "function") throw codeError("host_action_handler_invalid");
2351
+ return value;
2352
+ }
2353
+
2354
+ function unavailableApplicationHandler() {
2355
+ throw codeError("host_app_unavailable");
2356
+ }
2357
+
2358
+ function unavailableNativeBrokerHandler() {
2359
+ throw codeError("host_app_native_broker_unavailable");
2360
+ }
2361
+
2362
+ function isLegacySendRoute(pathname) {
2363
+ return pathname === HELLOLYNK_HOST_PATHS.agentMessages;
2364
+ }
2365
+
2366
+ function randomGeneration() {
2367
+ return crypto.randomBytes(24).toString("hex");
2368
+ }
2369
+
2370
+ function codeError(code) {
2371
+ const error = new Error(code);
2372
+ error.code = code;
2373
+ return error;
2374
+ }