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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (123) hide show
  1. package/LICENSE +92 -0
  2. package/README.md +86 -2
  3. package/bin/hellolynk-client-setup.mjs +2748 -0
  4. package/bin/hellolynk-mcp-config-shape.mjs +23 -0
  5. package/hellolynk-client-pack-manifest.json +619 -0
  6. package/hellolynk-client-pack-pin.json +4 -0
  7. package/integrations/trae-work/package.json +11 -0
  8. package/integrations/trae-work/runtime/neutral-host-bootstrap.mjs +38 -0
  9. package/integrations/trae-work/runtime/neutral-host-release-entry.mjs +16 -0
  10. package/integrations/trae-work/runtime/trae-work-bootstrap.mjs +30 -0
  11. package/integrations/trae-work/server/dynamic-ui-contract.mjs +376 -0
  12. package/integrations/trae-work/server/host-lifecycle.mjs +128 -0
  13. package/integrations/trae-work/server/mcp-proxy.mjs +181 -0
  14. package/integrations/trae-work/server/mcp-server.mjs +784 -0
  15. package/integrations/trae-work/server/native-task-cdp.mjs +241 -0
  16. package/integrations/trae-work/server/native-task-worker.mjs +199 -0
  17. package/integrations/trae-work/server/neutral-host-adapter.mjs +725 -0
  18. package/integrations/trae-work/server/trae-neutral-production.mjs +97 -0
  19. package/integrations/trae-work/server/trae-ui-launch.mjs +182 -0
  20. package/integrations/trae-work/server/ui-action-gate.mjs +122 -0
  21. package/integrations/trae-work/skills/hellolynk-inbox/SKILL.md +9 -0
  22. package/package.json +61 -4
  23. package/runtime/bootstrap/core/hellolynk-client-account.mjs +180 -0
  24. package/runtime/bootstrap/core/hellolynk-client-core.mjs +770 -0
  25. package/runtime/bootstrap/core/hellolynk-client-profile.mjs +191 -0
  26. package/runtime/bootstrap/core/hellolynk-host-auth.mjs +347 -0
  27. package/runtime/bootstrap/core/hellolynk-host-protected-store.mjs +2721 -0
  28. package/runtime/bootstrap/core/hellolynk-local-process-lock.mjs +157 -0
  29. package/runtime/bootstrap/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  30. package/runtime/bootstrap/core/hellolynk-platform-credential-store.mjs +773 -0
  31. package/runtime/bootstrap/core/hellolynk-runtime-safety.cjs +471 -0
  32. package/runtime/bootstrap/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  33. package/runtime/connect-activate.mjs +837 -0
  34. package/runtime/core/hellolynk-agent-configurator.mjs +701 -0
  35. package/runtime/core/hellolynk-client-account.mjs +180 -0
  36. package/runtime/core/hellolynk-client-core.mjs +770 -0
  37. package/runtime/core/hellolynk-client-profile.mjs +191 -0
  38. package/runtime/core/hellolynk-client-update-entry.mjs +31 -0
  39. package/runtime/core/hellolynk-client-update-runtime.mjs +600 -0
  40. package/runtime/core/hellolynk-client-update.mjs +453 -0
  41. package/runtime/core/hellolynk-contact-request-e2ee.mjs +867 -0
  42. package/runtime/core/hellolynk-device-enrollment.mjs +435 -0
  43. package/runtime/core/hellolynk-direct-text-e2ee-client.mjs +1964 -0
  44. package/runtime/core/hellolynk-direct-text-e2ee.mjs +1207 -0
  45. package/runtime/core/hellolynk-host-auth.mjs +347 -0
  46. package/runtime/core/hellolynk-host-protected-store.mjs +2721 -0
  47. package/runtime/core/hellolynk-host-transport-client.mjs +946 -0
  48. package/runtime/core/hellolynk-host.mjs +2374 -0
  49. package/runtime/core/hellolynk-human-confirmation-ledger.mjs +256 -0
  50. package/runtime/core/hellolynk-human-relationship-controller.mjs +1234 -0
  51. package/runtime/core/hellolynk-inbox-read-state.mjs +11 -0
  52. package/runtime/core/hellolynk-indexed-message-store.mjs +1558 -0
  53. package/runtime/core/hellolynk-instance-conversation.mjs +185 -0
  54. package/runtime/core/hellolynk-local-contact-requests.mjs +1733 -0
  55. package/runtime/core/hellolynk-local-contacts.mjs +1228 -0
  56. package/runtime/core/hellolynk-local-data-migration.mjs +766 -0
  57. package/runtime/core/hellolynk-local-data-protection.mjs +747 -0
  58. package/runtime/core/hellolynk-local-identity-trust.mjs +274 -0
  59. package/runtime/core/hellolynk-local-messaging.mjs +3043 -0
  60. package/runtime/core/hellolynk-local-peer-transport.mjs +103 -0
  61. package/runtime/core/hellolynk-local-process-lock.mjs +157 -0
  62. package/runtime/core/hellolynk-model-inbox.mjs +230 -0
  63. package/runtime/core/hellolynk-model-notices.cjs +60 -0
  64. package/runtime/core/hellolynk-native-addressing-tools.cjs +231 -0
  65. package/runtime/core/hellolynk-native-conversations.mjs +280 -0
  66. package/runtime/core/hellolynk-native-human-confirmation-broker.mjs +1036 -0
  67. package/runtime/core/hellolynk-native-inbox-runtime.mjs +553 -0
  68. package/runtime/core/hellolynk-native-notice.mjs +50 -0
  69. package/runtime/core/hellolynk-native-reply-routes.mjs +96 -0
  70. package/runtime/core/hellolynk-native-worker-lease.mjs +84 -0
  71. package/runtime/core/hellolynk-neutral-host-assembly.mjs +955 -0
  72. package/runtime/core/hellolynk-neutral-host-installation-store.mjs +765 -0
  73. package/runtime/core/hellolynk-neutral-host-launcher.mjs +665 -0
  74. package/runtime/core/hellolynk-neutral-host-process.mjs +4374 -0
  75. package/runtime/core/hellolynk-neutral-host-store-adapters.mjs +548 -0
  76. package/runtime/core/hellolynk-neutral-host-workbench-read-model.mjs +1365 -0
  77. package/runtime/core/hellolynk-platform-credential-store.mjs +773 -0
  78. package/runtime/core/hellolynk-relay-client-cli.mjs +232 -0
  79. package/runtime/core/hellolynk-release-retention.mjs +160 -0
  80. package/runtime/core/hellolynk-runtime-safety.cjs +471 -0
  81. package/runtime/core/hellolynk-workbench-action-grants.mjs +457 -0
  82. package/runtime/core/hellolynk-workbench-connection.mjs +234 -0
  83. package/runtime/neutral-host-bootstrap.mjs +38 -0
  84. package/runtime/neutral-host-release-entry.mjs +16 -0
  85. package/runtime/neutral-host-release-loader.mjs +156 -0
  86. package/runtime/neutral-host-release-manifest.json +394 -0
  87. package/runtime/neutral-host-release-pin.json +4 -0
  88. package/runtime/platform/windows/configure-agent.ps1 +281 -0
  89. package/runtime/platform/windows/credential-store.ps1 +844 -0
  90. package/runtime/platform/windows/hellolynk-host-protected-store.ps1 +2866 -0
  91. package/runtime/platform/windows/hellolynk-human-confirmation-ledger.ps1 +155 -0
  92. package/runtime/platform/windows/hellolynk-local-data-key.ps1 +300 -0
  93. package/runtime/platform/windows/hellolynk-native-human-confirmation-broker.ps1 +216 -0
  94. package/runtime/trae-work-bootstrap.mjs +30 -0
  95. package/runtime/workbench/candidate-bootstrap.mjs +54 -0
  96. package/runtime/workbench/contract.mjs +1314 -0
  97. package/runtime/workbench/hellolynk-workbench-build.json +215 -0
  98. package/runtime/workbench/host-capabilities.mjs +80 -0
  99. package/runtime/workbench/i18n.mjs +1127 -0
  100. package/runtime/workbench/loopback-surface.mjs +740 -0
  101. package/runtime/workbench/mcp-app-bundle.mjs +97 -0
  102. package/runtime/workbench/service.mjs +485 -0
  103. package/runtime/workbench/state.mjs +421 -0
  104. package/runtime/workbench/transports.mjs +171 -0
  105. package/runtime/workbench/ui/app.mjs +6120 -0
  106. package/runtime/workbench/ui/connection-form.mjs +155 -0
  107. package/runtime/workbench/ui/hellolynk-mark.png +0 -0
  108. package/runtime/workbench/ui/http-transport.mjs +247 -0
  109. package/runtime/workbench/ui/icons/arrow-left.svg +3 -0
  110. package/runtime/workbench/ui/icons/close.svg +3 -0
  111. package/runtime/workbench/ui/icons/contacts.svg +3 -0
  112. package/runtime/workbench/ui/icons/inbox.svg +3 -0
  113. package/runtime/workbench/ui/icons/refresh.svg +3 -0
  114. package/runtime/workbench/ui/icons/search.svg +3 -0
  115. package/runtime/workbench/ui/icons/security.svg +3 -0
  116. package/runtime/workbench/ui/icons/settings.svg +3 -0
  117. package/runtime/workbench/ui/index.html +139 -0
  118. package/runtime/workbench/ui/loopback-entry.mjs +26 -0
  119. package/runtime/workbench/ui/mcp-app-client.mjs +151 -0
  120. package/runtime/workbench/ui/mcp-app-entry.mjs +452 -0
  121. package/runtime/workbench/ui/styles.css +686 -0
  122. package/tools/hellolynk-activate-neutral-host-clients.mjs +837 -0
  123. package/tools/hellolynk-install-client-skills.mjs +1211 -0
@@ -0,0 +1,784 @@
1
+ #!/usr/bin/env node
2
+ import { prepareModelInboxInvocation, isModelInboxTool } from "../../../runtime/core/hellolynk-model-inbox.mjs";
3
+ import { nativeTool, optionalTarget, nativeAddressingError, replyHint, conversationGuidance, replyHintSchema, firstSendReceipt, paramText, resultText, sendArgumentKeys, sendRecipient, findContacts, writeFailure, failureText } from "../../../runtime/core/hellolynk-native-addressing-tools.cjs";
4
+ import { withHelloLynkModelNotices } from "../../../runtime/core/hellolynk-model-notices.cjs";
5
+
6
+ import path from "node:path";
7
+ import { rememberNativeReplyRoute } from "../../../runtime/core/hellolynk-native-reply-routes.mjs";
8
+ import { projectInboxReadState } from "../../../runtime/core/hellolynk-inbox-read-state.mjs";
9
+ import readline from "node:readline";
10
+ import { fileURLToPath } from "node:url";
11
+ import packageMetadata from "../package.json" with { type: "json" };
12
+ import {
13
+ createDynamicUiEnvelope, projectActionView, projectContactDetail, projectContactsView, projectPrivateInboxDashboard,
14
+ projectErrorView, projectInboxView, projectOutboxView, projectSelectedMessage,
15
+ projectSelectedMessageMetadata, projectSendForm,
16
+ TRAE_DYNAMIC_UI_CONTRACT,
17
+ } from "./dynamic-ui-contract.mjs";
18
+ import {
19
+ createPrivateUiMeta,
20
+ createTraeUiActionGate,
21
+ } from "./ui-action-gate.mjs";
22
+
23
+ if (packageMetadata.name !== "@hellolynk-ai/trae-work-mcp"
24
+ || typeof packageMetadata.version !== "string" || !/^\d+\.\d+\.\d+$/.test(packageMetadata.version)) {
25
+ throw codeError("trae_work_package_metadata_invalid");
26
+ }
27
+
28
+ export const TRAE_WORK_MCP_PROTOCOL_VERSION = "2025-11-25";
29
+ export const TRAE_WORK_MCP_SUPPORTED_PROTOCOL_VERSIONS = Object.freeze([TRAE_WORK_MCP_PROTOCOL_VERSION, "2025-06-18"]);
30
+ export const TRAE_WORK_MCP_MAX_FRAME_BYTES = 64 * 1024;
31
+
32
+ const READ_ONLY = Object.freeze({ readOnlyHint: true, openWorldHint: false, destructiveHint: false, idempotentHint: true });
33
+ const LOCAL_WRITE = Object.freeze({ readOnlyHint: false, openWorldHint: false, destructiveHint: false, idempotentHint: false });
34
+ const LOCAL_DELETE = Object.freeze({ readOnlyHint: false, openWorldHint: false, destructiveHint: true, idempotentHint: false });
35
+ const REQUEST_WRITE = Object.freeze({ readOnlyHint: false, openWorldHint: true, destructiveHint: false, idempotentHint: false });
36
+ const AUTHORIZED_SEND = Object.freeze({ readOnlyHint: false, openWorldHint: true, destructiveHint: false, idempotentHint: false });
37
+ const COMMON_PUBLIC_ERROR_CODES = new Set([
38
+ "native_addressing_arguments_invalid",
39
+ "native_directory_cursor_invalid", "native_directory_query_invalid", "native_conversation_id_invalid",
40
+ "action_id_invalid", "client_message_id_invalid", "confirmation_required", "contact_display_name_invalid",
41
+ "contact_id_invalid", "contact_recipient_not_found", "host_app_native_broker_unavailable",
42
+ "host_native_inbox_upgrade_required", "host_app_record_not_found", "host_app_state_conflict", "host_app_unavailable",
43
+ "host_app_reply_target_deleted", "host_app_authorization_required",
44
+ "hellolynk_node_22_16_required",
45
+ "host_attachment_ambiguous", "host_attachment_not_found", "host_capability_scope_denied",
46
+ "host_not_running", "host_request_replayed", "host_request_unauthorized", "human_action_status_invalid",
47
+ "invalid_params", "local_message_id_invalid", "local_message_ids_invalid", "message_text_invalid",
48
+ "method_not_found", "neutral_capability_binding_invalid", "neutral_capability_inactive",
49
+ "neutral_capability_owner_mismatch", "neutral_capability_provider_required", "neutral_capability_scopes_mismatch",
50
+ "neutral_capability_version_mismatch", "neutral_host_adapter_closed", "neutral_host_discovery_generation_mismatch",
51
+ "neutral_host_discovery_owner_mismatch", "neutral_host_discovery_stale", "neutral_host_heartbeat_failed",
52
+ "neutral_host_launcher_unavailable", "neutral_host_not_running", "neutral_host_release_superseded",
53
+ "outbox_message_id_invalid", "powershell7_missing", "recipient_invalid",
54
+ "recipient_selection_invalid", "unknown_tool",
55
+ "trae_ui_action_authorization_expired", "trae_ui_action_authorization_invalid",
56
+ "trae_ui_action_authorization_required", "trae_ui_action_scope_mismatch",
57
+ "trae_local_web_adapter_required", "trae_local_web_launch_invalid", "trae_local_web_launch_unavailable", "trae_local_web_payload_invalid",
58
+ ]);
59
+
60
+ const EMPTY_SCHEMA = Object.freeze({ type: "object", additionalProperties: false, properties: Object.freeze({}) });
61
+ const CONFIRM = Object.freeze({ type: "boolean", const: true, description: "必须来自人类在当前 UI 中的明确操作。" });
62
+ const ID_SCHEMA = Object.freeze({ type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$" });
63
+ const HISTORY_SCHEMA = Object.freeze({ type: "object", additionalProperties: false, properties: Object.freeze({
64
+ before: Object.freeze({ ...ID_SCHEMA, description: paramText.before }),
65
+ }) });
66
+ const MESSAGE_TEXT = Object.freeze({ type: "string", minLength: 1, maxLength: 20000, description: paramText.text });
67
+ // JSON Schema uses an unflagged ECMA-262 pattern; keep the portable anchors.
68
+ const WRITE_ID = Object.freeze({ type: "string", minLength: 3, maxLength: 128, pattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{2,127}$" });
69
+ const WRITE_ID_PATTERN = new RegExp(WRITE_ID.pattern);
70
+ const CLIENT_MESSAGE_ID = Object.freeze({ ...WRITE_ID, examples: ["msg-a1b2c3-01"], description: paramText.clientMessageId });
71
+
72
+ const HELLOLYNK_ID_SCHEMA = Object.freeze({ type: "string", pattern: "^hellolynk:(intl|cn):[a-z0-9][a-z0-9._-]{2,179}$" });
73
+ const UI_ACTION_TOKEN_SCHEMA = Object.freeze({ type: "string", pattern: "^hellolynkui_[A-Za-z0-9_-]{43}$" });
74
+ const DEFAULT_UI_ACTION_GATE = createTraeUiActionGate();
75
+ const NOTICES_SCHEMA = Object.freeze({ type: "array", items: { type: "string" } });
76
+ const OUTPUT_SCHEMA = Object.freeze({
77
+ type: "object", additionalProperties: false,
78
+ required: Object.freeze(["contract", "version", "renderer", "view", "state", "exposure", "component", "data"]),
79
+ properties: Object.freeze({
80
+ contract: Object.freeze({ type: "string", const: TRAE_DYNAMIC_UI_CONTRACT }),
81
+ version: Object.freeze({ type: "integer", const: 1 }), renderer: Object.freeze({ type: "string", const: "HelloLynkTraeLocalWebV18" }),
82
+ view: Object.freeze({ type: "string" }), state: Object.freeze({ type: "string" }), exposure: Object.freeze({ type: "string" }), component: Object.freeze({ type: "string", enum: ["messenger-inbox", "message-form", "status-card", "message-confirm"] }),
83
+ data: Object.freeze({ type: "object" }), notices: NOTICES_SCHEMA,
84
+ }),
85
+ });
86
+ const PENDING_MESSAGE_SCHEMA = Object.freeze({
87
+ type: "object", additionalProperties: false,
88
+ required: ["localMessageId", "conversationKey", "localDelegateId", "peerDelegateId", "contactId", "displayName", "unread", "createdAt", "wakeEligible"],
89
+ properties: {
90
+ localMessageId: ID_SCHEMA, conversationKey: ID_SCHEMA, localDelegateId: ID_SCHEMA,
91
+ peerDelegateId: ID_SCHEMA, contactId: { anyOf: [ID_SCHEMA, { type: "null" }] }, displayName: { type: "string" }, peerInstanceDisplayName: { type: "string" },
92
+ unread: { type: "boolean" }, createdAt: { type: "string" }, wakeEligible: { type: "boolean", const: true },
93
+ },
94
+ });
95
+ const CLAIM_ID_SCHEMA = Object.freeze({ type: "string", pattern: "^inclaim_[a-f0-9]{48}$" });
96
+ const PENDING_OUTPUT_SCHEMAS = Object.freeze({
97
+ hellolynk_list_pending_messages: {
98
+ type: "object", additionalProperties: false, required: ["messages"],
99
+ properties: { messages: { type: "array", maxItems: 1000, items: PENDING_MESSAGE_SCHEMA }, notices: NOTICES_SCHEMA },
100
+ },
101
+ hellolynk_claim_message: { type: "object", oneOf: [{
102
+ type: "object", additionalProperties: false, required: ["claimed", "claimId", "message"],
103
+ properties: { claimed: { type: "boolean" }, claimId: { type: ["string", "null"] }, message: { type: ["object", "null"] }, notices: NOTICES_SCHEMA },
104
+ oneOf: [
105
+ { properties: { claimed: { const: false }, claimId: { type: "null" }, message: { type: "null" } } },
106
+ { properties: { claimed: { const: true }, claimId: CLAIM_ID_SCHEMA, message: {
107
+ type: "object", additionalProperties: false,
108
+ required: [...PENDING_MESSAGE_SCHEMA.required, "peerPrincipalId", "messageType", "text", "source", "readDoesNotMarkRead"],
109
+ properties: {
110
+ ...PENDING_MESSAGE_SCHEMA.properties, peerPrincipalId: ID_SCHEMA, messageType: ID_SCHEMA,
111
+ text: { type: "string", minLength: 1 }, source: { type: "string", const: "hellolynk_external_message" },
112
+ readDoesNotMarkRead: { type: "boolean" }, humanReadAutomatically: { type: "boolean", const: true }, reply: replyHintSchema,
113
+ },
114
+ } } },
115
+ ],
116
+ }, { type: "object", additionalProperties: false, required: ["claimed", "messages", "expiredCount", "notice"],
117
+ properties: { claimed: { type: "boolean" }, messages: { type: "array", items: { type: "object" } }, expiredCount: { type: "integer", minimum: 0 }, notice: { type: ["string", "null"] }, notices: NOTICES_SCHEMA } }] },
118
+
119
+ });
120
+ const tool = (name, inputSchema, annotations, text = {}) => Object.freeze({
121
+ name, ...text, inputSchema, outputSchema: PENDING_OUTPUT_SCHEMAS[name] || OUTPUT_SCHEMA, annotations,
122
+ });
123
+
124
+ // nativeTool adds the titles and descriptions every client shares.
125
+ export const TRAE_WORK_INTERNAL_TOOLS = Object.freeze([
126
+ tool("hellolynk_open_inbox", EMPTY_SCHEMA, READ_ONLY),
127
+ tool("hellolynk_host_status", EMPTY_SCHEMA, READ_ONLY),
128
+ tool("hellolynk_get_unread_count", EMPTY_SCHEMA, READ_ONLY),
129
+ tool("hellolynk_list_authorized_contacts", EMPTY_SCHEMA, READ_ONLY),
130
+ tool("hellolynk_list_authorized_inbox_messages", HISTORY_SCHEMA, READ_ONLY),
131
+ tool("hellolynk_read_authorized_inbox_message", strictConversationSchema("localMessageId"), READ_ONLY),
132
+ tool("hellolynk_list_authorized_contact_requests", EMPTY_SCHEMA, READ_ONLY),
133
+ tool("hellolynk_read_authorized_contact_request", strictConversationSchema("localRecordId"), READ_ONLY),
134
+ tool("hellolynk_send_authorized_message", {
135
+ type: "object", additionalProperties: false, required: ["clientMessageId", "text"], properties: {
136
+ clientMessageId: CLIENT_MESSAGE_ID, contactId: ID_SCHEMA, recipientDelegateId: ID_SCHEMA, text: MESSAGE_TEXT,
137
+ },
138
+ }, AUTHORIZED_SEND),
139
+ tool("hellolynk_reply_authorized_message", {
140
+ type: "object", additionalProperties: false, required: ["localMessageId", "conversationKey", "clientMessageId", "text"], properties: {
141
+ localMessageId: { ...ID_SCHEMA, description: paramText.localMessageId }, conversationKey: { ...ID_SCHEMA, description: paramText.conversationKey },
142
+ clientMessageId: CLIENT_MESSAGE_ID, text: MESSAGE_TEXT,
143
+ },
144
+ }, AUTHORIZED_SEND),
145
+ tool("hellolynk_render_inbox", EMPTY_SCHEMA, READ_ONLY, { title: "显示 HelloLynk 消息", description: "显示本机消息列表,不含正文。" }),
146
+ tool("hellolynk_read_inbox_message", {
147
+ type: "object", additionalProperties: false, required: ["localMessageId"], properties: { localMessageId: ID_SCHEMA },
148
+ }, READ_ONLY, { title: "查看一条HelloLynk消息", description: "返回所选消息的元数据,不含正文。" }),
149
+ tool("hellolynk_mark_inbox_message_read", {
150
+ type: "object", additionalProperties: false, required: ["localMessageId", "confirm", "uiActionToken"],
151
+ properties: { localMessageId: ID_SCHEMA, confirm: CONFIRM, uiActionToken: UI_ACTION_TOKEN_SCHEMA },
152
+ }, LOCAL_WRITE, { title: "把一条HelloLynk消息标为已读", description: "把所选消息标为已读,需要本人在界面确认。" }),
153
+ tool("hellolynk_delete_read_inbox_messages", {
154
+ type: "object", additionalProperties: false, required: ["localMessageIds", "confirm", "uiActionToken"],
155
+ properties: { localMessageIds: { type: "array", minItems: 1, maxItems: 500, uniqueItems: true, items: ID_SCHEMA }, confirm: CONFIRM, uiActionToken: UI_ACTION_TOKEN_SCHEMA },
156
+ }, LOCAL_DELETE, { title: "删除已读HelloLynk消息", description: "删除所选的已读消息,需要本人在界面确认。" }),
157
+ tool("hellolynk_render_contacts", EMPTY_SCHEMA, READ_ONLY, { title: "显示HelloLynk联系人", description: "显示联系人列表。" }),
158
+ tool("hellolynk_get_contact_detail", {
159
+ type: "object", additionalProperties: false, required: ["contactId"], properties: { contactId: ID_SCHEMA },
160
+ }, READ_ONLY, { title: "查看一个HelloLynk联系人", description: "返回所选联系人的详情。" }),
161
+ tool("hellolynk_save_contact", {
162
+ type: "object", additionalProperties: false, required: ["hellolynkId", "displayName", "confirm", "uiActionToken"],
163
+ properties: { contactId: ID_SCHEMA, hellolynkId: HELLOLYNK_ID_SCHEMA, displayName: { type: "string", minLength: 1, maxLength: 80 }, confirm: CONFIRM, uiActionToken: UI_ACTION_TOKEN_SCHEMA },
164
+ }, LOCAL_WRITE, { title: "保存HelloLynk联系人", description: "保存联系人,需要本人在界面确认。" }),
165
+ tool("hellolynk_delete_contact", {
166
+ type: "object", additionalProperties: false, required: ["contactId", "confirm", "uiActionToken"],
167
+ properties: { contactId: ID_SCHEMA, confirm: CONFIRM, uiActionToken: UI_ACTION_TOKEN_SCHEMA },
168
+ }, LOCAL_DELETE, { title: "删除HelloLynk联系人", description: "删除联系人,需要本人在界面确认。" }),
169
+ tool("hellolynk_render_outbox", EMPTY_SCHEMA, READ_ONLY, { title: "显示 HelloLynk 发出记录", description: "显示发出记录,不含正文。" }),
170
+ tool("hellolynk_retry_outbox_message", {
171
+ type: "object", additionalProperties: false, required: ["clientMessageId", "confirm", "uiActionToken"],
172
+ properties: { clientMessageId: ID_SCHEMA, confirm: CONFIRM, uiActionToken: UI_ACTION_TOKEN_SCHEMA },
173
+ }, REQUEST_WRITE, { title: "重试一条发件记录", description: "重试一条发出失败的消息,需要本人在界面确认。" }),
174
+ tool("hellolynk_render_send_form", EMPTY_SCHEMA, READ_ONLY, { title: "显示HelloLynk发信表单", description: "显示发信表单。" }),
175
+ tool("hellolynk_request_human_send", {
176
+ type: "object", additionalProperties: false, required: ["clientMessageId", "text", "confirm", "uiActionToken"],
177
+ oneOf: [
178
+ { required: ["contactId"], not: { required: ["recipientHelloLynkId"] } },
179
+ { required: ["recipientHelloLynkId"], not: { required: ["contactId"] } },
180
+ ],
181
+ properties: {
182
+ clientMessageId: ID_SCHEMA, contactId: ID_SCHEMA, recipientHelloLynkId: HELLOLYNK_ID_SCHEMA,
183
+ text: MESSAGE_TEXT, confirm: CONFIRM,
184
+ uiActionToken: UI_ACTION_TOKEN_SCHEMA,
185
+ },
186
+ }, REQUEST_WRITE, { title: "请求人类确认发送HelloLynk", description: "请本人在 HelloLynk 里确认发送;本工具不直接发送。" }),
187
+ tool("hellolynk_get_human_send_status", {
188
+ type: "object", additionalProperties: false, required: ["actionId"], properties: { actionId: ID_SCHEMA },
189
+ }, READ_ONLY, { title: "查看人类确认请求状态", description: "查看确认请求的状态。" }),
190
+ ].map(nativeTool));
191
+
192
+ // Public model tools expose projected metadata and explicitly authorized content.
193
+ // Host checks still control reads, claims, and sends; human UI data stays private.
194
+ export const TRAE_WORK_TOOLS = Object.freeze([
195
+ nativeTool(tool("hellolynk_list_pending_messages", EMPTY_SCHEMA, { readOnlyHint: false, openWorldHint: false, destructiveHint: false, idempotentHint: true })),
196
+ nativeTool(tool("hellolynk_claim_message", pendingSchema(), { readOnlyHint: false, openWorldHint: false, destructiveHint: false, idempotentHint: false })),
197
+ TRAE_WORK_INTERNAL_TOOLS[0], TRAE_WORK_INTERNAL_TOOLS[1],
198
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_get_unread_count"),
199
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_list_authorized_contacts"),
200
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_list_authorized_inbox_messages"),
201
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_read_authorized_inbox_message"),
202
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_list_authorized_contact_requests"),
203
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_read_authorized_contact_request"),
204
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_send_authorized_message"),
205
+ TRAE_WORK_INTERNAL_TOOLS.find((item) => item.name === "hellolynk_reply_authorized_message"),
206
+ ]);
207
+
208
+ export function createRestrictedStdioMcpSurface(options = {}) {
209
+ const publicErrorCodes = new Set([...COMMON_PUBLIC_ERROR_CODES, ...(options.publicErrorCodes || [])]);
210
+ const serverInfo = Object.freeze({
211
+ name: safeSurfaceText(options.name, "restricted_surface_name_invalid"),
212
+ title: safeSurfaceText(options.title, "restricted_surface_title_invalid"),
213
+ version: safeSurfaceText(options.version || packageMetadata.version, "restricted_surface_version_invalid"),
214
+ });
215
+ if (!Array.isArray(options.tools) || options.tools.length < 1 || options.tools.length > 32) throw codeError("restricted_surface_tools_invalid");
216
+ return Object.freeze({
217
+ protocolVersion: options.protocolVersion || TRAE_WORK_MCP_PROTOCOL_VERSION,
218
+ supportedProtocolVersions: Object.freeze(options.supportedProtocolVersions || [...TRAE_WORK_MCP_SUPPORTED_PROTOCOL_VERSIONS]),
219
+ serverInfo, instructions: safeSurfaceText(options.instructions, "restricted_surface_instructions_invalid", 1_000),
220
+ tools: Object.freeze([...options.tools]), isPublicErrorCode: (code) => publicErrorCodes.has(code),
221
+ });
222
+ }
223
+
224
+ export const TRAE_WORK_MCP_SURFACE = createRestrictedStdioMcpSurface({
225
+ name: "hellolynk-trae-work", title: "HelloLynk 通讯", version: packageMetadata.version, tools: TRAE_WORK_TOOLS,
226
+ instructions: conversationGuidance + "“打开 HelloLynk”调用 hellolynk_open_inbox 一次,再用 OpenPreview 打开返回地址。",
227
+ publicErrorCodes: [
228
+ "neutral_trae_work_binding_configuration_required", "neutral_trae_work_binding_invalid",
229
+ "neutral_trae_work_capability_ambiguous", "neutral_trae_work_capability_binding_mismatch",
230
+ "neutral_trae_work_capability_missing",
231
+ ],
232
+ });
233
+
234
+ export async function runTraeWorkStdioMcpServer(options = {}) {
235
+ const adapter = options.adapter; if (!adapter) throw codeError("neutral_host_adapter_required");
236
+ const uiActionGate = options.uiActionGate || createTraeUiActionGate();
237
+ const input = options.input || process.stdin; const output = options.output || process.stdout;
238
+ const rl = readline.createInterface({ input, crlfDelay: Number.POSITIVE_INFINITY });
239
+ const stop = () => rl.close(); process.once("SIGINT", stop); process.once("SIGTERM", stop);
240
+ try {
241
+ for await (const line of rl) {
242
+ if (!line.trim()) continue;
243
+ if (Buffer.byteLength(line, "utf8") > TRAE_WORK_MCP_MAX_FRAME_BYTES) { write(output, jsonRpcError(null, -32600, "frame_too_large")); continue; }
244
+ await handleTraeWorkMcpLine(line, output, {
245
+ adapter, surface: options.surface || TRAE_WORK_MCP_SURFACE, uiActionGate,
246
+ openInbox: options.openInbox,
247
+ privateUiEnabled: options.privateUiEnabled === true,
248
+ });
249
+ }
250
+ } finally { process.removeListener("SIGINT", stop); process.removeListener("SIGTERM", stop); }
251
+ }
252
+ export async function handleTraeWorkMcpLine(line, output, runtime) {
253
+ let message; try { message = JSON.parse(line); } catch { write(output, jsonRpcError(null, -32700, "parse_error")); return; }
254
+ const response = await handleTraeWorkJsonRpcMessage(message, runtime); if (response) write(output, response);
255
+ }
256
+ export async function handleTraeWorkJsonRpcMessage(message, runtime = {}) {
257
+ const surface = runtime.surface || TRAE_WORK_MCP_SURFACE;
258
+ const id = isPlainObject(message) && Object.hasOwn(message, "id") ? message.id : null;
259
+ if (!isPlainObject(message) || !exactKeysAllowed(message, ["id", "jsonrpc", "method", "params"])
260
+ || message.jsonrpc !== "2.0" || typeof message.method !== "string"
261
+ || (Object.hasOwn(message, "id") && !validId(message.id))) return jsonRpcError(id, -32600, "invalid_request");
262
+ if (!Object.hasOwn(message, "id")) return null;
263
+ try { return { jsonrpc: "2.0", id: message.id, result: await dispatchTraeWorkMcp(message.method, message.params, { ...runtime, surface }) }; }
264
+ catch (error) {
265
+ const code = toRestrictedStdioPublicErrorCode(error, surface);
266
+ return jsonRpcError(message.id, code === "method_not_found" ? -32601 : code === "invalid_params" ? -32602 : -32603, code);
267
+ }
268
+ }
269
+ export async function dispatchTraeWorkMcp(method, params, runtime = {}) {
270
+ const run = () => dispatchWithoutModelNotices(method, params, runtime);
271
+ return method === "tools/call" ? withHelloLynkModelNotices(run) : run();
272
+ }
273
+
274
+ async function dispatchWithoutModelNotices(method, params, runtime = {}) {
275
+ const surface = runtime.surface || TRAE_WORK_MCP_SURFACE;
276
+ if (method === "initialize") return {
277
+ protocolVersion: negotiateInitializeProtocolVersion(params, surface), capabilities: { tools: { listChanged: false } },
278
+ serverInfo: surface.serverInfo, instructions: surface.instructions,
279
+ };
280
+ if (method === "ping") { assertEmptyParams(params); return {}; }
281
+ if (method === "tools/list") { assertEmptyParams(params); return { tools: surface.tools }; }
282
+ if (method === "tools/call") return callTool(
283
+ params,
284
+ runtime.adapter,
285
+ surface,
286
+ runtime.uiActionGate || DEFAULT_UI_ACTION_GATE,
287
+ runtime.openInbox,
288
+ runtime.privateUiEnabled === true,
289
+ );
290
+ throw codeError("method_not_found");
291
+ }
292
+
293
+ async function callTool(params, adapter, surface, uiActionGate, openInbox, privateUiEnabled) {
294
+ if (!isModelInboxTool(params?.name)) return callToolWithoutInboxScope(params, adapter, surface, uiActionGate, openInbox, privateUiEnabled);
295
+ try {
296
+ const invocation = await prepareModelInboxInvocation(adapter, { name: params.name, args: params.arguments || {}, readEnvelope: true, detailTarget: "data",
297
+ caller: params._meta?.chatSessionId ? { threadId: params._meta.chatSessionId } : undefined });
298
+ return invocation.finish(await callToolWithoutInboxScope({ ...params, arguments: invocation.args },
299
+ invocation.adapter, surface, uiActionGate, openInbox, privateUiEnabled));
300
+ } catch (error) {
301
+ if (params.name === 'hellolynk_reply_authorized_message') return authorizedMessagingFailure(error, surface,
302
+ { phase: error?.code === 'model_inbox_scope_invalid' ? 'input_validation' : 'preparation' });
303
+ return failure(error, surface);
304
+ }
305
+ }
306
+
307
+ async function callToolWithoutInboxScope(params, adapter, surface, uiActionGate, openInbox, privateUiEnabled) {
308
+ if (!isPlainObject(params) || !exactKeysAllowed(params, ["_meta", "arguments", "name"])
309
+ || typeof params.name !== "string"
310
+ || (Object.hasOwn(params, "_meta") && !isPlainObject(params._meta))
311
+ || (Object.hasOwn(params, "arguments") && !isPlainObject(params.arguments))) throw codeError("invalid_params");
312
+ // MCP defines CallToolRequestParams.arguments as optional. TRAE omits it for
313
+ // parameterless tools and may add protocol _meta; neither changes tool input.
314
+ const args = Object.hasOwn(params, "arguments") ? params.arguments : {};
315
+ const writeAttempt = ["hellolynk_send_authorized_message", "hellolynk_reply_authorized_message"].includes(params.name) ? { phase: "input_validation" } : null;
316
+ try {
317
+ if (!surface.tools.some((item) => item.name === params.name) && privateUiEnabled !== true) {
318
+ throw codeError("unknown_tool");
319
+ }
320
+ if (params.name === "hellolynk_open_inbox") {
321
+ assertArguments(args, []);
322
+ if (typeof openInbox !== "function") throw codeError("trae_local_web_launch_unavailable");
323
+ const launched = await openInbox();
324
+ if (launched?.launched !== true || !/^http:\/\/127\.0\.0\.1:\d{1,5}\/inbox$/.test(String(launched.cleanUrl || ""))) {
325
+ throw codeError("trae_local_web_launch_invalid");
326
+ }
327
+ return success(resultText.ready(launched.cleanUrl), createDynamicUiEnvelope("action", "ready", {
328
+ message: "HelloLynk 已就绪。",
329
+ actionId: "trae-inbox-browser-open",
330
+ state: "ready",
331
+ createdAtMs: Date.now(),
332
+ expiresAtMs: Date.now() + 120_000,
333
+ url: launched.cleanUrl,
334
+ }, { exposure: "aggregate-only" }));
335
+ }
336
+ const pending = await pendingTool(params.name, args, adapter);
337
+ if (pending) return pending;
338
+ if (params.name === "hellolynk_host_status") {
339
+ assertArguments(args, []); const status = projectStatus(await adapter.hostStatus());
340
+ return success("本机 HelloLynk 已连接。", createDynamicUiEnvelope("status", "ready", status, { exposure: "aggregate-only" }));
341
+ }
342
+ if (params.name === "hellolynk_get_unread_count") {
343
+ assertArguments(args, []);
344
+ const unread = (await adapter.readUnreadCount())?.unreadCount;
345
+ if (!Number.isSafeInteger(unread) || unread < 0) throw codeError("host_app_response_invalid");
346
+ return success(resultText.unread(unread), createDynamicUiEnvelope("status", "ready", { counts: { unread } }, { exposure: "aggregate-only" }));
347
+ }
348
+ if (params.name === "hellolynk_list_authorized_contacts") {
349
+ const result = await findContacts(args, (query) => adapter.listAuthorizedContacts(query));
350
+ const contacts = (Array.isArray(result?.contacts) ? result.contacts : []).map((item) => ({
351
+ contactId: safeId(item?.contactId, "contact_id_invalid", 198),
352
+ displayName: safeDisplayName(item?.displayName, "contact_display_name_invalid"),
353
+ recipientDelegateId: safeId(item?.recipientDelegateId, "recipient_delegate_id_invalid", 198),
354
+ instanceDisplayName: safeDisplayName(item?.instanceDisplayName, "instance_display_name_invalid"),
355
+ }));
356
+ return success(resultText.contacts(contacts.length), createDynamicUiEnvelope("status", "ready", {
357
+ authorizedContacts: contacts, ...(result.nativeConversations ? { nativeConversations: result.nativeConversations } : {}),
358
+ }, { exposure: "aggregate-only" }));
359
+ }
360
+ if (params.name === "hellolynk_list_authorized_inbox_messages") {
361
+ const older = isPlainObject(args) && Object.hasOwn(args, "before");
362
+ assertArguments(args, older ? ["before"] : []);
363
+ const result = await adapter.listAuthorizedInboxMessages(older ? { before: safeId(args.before, "local_message_id_invalid", 198) } : {});
364
+ const messages = (Array.isArray(result?.messages) ? result.messages : []).map((item) => ({
365
+ localMessageId: safeId(item?.localMessageId, "local_message_id_invalid", 198),
366
+ conversationKey: safeId(item?.conversationKey, "conversation_key_invalid", 240),
367
+ localDelegateId: safeId(item?.localDelegateId, "local_delegate_id_invalid", 198),
368
+ peerDelegateId: safeId(item?.peerDelegateId, "peer_delegate_id_invalid", 198),
369
+ contactId: item?.contactId === null ? null : safeId(item?.contactId, "contact_id_invalid", 198),
370
+ displayName: safeDisplayName(item?.displayName, "contact_display_name_invalid"),
371
+ ...(item.peerInstanceDisplayName == null ? {} : { peerInstanceDisplayName: safeDisplayName(item.peerInstanceDisplayName, "instance_display_name_invalid") }),
372
+ unread: item?.unread === true,
373
+ createdAt: safeText(item?.createdAt || "", "timestamp_invalid", 80), ...replyHint(item),
374
+ }));
375
+ return success(resultText.inbox(messages.length, older), createDynamicUiEnvelope("status", "ready", {
376
+ authorizedMessages: messages,
377
+ }, { exposure: "metadata-only" }));
378
+ }
379
+ if (params.name === "hellolynk_read_authorized_inbox_message") {
380
+ assertArguments(args, ["localMessageId", "conversationKey"]);
381
+ const result = await adapter.readAuthorizedInboxMessage(safeId(args.localMessageId, "local_message_id_invalid", 198), safeId(args.conversationKey, "conversation_key_invalid", 240));
382
+ return success("已读取来信。", createDynamicUiEnvelope("message", "ready", { message: projectExternalMessage(result?.message ?? result) }, { exposure: "selected-content" }));
383
+ }
384
+ if (params.name === "hellolynk_list_authorized_contact_requests") {
385
+ assertArguments(args, []); const result = await adapter.listAuthorizedContactRequests();
386
+ const requests = (result?.requests || []).map(projectRequestMetadata);
387
+ return success(`联系申请 ${requests.length} 条。`, createDynamicUiEnvelope("status", "ready", { authorizedContactRequests: requests }, { exposure: "metadata-only" }));
388
+ }
389
+ if (params.name === "hellolynk_read_authorized_contact_request") {
390
+ assertArguments(args, ["localRecordId", "conversationKey"]);
391
+ const result = await adapter.readAuthorizedContactRequest(safeId(args.localRecordId, "local_record_id_invalid", 198), safeId(args.conversationKey, "conversation_key_invalid", 240));
392
+ return success("已读取联系申请。", createDynamicUiEnvelope("message", "ready", { request: projectExternalRequest(result?.request ?? result) }, { exposure: "selected-content" }));
393
+ }
394
+ if (params.name === "hellolynk_send_authorized_message") {
395
+ assertArguments(args, sendArgumentKeys(args));
396
+ if (Object.values(args).some((value) => typeof value !== "string")) throw codeError("invalid_params");
397
+ if (!Object.hasOwn(args, "contact")) {
398
+ safeId(args.contactId, "contact_id_invalid", 198); safeId(args.recipientDelegateId, "recipient_delegate_id_invalid", 198);
399
+ }
400
+ optionalTarget(args);
401
+ writeAttempt.clientMessageId = authorizedWriteId(args.clientMessageId);
402
+ const text = safeText(args.text, "message_text_invalid", 20_000, 20 * 1024, true);
403
+ try {
404
+ writeAttempt.phase = "preparation";
405
+ const recipient = await sendRecipient(args, (query) => adapter.listAuthorizedContacts(query));
406
+ const input = {
407
+ ...optionalTarget(args),
408
+ clientMessageId: writeAttempt.clientMessageId,
409
+ contactId: safeId(recipient.contactId, "contact_id_invalid", 198),
410
+ recipientDelegateId: safeId(recipient.recipientDelegateId, "recipient_delegate_id_invalid", 198),
411
+ text,
412
+ };
413
+ const originRegistered = await rememberToolOrigin(adapter, params._meta, input);
414
+ writeAttempt.phase = "adapter_call";
415
+ const result = await adapter.sendAuthorizedMessage(input);
416
+ writeAttempt.phase = "response_validation";
417
+ if (result?.accepted !== true || result.ok === false || result.authorization !== "contact_delegate_allowlist"
418
+ || result.clientMessageId !== input.clientMessageId || result.contactId !== input.contactId
419
+ || !["accepted", "delivered_to_live_connection", "acknowledged_by_recipient_local_store"].includes(result.status)) throw codeError("host_app_response_invalid");
420
+ const receipt = firstSendReceipt(input, result.status);
421
+ return success(receipt.text, createDynamicUiEnvelope("action", "ready", {
422
+ accepted: true, operation: "authorized_send", contactId: input.contactId, clientMessageId: input.clientMessageId,
423
+ recipientDelegateId: input.recipientDelegateId, originRegistered,
424
+ destination: receipt.destination,
425
+ status: result.status,
426
+ }, { exposure: "aggregate-only" }));
427
+ } catch (error) {
428
+ return authorizedMessagingFailure(error, surface, writeAttempt);
429
+ }
430
+ }
431
+ if (params.name === "hellolynk_reply_authorized_message") {
432
+ assertArguments(args, ["localMessageId", "conversationKey", "clientMessageId", "text"]);
433
+ if ([args.localMessageId, args.conversationKey, args.clientMessageId, args.text].some((value) => typeof value !== "string")) throw codeError("invalid_params");
434
+ writeAttempt.clientMessageId = authorizedWriteId(args.clientMessageId);
435
+ const input = {
436
+ localMessageId: safeId(args.localMessageId, "local_message_id_invalid", 198),
437
+ conversationKey: safeId(args.conversationKey, "conversation_key_invalid", 240),
438
+ clientMessageId: writeAttempt.clientMessageId,
439
+ text: safeText(args.text, "message_text_invalid", 20_000, 20 * 1024, true),
440
+ };
441
+ try {
442
+ writeAttempt.phase = "preparation";
443
+ await adapter.assertModelInboxReply(input);
444
+ const originRegistered = await rememberToolOrigin(adapter, params._meta, input);
445
+ writeAttempt.phase = "adapter_call";
446
+ const result = await adapter.replyAuthorizedMessage(input);
447
+ writeAttempt.phase = "response_validation";
448
+ if (result?.accepted !== true || result.ok === false || result.authorization !== "contact_delegate_allowlist"
449
+ || result.clientMessageId !== input.clientMessageId
450
+ || !["accepted", "delivered_to_live_connection", "acknowledged_by_recipient_local_store"].includes(result.status)) throw codeError("host_app_response_invalid");
451
+ const status = safeText(result.status, "human_action_status_invalid", 80);
452
+ return success(resultText.sent(status, input.clientMessageId), createDynamicUiEnvelope("action", "ready", {
453
+ accepted: true, operation: "authorized_reply", localMessageId: input.localMessageId,
454
+ clientMessageId: input.clientMessageId, originRegistered,
455
+ contactId: safeId(result.contactId, "contact_id_invalid", 198),
456
+ status,
457
+ }, { exposure: "aggregate-only" }));
458
+ } catch (error) {
459
+ return authorizedMessagingFailure(error, surface, writeAttempt);
460
+ }
461
+ }
462
+ if (params.name === "hellolynk_render_inbox") {
463
+ assertArguments(args, []); const [raw, contacts, requests, outbox] = await Promise.all([
464
+ adapter.readInbox(), adapter.listContacts(), adapter.listContactRequests(), adapter.readOutbox(),
465
+ ]); const envelope = projectInboxView(raw);
466
+ const allIds = raw.messages.map((item) => safeId(item.localMessageId, "local_message_id_invalid", 198));
467
+ const readIds = raw.messages.filter((item) => item.unread === false)
468
+ .map((item) => safeId(item.localMessageId, "local_message_id_invalid", 198));
469
+ const grants = privateUiEnabled ? [
470
+ ...(allIds.length ? [uiActionGate.issue({ toolName: "hellolynk_mark_inbox_message_read", mode: "subset", allowedIds: allIds })] : []),
471
+ ...(readIds.length ? [uiActionGate.issue({ toolName: "hellolynk_delete_read_inbox_messages", mode: "subset", allowedIds: readIds })] : []),
472
+ ...(outbox.messages.filter((item) => item.retryAllowed === true).length ? [uiActionGate.issue({ toolName: "hellolynk_retry_outbox_message", mode: "subset", allowedIds: outbox.messages.filter((item) => item.retryAllowed === true).map((item) => safeId(item.clientMessageId, "outbox_message_id_invalid", 198)) })] : []),
473
+ ...(contacts.contacts.length ? [uiActionGate.issue({ toolName: "hellolynk_save_contact", mode: "subset", allowedIds: contacts.contacts.map((item) => safeId(item.contactId, "contact_id_invalid", 198)) }), uiActionGate.issue({ toolName: "hellolynk_delete_contact", mode: "subset", allowedIds: contacts.contacts.map((item) => safeId(item.contactId, "contact_id_invalid", 198)) })] : []),
474
+ uiActionGate.issue({ toolName: "hellolynk_save_contact", mode: "new-contact", allowedIds: [] }),
475
+ uiActionGate.issue({ toolName: "hellolynk_request_human_send", mode: "send-form", allowedIds: contacts.contacts.map((item) => safeId(item.contactId, "contact_id_invalid", 198)) }),
476
+ ] : [];
477
+ return success("HelloLynk message metadata is available to the installed TRAE extension.", envelope, privateUiEnabled
478
+ ? createPrivateUiMeta(projectPrivateInboxDashboard(raw, contacts, requests, outbox), grants)
479
+ : null);
480
+ }
481
+ if (params.name === "hellolynk_read_inbox_message") {
482
+ assertArguments(args, ["localMessageId"]); const id = safeId(args.localMessageId, "local_message_id_invalid", 198);
483
+ const raw = await adapter.readInbox();
484
+ const modelEnvelope = projectSelectedMessageMetadata(raw, id);
485
+ let privateMeta = null;
486
+ if (privateUiEnabled) {
487
+ const privateEnvelope = projectSelectedMessage(raw, id);
488
+ const selected = raw.messages.find((item) => item.localMessageId === id);
489
+ const toolName = selected.unread === true ? "hellolynk_mark_inbox_message_read" : "hellolynk_delete_read_inbox_messages";
490
+ const grant = uiActionGate.issue({ toolName, mode: "exact", allowedIds: [id] });
491
+ privateMeta = createPrivateUiMeta(privateEnvelope, [grant]);
492
+ }
493
+ return success(
494
+ "One user-selected message is available only to the private HelloLynk TRAE extension bridge.",
495
+ modelEnvelope,
496
+ privateMeta,
497
+ );
498
+ }
499
+ if (params.name === "hellolynk_mark_inbox_message_read") {
500
+ assertArguments(args, ["confirm", "localMessageId", "uiActionToken"]); assertConfirmed(args.confirm);
501
+ const id = safeId(args.localMessageId, "local_message_id_invalid", 198);
502
+ uiActionGate.consume(args.uiActionToken, { toolName: params.name, targetIds: [id] });
503
+ const raw = await adapter.markInboxMessageRead(id);
504
+ return success("The selected local message was marked read.", localAction("message_marked_read", { localMessageId: safeId(raw.localMessageId, "local_message_id_invalid", 198), unread: raw.unread }));
505
+ }
506
+ if (params.name === "hellolynk_delete_read_inbox_messages") {
507
+ assertArguments(args, ["confirm", "localMessageIds", "uiActionToken"]); assertConfirmed(args.confirm);
508
+ const ids = safeIdArray(args.localMessageIds, "local_message_ids_invalid", 500);
509
+ uiActionGate.consume(args.uiActionToken, { toolName: params.name, targetIds: ids });
510
+ const raw = await adapter.deleteReadInboxMessages(ids);
511
+ return success("The Host deleted only the selected local read messages.", localAction("read_messages_deleted", { deletedCount: safeInteger(raw.deletedCount, "local_message_ids_invalid") }));
512
+ }
513
+ if (params.name === "hellolynk_render_contacts") {
514
+ assertArguments(args, []); const [contacts, requests] = await Promise.all([adapter.listContacts(), adapter.listContactRequests()]);
515
+ const envelope = projectContactsView(contacts, requests);
516
+ const privateMeta = privateUiEnabled ? createPrivateUiMeta(envelope, [
517
+ uiActionGate.issue({ toolName: "hellolynk_save_contact", mode: "new-contact", allowedIds: [] }),
518
+ ]) : null;
519
+ return success("Contact metadata and model-safe request counts are available to the installed HelloLynk TRAE extension.", envelope, privateMeta);
520
+ }
521
+ if (params.name === "hellolynk_get_contact_detail") {
522
+ assertArguments(args, ["contactId"]); const id = safeId(args.contactId, "contact_id_invalid", 198);
523
+ const envelope = projectContactDetail(await adapter.listContacts(), id);
524
+ const grants = privateUiEnabled ? [
525
+ uiActionGate.issue({ toolName: "hellolynk_save_contact", mode: "exact", allowedIds: [id] }),
526
+ uiActionGate.issue({ toolName: "hellolynk_delete_contact", mode: "exact", allowedIds: [id] }),
527
+ ] : [];
528
+ return success("One user-selected contact is available to the private HelloLynk TRAE extension bridge.", envelope,
529
+ privateUiEnabled ? createPrivateUiMeta(envelope, grants) : null);
530
+ }
531
+ if (params.name === "hellolynk_save_contact") {
532
+ assertArgumentsAllowed(
533
+ args,
534
+ ["confirm", "contactId", "displayName", "hellolynkId", "uiActionToken"],
535
+ ["confirm", "displayName", "hellolynkId", "uiActionToken"],
536
+ );
537
+ assertConfirmed(args.confirm);
538
+ const targetIds = args.contactId == null ? [] : [safeId(args.contactId, "contact_id_invalid", 198)];
539
+ uiActionGate.consume(args.uiActionToken, { toolName: params.name, targetIds });
540
+ const raw = await adapter.saveContact({
541
+ ...(targetIds.length ? { contactId: targetIds[0] } : {}),
542
+ hellolynkId: safeHelloLynkId(args.hellolynkId), displayName: safeDisplayName(args.displayName, "contact_display_name_invalid"),
543
+ });
544
+ return success("The contact was saved by HelloLynk on this computer.", createDynamicUiEnvelope("action", "ready", {
545
+ operation: "contact_saved", contactId: safeId(raw.contact?.contactId, "contact_id_invalid", 198),
546
+ displayName: safeDisplayName(raw.contact?.displayName, "contact_display_name_invalid"),
547
+ }, { exposure: "aggregate-only" }));
548
+ }
549
+ if (params.name === "hellolynk_delete_contact") {
550
+ assertArguments(args, ["confirm", "contactId", "uiActionToken"]); assertConfirmed(args.confirm);
551
+ const id = safeId(args.contactId, "contact_id_invalid", 198);
552
+ uiActionGate.consume(args.uiActionToken, { toolName: params.name, targetIds: [id] });
553
+ const raw = await adapter.deleteContact(id);
554
+ return success("The selected local contact record was deleted.", localAction("contact_deleted", { contactId: id, removed: raw.removed === true }));
555
+ }
556
+ if (params.name === "hellolynk_render_outbox") {
557
+ assertArguments(args, []); const raw = await adapter.readOutbox(); const envelope = projectOutboxView(raw);
558
+ const retryableIds = raw.messages.filter((item) => item.retryAllowed === true)
559
+ .map((item) => safeId(item.clientMessageId, "outbox_message_id_invalid", 198));
560
+ const grants = privateUiEnabled && retryableIds.length
561
+ ? [uiActionGate.issue({ toolName: "hellolynk_retry_outbox_message", mode: "subset", allowedIds: retryableIds })]
562
+ : [];
563
+ return success("HelloLynk sent-message status metadata is available to the installed TRAE extension.", envelope,
564
+ privateUiEnabled ? createPrivateUiMeta(envelope, grants) : null);
565
+ }
566
+ if (params.name === "hellolynk_retry_outbox_message") {
567
+ assertArguments(args, ["clientMessageId", "confirm", "uiActionToken"]); assertConfirmed(args.confirm);
568
+ const id = safeId(args.clientMessageId, "outbox_message_id_invalid", 198);
569
+ uiActionGate.consume(args.uiActionToken, { toolName: params.name, targetIds: [id] });
570
+ const grant = await adapter.issueOutboxRetryGrant(id);
571
+ const raw = await adapter.retryOutboxMessage(id, grant.retryGrant); const message = raw.message;
572
+ return success("The Host processed the explicit retry request.", localAction("outbox_retry_requested", {
573
+ clientMessageId: safeId(message?.clientMessageId, "outbox_message_id_invalid", 198),
574
+ status: safeId(message?.status, "human_action_status_invalid", 198), retryAllowed: message?.retryAllowed === true,
575
+ }));
576
+ }
577
+ if (params.name === "hellolynk_render_send_form") {
578
+ assertArguments(args, []); const contacts = await adapter.listContacts(); const envelope = projectSendForm(contacts);
579
+ const privateMeta = privateUiEnabled ? createPrivateUiMeta(envelope, [uiActionGate.issue({
580
+ toolName: "hellolynk_request_human_send", mode: "send-form",
581
+ allowedIds: contacts.contacts.map((item) => safeId(item.contactId, "contact_id_invalid", 198)),
582
+ })]) : null;
583
+ return success("The human-confirmed send form is available to the private HelloLynk TRAE extension bridge.", envelope, privateMeta);
584
+ }
585
+ if (params.name === "hellolynk_request_human_send") {
586
+ assertArgumentsAllowed(
587
+ args,
588
+ ["clientMessageId", "confirm", "contactId", "recipientHelloLynkId", "text", "uiActionToken"],
589
+ ["clientMessageId", "confirm", "text", "uiActionToken"],
590
+ );
591
+ assertConfirmed(args.confirm);
592
+ const hasContact = Object.hasOwn(args, "contactId"); const hasRecipient = Object.hasOwn(args, "recipientHelloLynkId");
593
+ if (hasContact === hasRecipient) throw codeError("recipient_selection_invalid");
594
+ let recipientHelloLynkId;
595
+ if (hasContact) {
596
+ const contactId = safeId(args.contactId, "contact_id_invalid", 198);
597
+ uiActionGate.consume(args.uiActionToken, {
598
+ toolName: params.name, targetIds: [contactId], recipientKind: "contact",
599
+ });
600
+ const contacts = await adapter.listContacts();
601
+ const contact = contacts.contacts?.find((item) => item.contactId === contactId);
602
+ if (!contact) throw codeError("contact_recipient_not_found");
603
+ recipientHelloLynkId = safeHelloLynkId(contact.hellolynkId);
604
+ } else {
605
+ recipientHelloLynkId = safeHelloLynkId(args.recipientHelloLynkId);
606
+ uiActionGate.consume(args.uiActionToken, {
607
+ toolName: params.name, targetIds: [], recipientKind: "typed",
608
+ });
609
+ }
610
+ const input = {
611
+ clientMessageId: safeId(args.clientMessageId, "client_message_id_invalid", 198), recipientHelloLynkId,
612
+ text: safeText(args.text, "message_text_invalid", 20_000, 20 * 1024, true),
613
+ };
614
+ const raw = await adapter.requestHumanSend(input); assertNoSensitiveProjection(raw, [input.recipientHelloLynkId, input.text]);
615
+ return success("Native human confirmation was requested; this tool did not approve or send the message.", projectActionView(raw, "等待本机 HelloLynk 原生确认"));
616
+ }
617
+ if (params.name === "hellolynk_get_human_send_status") {
618
+ assertArguments(args, ["actionId"]); const id = safeId(args.actionId, "action_id_invalid", 198);
619
+ return success("Native confirmation status is available to the installed HelloLynk TRAE extension.", projectActionView(await adapter.getHumanSendStatus(id), "本机 HelloLynk 原生确认状态"));
620
+ }
621
+ throw codeError("unknown_tool");
622
+ } catch (error) { return writeAttempt ? authorizedMessagingFailure(error, surface, writeAttempt) : failure(error, surface); }
623
+ }
624
+
625
+ async function rememberToolOrigin(adapter, meta, input) {
626
+ // TRAE's native MCP executor supplies chatSessionId outside tool arguments.
627
+ // Registration failure never changes the message's single send attempt.
628
+ const threadId = meta?.chatSessionId;
629
+ if (typeof threadId !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(threadId)) return false;
630
+ try {
631
+ await rememberNativeReplyRoute(await adapter.getNativeTaskScope(), {
632
+ threadId, clientMessageId: input.clientMessageId,
633
+ ...(input.recipientDelegateId ? { peerDelegateId: input.recipientDelegateId }
634
+ : { conversationKey: input.conversationKey }),
635
+ });
636
+ return true;
637
+ } catch { return false; }
638
+ }
639
+
640
+ function localAction(operation, fields) { return createDynamicUiEnvelope("action", "ready", { operation, ...fields }, { exposure: "aggregate-only" }); }
641
+ function projectStatus(value) {
642
+ if (!isPlainObject(value) || !exactKeys(value, ["configured", "ok", "status"]) || value.ok !== true || value.configured !== true || value.status !== "attached") throw codeError("neutral_host_status_invalid");
643
+ return { connected: true, configured: true, status: "attached" };
644
+ }
645
+ function success(text, structuredContent, privateMeta = null) {
646
+ return {
647
+ // MCP clients that only pass content to the model need the same projected
648
+ // result. Never serialize the whole response: _meta belongs to private UI.
649
+ content: [{ type: "text", text }, { type: "text", text: JSON.stringify(structuredContent) }],
650
+ structuredContent,
651
+ ...(privateMeta ? { _meta: privateMeta } : {}),
652
+ isError: false,
653
+ };
654
+ }
655
+ function failure(error, surface) {
656
+ const nativeError = nativeAddressingError(error);
657
+ if (nativeError) return nativeFailureEnvelope(nativeError);
658
+ const code = toRestrictedStdioPublicErrorCode(error, surface);
659
+ return { ...success(failureText(code), projectErrorView(code)), isError: true };
660
+ }
661
+ function authorizedMessagingFailure(error, surface, attempt) {
662
+ const nativeError = nativeAddressingError(error, attempt);
663
+ if (nativeError) return nativeFailureEnvelope(nativeError);
664
+ const code = toRestrictedStdioPublicErrorCode(error, surface);
665
+ // Phase and retry policy come only from this invocation, never from an adapter error.
666
+ const { text, ...outcome } = writeFailure(code, attempt.phase);
667
+ const envelope = createDynamicUiEnvelope("error", "blocked", { errorCode: code, ...outcome, phase: attempt.phase,
668
+ ...(attempt.clientMessageId ? { clientMessageId: attempt.clientMessageId } : {}) }, { exposure: "aggregate-only" });
669
+ return { ...success(text, envelope), isError: true };
670
+ }
671
+ function nativeFailureEnvelope(result) {
672
+ const { ok: _ok, ...fields } = result.structuredContent;
673
+ const envelope = createDynamicUiEnvelope("error", "blocked", fields, { exposure: "aggregate-only" });
674
+ return { ...success(result.content[0].text, envelope), isError: true };
675
+ }
676
+ function negotiateInitializeProtocolVersion(value, surface) {
677
+ if (!isPlainObject(value) || !exactKeysAllowed(value, ["_meta", "capabilities", "clientInfo", "protocolVersion"])
678
+ || !Object.hasOwn(value, "capabilities") || !Object.hasOwn(value, "clientInfo") || !Object.hasOwn(value, "protocolVersion")
679
+ || typeof value.protocolVersion !== "string" || value.protocolVersion.length > 64 || !isPlainObject(value.capabilities)
680
+ || !isPlainObject(value.clientInfo) || (Object.hasOwn(value, "_meta") && !isPlainObject(value._meta))) throw codeError("invalid_params");
681
+ return surface.supportedProtocolVersions.includes(value.protocolVersion) ? value.protocolVersion : surface.protocolVersion;
682
+ }
683
+ function assertEmptyParams(value) { if (value == null) return; if (!isPlainObject(value) || Object.keys(value).length) throw codeError("invalid_params"); }
684
+ function assertArguments(value, keys) { if (!isPlainObject(value) || !exactKeys(value, keys)) throw codeError("invalid_params"); }
685
+ function assertArgumentsAllowed(value, allowed, required) {
686
+ if (!isPlainObject(value) || !exactKeysAllowed(value, allowed) || required.some((key) => !Object.hasOwn(value, key))) throw codeError("invalid_params");
687
+ }
688
+ function assertConfirmed(value) { if (value !== true) throw codeError("confirmation_required"); }
689
+ function assertNoSensitiveProjection(value, sensitive) { const serialized = JSON.stringify(value); if (sensitive.some((item) => item && serialized.includes(item))) throw codeError("human_action_status_invalid"); }
690
+ function safeHelloLynkId(value) { const text = String(value || ""); if (!/^hellolynk:(intl|cn):[a-z0-9][a-z0-9._-]{2,179}$/.test(text)) throw codeError("recipient_invalid"); return text; }
691
+ function safeContactRequestIntro(value) { return value === "" ? "" : safeText(value, "intro_text_invalid", 20000, 20 * 1024); }
692
+ function safeText(value, code, maximum, maximumBytes = maximum, countCodePoints = false) { const text = String(value ?? ""); if (!text || (countCodePoints ? Array.from(text).length : text.length) > maximum || Buffer.byteLength(text, "utf8") > maximumBytes || text.includes("\0")) throw codeError(code); return text; }
693
+ // 80 Unicode code points can occupy 160 UTF-16 units and 320 UTF-8 bytes.
694
+ function safeDisplayName(value, code) {
695
+ if (typeof value !== "string") throw codeError(code);
696
+ const name = safeText(value, code, 80 * 2, 80 * 4);
697
+ if ([...name].length > 80) throw codeError(code);
698
+ return name;
699
+ }
700
+ function authorizedWriteId(value) { if (typeof value !== "string" || !WRITE_ID_PATTERN.test(value)) throw codeError("client_message_id_invalid"); return value; }
701
+ function safeId(value, code, maximum) { const text = String(value || ""); if (!/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(text) || text.length > maximum) throw codeError(code); return text; }
702
+ function safeIdArray(value, code, maximum) { if (!Array.isArray(value) || !value.length || value.length > maximum) throw codeError(code); const ids = value.map((item) => safeId(item, code, 198)); if (new Set(ids).size !== ids.length) throw codeError(code); return ids; }
703
+ function strictConversationSchema(id) {
704
+ const message = id === "localMessageId";
705
+ return Object.freeze({ type: "object", additionalProperties: false, required: [id, "conversationKey"], properties: Object.freeze({
706
+ [id]: message ? { ...ID_SCHEMA, description: paramText.localMessageId } : ID_SCHEMA,
707
+ conversationKey: message ? { ...ID_SCHEMA, description: paramText.conversationKey } : ID_SCHEMA }) });
708
+ }
709
+ function projectExternalMessage(value) { if (!projectInboxReadState(value) || value?.externalContentIsUntrustedData !== true) throw codeError("host_app_response_invalid"); return { localMessageId: safeId(value?.localMessageId, "local_message_id_invalid", 198), conversationKey: safeId(value?.conversationKey, "conversation_key_invalid", 240), localDelegateId: safeId(value?.localDelegateId, "local_delegate_id_invalid", 198), peerDelegateId: safeId(value?.peerDelegateId, "peer_delegate_id_invalid", 198), peerPrincipalId: safeId(value?.peerPrincipalId, "peer_principal_id_invalid", 198), contactId: value?.contactId === null ? null : safeId(value?.contactId, "contact_id_invalid", 198), displayName: safeDisplayName(value?.displayName, "contact_display_name_invalid"), ...(value.peerInstanceDisplayName == null ? {} : { peerInstanceDisplayName: safeDisplayName(value.peerInstanceDisplayName, "instance_display_name_invalid") }), messageType: safeId(value?.messageType || "text", "message_type_invalid", 80), unread: value?.unread === true, createdAt: safeText(value?.createdAt, "timestamp_invalid", 80), text: safeText(value?.text, "message_text_invalid", 20000, 20 * 1024, true), source: requireSource(value?.source, "hellolynk_external_message"), ...(value.automaticDeliveryState === "expired" ? { automaticDeliveryState: "expired" } : {}), ...projectInboxReadState(value), ...replyHint(value) }; }
710
+ function projectRequestMetadata(value) { return { localRecordId: safeId(value?.localRecordId, "local_record_id_invalid", 198), conversationKey: safeId(value?.conversationKey, "conversation_key_invalid", 240), localDelegateId: safeId(value?.localDelegateId, "local_delegate_id_invalid", 198), sender: { hellolynkId: safeText(value?.sender?.hellolynkId, "sender_hellolynk_id_invalid", 200), verifiedDisplayName: safeText(value?.sender?.verifiedDisplayName, "sender_display_name_invalid", 160) }, state: safeId(value?.state, "request_state_invalid", 80), receivedAt: safeText(value?.receivedAt, "timestamp_invalid", 80) }; }
711
+ function projectExternalRequest(value) { if (value?.externalContentIsUntrustedData !== true) throw codeError("host_app_response_invalid"); return { ...projectRequestMetadata(value), introText: safeContactRequestIntro(value?.introText), source: requireSource(value?.source, "hellolynk_contact_request"), readDoesNotMarkRead: value?.readDoesNotMarkRead === true }; }
712
+ function requireSource(value, expected) { if (value !== expected) throw codeError("external_content_source_invalid"); return expected; }
713
+ function safeInteger(value, code) { const number = Number(value); if (!Number.isSafeInteger(number) || number < 0) throw codeError(code); return number; }
714
+ export function toTraeWorkPublicErrorCode(error) { return toRestrictedStdioPublicErrorCode(error, TRAE_WORK_MCP_SURFACE); }
715
+ export function toRestrictedStdioPublicErrorCode(error, surface = TRAE_WORK_MCP_SURFACE) { const code = typeof error?.code === "string" ? error.code : ""; return surface.isPublicErrorCode(code) ? code : "hellolynk_operation_failed"; }
716
+ function safeSurfaceText(value, code, maximum = 120) { const text = String(value || ""); if (!text || text.length > maximum || /[\r\n\0]/.test(text)) throw codeError(code); return text; }
717
+ function exactKeys(value, expected) { const actual = Object.keys(value).sort(); const wanted = [...expected].sort(); return actual.length === wanted.length && actual.every((key, index) => key === wanted[index]); }
718
+ function exactKeysAllowed(value, allowed) { const set = new Set(allowed); return Object.keys(value).every((key) => set.has(key)); }
719
+ function isPlainObject(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; const proto = Object.getPrototypeOf(value); return proto === Object.prototype || proto === null; }
720
+ function validId(value) { return value === null || typeof value === "string" || Number.isSafeInteger(value); }
721
+ function jsonRpcError(id, code, message) { return { jsonrpc: "2.0", id, error: { code, message } }; }
722
+ function write(output, value) { output.write(`${JSON.stringify(value)}\n`); }
723
+ function codeError(code) { const error = new Error(code); error.code = code; return error; }
724
+
725
+ const isMain = process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url);
726
+ if (isMain) { process.stderr.write(`${JSON.stringify({ ok: false, error: "trae_work_mcp_proxy_required" })}\n`); process.exitCode = 1; }
727
+
728
+ function pendingInput(args, finish = false) {
729
+ const keys = finish ? ["localMessageId", "conversationKey", "claimId", "outcome"] : ["localMessageId", "conversationKey"];
730
+ if (!args || typeof args !== "object" || Array.isArray(args) || Object.keys(args).length !== keys.length
731
+ || keys.some((key) => !Object.hasOwn(args, key))
732
+ || [args.localMessageId, args.conversationKey].some((value) => typeof value !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(value))
733
+ || (finish && (typeof args.claimId !== "string" || !/^inclaim_[a-f0-9]{48}$/.test(args.claimId) || !["completed", "unknown"].includes(args.outcome)))) throw codeError("invalid_params");
734
+ return args;
735
+ }
736
+ function pendingSchema() {
737
+ return { type: "object", additionalProperties: false,
738
+ properties: { batchId: { type: "string", pattern: "^inbatch_[a-f0-9]{48}$", description: paramText.batchId },
739
+ localMessageId: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$", description: paramText.localMessageId },
740
+ conversationKey: { type: "string", pattern: "^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$", description: paramText.conversationKey } },
741
+ oneOf: [{ required: ["batchId"], not: { anyOf: [{ required: ["localMessageId"] }, { required: ["conversationKey"] }] } },
742
+ { required: ["localMessageId", "conversationKey"], not: { required: ["batchId"] } }] };
743
+ }
744
+ export function projectPendingMessages(result) {
745
+ if (!Array.isArray(result?.messages) || result.messages.length > 1000) throw codeError("host_app_response_invalid");
746
+ return result.messages.map((value) => {
747
+ pendingInput({ localMessageId: value?.localMessageId, conversationKey: value?.conversationKey });
748
+ if (value?.wakeEligible !== true || typeof value.unread !== "boolean"
749
+ || [...(value.contactId === null ? [] : [value.contactId]), value.localDelegateId, value.peerDelegateId].some((id) => typeof id !== "string" || !/^[A-Za-z0-9][A-Za-z0-9_.:-]{2,198}$/.test(id))
750
+ || typeof value.displayName !== "string" || !value.displayName || value.displayName.length > 160 || /[\r\n\0]/.test(value.displayName)
751
+ || typeof value.createdAt !== "string" || value.createdAt.length > 80 || !Number.isFinite(Date.parse(value.createdAt))) throw codeError("host_app_response_invalid");
752
+ return { ...Object.fromEntries(["localMessageId", "conversationKey", "localDelegateId", "peerDelegateId", "contactId", "displayName", "unread", "createdAt", "wakeEligible"].map((key) => [key, value[key]])),
753
+ ...(value.peerInstanceDisplayName == null ? {} : { peerInstanceDisplayName: safeDisplayName(value.peerInstanceDisplayName, "instance_display_name_invalid") }) };
754
+ });
755
+ }
756
+ async function pendingTool(name, args, adapter) {
757
+ if (name === "hellolynk_list_pending_messages") {
758
+ if (Object.keys(args).length) throw codeError("invalid_params");
759
+ const messages = projectPendingMessages(await adapter.listPendingMessages());
760
+ return success(resultText.pending(messages.length), { messages });
761
+ }
762
+ if (name === "hellolynk_claim_message") {
763
+ if (Object.hasOwn(args, "batchId")) {
764
+ if (Object.keys(args).length !== 1 || !/^inbatch_[a-f0-9]{48}$/.test(args.batchId || "")) throw codeError("invalid_params");
765
+ const result = await adapter.claimBatch(args.batchId);
766
+ if (!Array.isArray(result?.messages) || result.claimed !== (result.messages.length > 0)
767
+ || !Number.isSafeInteger(result.expiredCount) || result.expiredCount < 0) throw codeError("host_app_response_invalid");
768
+ const messages = result.messages.map(projectExternalMessage);
769
+ if (messages.reduce((count, m) => count + Array.from(m.text).length, 0) > 20000) throw codeError("host_app_response_invalid");
770
+ const notice = result.claimed ? (result.expiredCount ? `另有 ${result.expiredCount} 条较早来信超出单次读取上限,未处理。` : null) : "这批来信已读取,无需重复读取。";
771
+ return success(result.claimed ? resultText.claimed(messages.length, result.expiredCount) : notice, { claimed: result.claimed, messages, expiredCount: result.expiredCount, notice });
772
+ }
773
+ pendingInput(args);
774
+ const result = await adapter.claimMessage(args.localMessageId, args.conversationKey);
775
+ if (result?.claimed === false && result.claimId === null && result.message === null) return success("此来信已被领取或处理。", { claimed: false, claimId: null, message: null });
776
+ if (result?.claimed !== true || typeof result.claimId !== "string" || !/^inclaim_[a-f0-9]{48}$/.test(result.claimId)) throw codeError("host_app_response_invalid");
777
+ const message = projectExternalMessage(result.message);
778
+ if (message.localMessageId !== args.localMessageId || message.conversationKey !== args.conversationKey
779
+ || !projectInboxReadState(message) || result.message?.wakeEligible !== true) throw codeError("host_app_response_invalid");
780
+ return success(resultText.claimed(1), { claimed: true, claimId: result.claimId, message: { ...message, wakeEligible: true } });
781
+ }
782
+
783
+ return null;
784
+ }