@hailer/mcp 0.1.6 → 0.1.9

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 (137) hide show
  1. package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
  2. package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
  3. package/.claude/agents/agent-kenji-data-reader.md +5 -3
  4. package/.claude/hooks/sync-marketplace-agents.cjs +117 -56
  5. package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
  6. package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
  7. package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
  8. package/CHANGELOG.md +20 -0
  9. package/CLAUDE.md +37 -16
  10. package/REFACTOR_STATUS.md +127 -0
  11. package/dist/cli.js +0 -0
  12. package/dist/client/agents/base.d.ts +202 -0
  13. package/dist/client/agents/base.js +737 -0
  14. package/dist/client/agents/definitions.d.ts +53 -0
  15. package/dist/client/agents/definitions.js +178 -0
  16. package/dist/client/agents/orchestrator.d.ts +119 -0
  17. package/dist/client/agents/orchestrator.js +760 -0
  18. package/dist/client/agents/specialist.d.ts +86 -0
  19. package/dist/client/agents/specialist.js +340 -0
  20. package/dist/client/bot-manager.d.ts +44 -0
  21. package/dist/client/bot-manager.js +173 -0
  22. package/dist/client/chat-agent-daemon.d.ts +464 -0
  23. package/dist/client/chat-agent-daemon.js +1774 -0
  24. package/dist/client/daemon-factory.d.ts +106 -0
  25. package/dist/client/daemon-factory.js +301 -0
  26. package/dist/client/factory.d.ts +107 -0
  27. package/dist/client/factory.js +304 -0
  28. package/dist/client/index.d.ts +17 -0
  29. package/dist/client/index.js +38 -0
  30. package/dist/client/multi-bot-manager.d.ts +18 -0
  31. package/dist/client/multi-bot-manager.js +88 -1
  32. package/dist/client/orchestrator-daemon.d.ts +87 -0
  33. package/dist/client/orchestrator-daemon.js +444 -0
  34. package/dist/client/services/agent-registry.d.ts +108 -0
  35. package/dist/client/services/agent-registry.js +630 -0
  36. package/dist/client/services/conversation-manager.d.ts +50 -0
  37. package/dist/client/services/conversation-manager.js +136 -0
  38. package/dist/client/services/mcp-client.d.ts +48 -0
  39. package/dist/client/services/mcp-client.js +105 -0
  40. package/dist/client/services/message-classifier.d.ts +37 -0
  41. package/dist/client/services/message-classifier.js +187 -0
  42. package/dist/client/services/message-formatter.d.ts +84 -0
  43. package/dist/client/services/message-formatter.js +353 -0
  44. package/dist/client/services/session-logger.d.ts +106 -0
  45. package/dist/client/services/session-logger.js +446 -0
  46. package/dist/client/services/tool-executor.d.ts +41 -0
  47. package/dist/client/services/tool-executor.js +169 -0
  48. package/dist/client/services/workspace-schema-cache.d.ts +149 -0
  49. package/dist/client/services/workspace-schema-cache.js +732 -0
  50. package/dist/client/specialist-daemon.d.ts +77 -0
  51. package/dist/client/specialist-daemon.js +197 -0
  52. package/dist/client/specialists.d.ts +53 -0
  53. package/dist/client/specialists.js +178 -0
  54. package/dist/client/tool-schema-loader.d.ts +4 -3
  55. package/dist/client/tool-schema-loader.js +54 -8
  56. package/dist/client/types.d.ts +283 -55
  57. package/dist/client/types.js +113 -2
  58. package/dist/config.d.ts +1 -1
  59. package/dist/config.js +1 -1
  60. package/dist/core.d.ts +10 -2
  61. package/dist/core.js +43 -27
  62. package/dist/lib/logger.js +15 -3
  63. package/dist/mcp/UserContextCache.js +2 -2
  64. package/dist/mcp/hailer-clients.js +5 -5
  65. package/dist/mcp/signal-handler.js +27 -5
  66. package/dist/mcp/tools/activity.js +137 -65
  67. package/dist/mcp/tools/app-core.js +4 -140
  68. package/dist/mcp/tools/app-marketplace.js +15 -260
  69. package/dist/mcp/tools/app-member.js +2 -73
  70. package/dist/mcp/tools/app-scaffold.js +146 -87
  71. package/dist/mcp/tools/discussion.js +348 -73
  72. package/dist/mcp/tools/insight.js +74 -190
  73. package/dist/mcp/tools/workflow.js +20 -94
  74. package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
  75. package/dist/mcp/utils/hailer-api-client.js +24 -10
  76. package/dist/mcp-server.d.ts +4 -0
  77. package/dist/mcp-server.js +24 -4
  78. package/dist/routes/agents.d.ts +44 -0
  79. package/dist/routes/agents.js +311 -0
  80. package/dist/services/agent-credential-store.d.ts +73 -0
  81. package/dist/services/agent-credential-store.js +212 -0
  82. package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
  83. package/lineup-manager/dist/assets/index-e168f265.js +600 -0
  84. package/lineup-manager/dist/index.html +15 -0
  85. package/lineup-manager/dist/manifest.json +17 -0
  86. package/lineup-manager/dist/vite.svg +1 -0
  87. package/package.json +1 -1
  88. package/dist/client/adaptive-documentation-bot.d.ts +0 -106
  89. package/dist/client/adaptive-documentation-bot.js +0 -464
  90. package/dist/client/adaptive-documentation-types.d.ts +0 -66
  91. package/dist/client/adaptive-documentation-types.js +0 -9
  92. package/dist/client/agent-activity-bot.d.ts +0 -51
  93. package/dist/client/agent-activity-bot.js +0 -166
  94. package/dist/client/agent-tracker.d.ts +0 -499
  95. package/dist/client/agent-tracker.js +0 -659
  96. package/dist/client/description-updater.d.ts +0 -56
  97. package/dist/client/description-updater.js +0 -259
  98. package/dist/client/log-parser.d.ts +0 -72
  99. package/dist/client/log-parser.js +0 -387
  100. package/dist/client/mcp-assistant.d.ts +0 -21
  101. package/dist/client/mcp-assistant.js +0 -58
  102. package/dist/client/mcp-client.d.ts +0 -50
  103. package/dist/client/mcp-client.js +0 -538
  104. package/dist/client/message-processor.d.ts +0 -35
  105. package/dist/client/message-processor.js +0 -357
  106. package/dist/client/providers/anthropic-provider.d.ts +0 -19
  107. package/dist/client/providers/anthropic-provider.js +0 -645
  108. package/dist/client/providers/assistant-provider.d.ts +0 -17
  109. package/dist/client/providers/assistant-provider.js +0 -51
  110. package/dist/client/providers/llm-provider.d.ts +0 -47
  111. package/dist/client/providers/llm-provider.js +0 -367
  112. package/dist/client/providers/openai-provider.d.ts +0 -23
  113. package/dist/client/providers/openai-provider.js +0 -630
  114. package/dist/client/simple-llm-caller.d.ts +0 -19
  115. package/dist/client/simple-llm-caller.js +0 -100
  116. package/dist/client/skill-generator.d.ts +0 -81
  117. package/dist/client/skill-generator.js +0 -386
  118. package/dist/client/test-adaptive-bot.d.ts +0 -9
  119. package/dist/client/test-adaptive-bot.js +0 -82
  120. package/dist/client/token-pricing.d.ts +0 -38
  121. package/dist/client/token-pricing.js +0 -127
  122. package/dist/client/token-tracker.d.ts +0 -232
  123. package/dist/client/token-tracker.js +0 -457
  124. package/dist/client/token-usage-bot.d.ts +0 -53
  125. package/dist/client/token-usage-bot.js +0 -153
  126. package/dist/client/tool-executor.d.ts +0 -69
  127. package/dist/client/tool-executor.js +0 -159
  128. package/dist/lib/materialize.d.ts +0 -3
  129. package/dist/lib/materialize.js +0 -101
  130. package/dist/lib/normalizedName.d.ts +0 -7
  131. package/dist/lib/normalizedName.js +0 -48
  132. package/dist/lib/terminal-prompt.d.ts +0 -9
  133. package/dist/lib/terminal-prompt.js +0 -108
  134. package/dist/mcp/tools/skill.d.ts +0 -10
  135. package/dist/mcp/tools/skill.js +0 -279
  136. package/dist/mcp/tools/workflow-template.d.ts +0 -19
  137. package/dist/mcp/tools/workflow-template.js +0 -822
@@ -1,357 +0,0 @@
1
- "use strict";
2
- /**
3
- * Message Processor for MCP Client
4
- * Handles incoming socket.io signals and processes messages that mention MCP agents
5
- * or are sent directly in one-on-one conversations with MCP agents
6
- */
7
- Object.defineProperty(exports, "__esModule", { value: true });
8
- exports.HailerMessageProcessor = void 0;
9
- class HailerMessageProcessor {
10
- multiBotManager;
11
- mcpAgentIds;
12
- enableDirectMessages;
13
- constructor(multiBotManager, mcpAgentIds = [], enableDirectMessages = false) {
14
- this.multiBotManager = multiBotManager;
15
- this.mcpAgentIds = mcpAgentIds;
16
- this.enableDirectMessages = enableDirectMessages;
17
- }
18
- shouldProcess(signal) {
19
- // Only process discussion.message and messenger.new signals
20
- if (signal.type !== "discussion.message" &&
21
- signal.type !== "messenger.new") {
22
- return false;
23
- }
24
- // For messenger.new signals, we'll check both hailerTag mentions and direct messages in extractMessage
25
- if (signal.type === "messenger.new") {
26
- return true; // Let extractMessage handle all the logic
27
- }
28
- // For discussion.message signals, check here
29
- const messageContent = signal.data?.message?.content || "";
30
- return this.containsMcpAgentMention(messageContent).found;
31
- }
32
- containsMcpAgentMention(content) {
33
- if (this.mcpAgentIds.length === 0) {
34
- // Fallback to old @mcp-agent detection if no agent IDs configured
35
- return {
36
- found: content.toLowerCase().includes("@mcp-agent"),
37
- mentionedBotIds: [],
38
- };
39
- }
40
- // Check for hailerTag mentions of our MCP agents
41
- // Pattern: [hailerTag|Agent Name](agentUserId)
42
- const hailerTagPattern = /\[hailerTag\|[^\]]+\]\(([^)]+)\)/g;
43
- let match;
44
- const mentionedBotIds = [];
45
- while ((match = hailerTagPattern.exec(content)) !== null) {
46
- const mentionedUserId = match[1];
47
- if (this.mcpAgentIds.includes(mentionedUserId)) {
48
- mentionedBotIds.push(mentionedUserId);
49
- console.log(`✅ MCP Client: Found mention of bot: ${mentionedUserId}`);
50
- }
51
- }
52
- return { found: mentionedBotIds.length > 0, mentionedBotIds };
53
- }
54
- /**
55
- * Fetch discussion participants to determine if it's a one-on-one conversation
56
- * Uses messenger.load_discussions API to get actual member count
57
- */
58
- async fetchDiscussionParticipants(discussionId, botClient) {
59
- try {
60
- // Try messenger.load_discussions - it expects an array of discussion IDs
61
- const response = await botClient.client.socket.request("messenger.load_discussions", [[discussionId]]);
62
- // Response is an object with discussionId as key
63
- if (response && response[discussionId]) {
64
- const discussion = response[discussionId];
65
- // First try: Use the 'participants' array which has plain user IDs
66
- if (discussion.participants && Array.isArray(discussion.participants)) {
67
- console.log(`📊 MCP Client: Discussion has ${discussion.participants.length} participant(s)`);
68
- return discussion.participants;
69
- }
70
- // Fallback: Use 'members' array and extract IDs
71
- if (discussion.members && Array.isArray(discussion.members)) {
72
- const memberIds = [];
73
- for (const member of discussion.members) {
74
- // Members have format: { id: "user_USERID", ... }
75
- if (member && member.id && typeof member.id === "string") {
76
- // Extract the actual user ID from "user_USERID" format
77
- const userId = member.id.replace(/^user_/, "");
78
- memberIds.push(userId);
79
- }
80
- else if (typeof member === "string") {
81
- memberIds.push(member);
82
- }
83
- else if (member && member._id) {
84
- memberIds.push(member._id);
85
- }
86
- }
87
- console.log(`📊 MCP Client: Discussion has ${memberIds.length} member(s)`);
88
- return memberIds;
89
- }
90
- }
91
- const messagesResponse = await botClient.client.socket.request("v3.discussion.message.latest", [discussionId]);
92
- const messages = messagesResponse?.messages || [];
93
- const participantIds = new Set();
94
- // Extract unique participant IDs from messages
95
- for (const message of messages) {
96
- if (message.uid) {
97
- participantIds.add(message.uid);
98
- }
99
- }
100
- // Add the bot itself as a participant
101
- participantIds.add(botClient.userId);
102
- const participants = Array.from(participantIds);
103
- console.log(`📊 MCP Client: Discussion has ${participants.length} participant(s) (message-based fallback)`);
104
- return participants;
105
- }
106
- catch (error) {
107
- console.error(`❌ MCP Client: Failed to fetch discussion participants:`, error);
108
- // On error, also try fallback
109
- try {
110
- const messagesResponse = await botClient.client.socket.request("v3.discussion.message.latest", [discussionId]);
111
- const messages = messagesResponse?.messages || [];
112
- const participantIds = new Set();
113
- for (const message of messages) {
114
- if (message.uid) {
115
- participantIds.add(message.uid);
116
- }
117
- }
118
- participantIds.add(botClient.userId);
119
- const participants = Array.from(participantIds);
120
- console.log(`📊 MCP Client: Discussion has ${participants.length} participant(s) (error recovery)`);
121
- return participants;
122
- }
123
- catch (fallbackError) {
124
- console.error(`❌ MCP Client: All participant detection methods failed`);
125
- return [];
126
- }
127
- }
128
- }
129
- /**
130
- * Check if a discussion is a one-on-one conversation with a specific MCP agent
131
- * @param discussionId - The discussion to check
132
- * @param senderId - The user who sent the message
133
- * @param botIdToCheck - The specific bot ID to check for direct message
134
- * @returns The bot ID if it's a 1:1 conversation with that bot, null otherwise
135
- */
136
- async isDirectMessageToBot(discussionId, senderId, botIdToCheck) {
137
- // Get the bot client for the specific bot we're checking
138
- const botClient = this.multiBotManager.getBotClient(botIdToCheck);
139
- if (!botClient) {
140
- console.warn(`⚠️ MCP Client: No bot client found for bot ID: ${botIdToCheck}`);
141
- return null;
142
- }
143
- const participants = await this.fetchDiscussionParticipants(discussionId, botClient);
144
- // For a one-on-one conversation, we expect exactly 2 participants
145
- if (participants.length !== 2) {
146
- return null;
147
- }
148
- // Check if the two participants are the sender and the bot we're checking for
149
- const includesSender = participants.includes(senderId);
150
- const includesBot = participants.includes(botIdToCheck);
151
- if (includesSender && includesBot) {
152
- console.log(`✅ MCP Client: 1:1 chat detected - will respond without tag`);
153
- return botIdToCheck;
154
- }
155
- return null;
156
- }
157
- async extractMessage(signal) {
158
- try {
159
- if (!this.shouldProcess(signal)) {
160
- return [];
161
- }
162
- if (signal.type === "messenger.new") {
163
- return await this.extractFromMessengerNew(signal);
164
- }
165
- else {
166
- // For discussion.message signals
167
- const messageData = signal.data?.message;
168
- if (!messageData) {
169
- console.warn("❌ MCP Client: No message data in signal");
170
- return [];
171
- }
172
- const messageContent = messageData.content || "";
173
- const mentionResult = this.containsMcpAgentMention(messageContent);
174
- if (!mentionResult.found) {
175
- return [];
176
- }
177
- // Create a message for each mentioned bot
178
- const messages = [];
179
- for (const botId of mentionResult.mentionedBotIds) {
180
- const extractedMessage = {
181
- id: messageData._id || `msg-${Date.now()}`,
182
- content: messageContent,
183
- timestamp: signal.timestamp,
184
- discussionId: messageData.discussion || "",
185
- userId: messageData.user || "",
186
- userName: messageData.userName || "Unknown User",
187
- workspaceId: signal.workspaceId,
188
- mentionedOrDirectMessagedBotId: botId,
189
- };
190
- messages.push(extractedMessage);
191
- }
192
- console.log(`🤖 MCP Client: Processing message from ${messages[0]?.userName} for ${messages.length} bot(s)`);
193
- return messages;
194
- }
195
- }
196
- catch (error) {
197
- console.error("❌ MCP Client: Failed to extract message from signal:", error);
198
- return [];
199
- }
200
- }
201
- async extractFromMessengerNew(signal) {
202
- try {
203
- const { discussion, msg_id, uid } = signal.data;
204
- // Validate required fields
205
- if (!uid || !discussion) {
206
- console.warn("❌ MCP Client: Missing uid or discussion in messenger.new signal");
207
- return [];
208
- }
209
- // IMPORTANT: Ignore messages from our own bots to prevent loops
210
- if (this.mcpAgentIds.includes(uid)) {
211
- console.log(`🔇 MCP Client: Ignoring message from bot ${uid} (preventing self-trigger loop)`);
212
- return [];
213
- }
214
- // Try to fetch the message with the first available bot client to check mentions
215
- const botClients = this.multiBotManager.getAllBotClients();
216
- let targetMessage = null;
217
- let messageContent = "";
218
- let workingBotClient = null;
219
- // Try each bot client until we find the message
220
- for (const botClient of botClients) {
221
- try {
222
- // Fetch the message content using the discussion message API
223
- const response = await botClient.client.socket.request("v3.discussion.message.latest", [discussion]);
224
- // Extract messages from the response object
225
- const messages = response?.messages || [];
226
- // Find the specific message by ID
227
- targetMessage = messages.find((msg) => msg._id === msg_id);
228
- if (targetMessage) {
229
- messageContent = targetMessage.msg || targetMessage.content || "";
230
- workingBotClient = botClient;
231
- break; // Found the message, no need to try other bots
232
- }
233
- }
234
- catch (error) {
235
- // Continue with next bot client if this one fails
236
- continue;
237
- }
238
- }
239
- if (!targetMessage || !workingBotClient) {
240
- console.warn(`❌ MCP Client: Could not find message ${msg_id} in discussion ${discussion} with any bot client`);
241
- return [];
242
- }
243
- // Filter out system messages (like "Hi, I created this group!" or user invites)
244
- const msgType = targetMessage.type;
245
- if (msgType && msgType !== "user") {
246
- console.log(`🔇 MCP Client: Ignoring system message type "${msgType}" (discussion: ${discussion})`);
247
- return [];
248
- }
249
- // Also check for common system message patterns
250
- const systemMessagePatterns = [
251
- /^Hi, I created this group!$/i,
252
- /^I invited .+ to this group\.?$/i,
253
- /^.+ was invited to this group\.?$/i,
254
- /^.+ joined the group\.?$/i,
255
- /^.+ left the group\.?$/i,
256
- ];
257
- for (const pattern of systemMessagePatterns) {
258
- if (pattern.test(messageContent.trim())) {
259
- console.log(`🔇 MCP Client: Ignoring system message pattern: "${messageContent}" (discussion: ${discussion})`);
260
- return [];
261
- }
262
- }
263
- // First, check if the message contains an explicit mention of one of our MCP agents
264
- const mentionResult = this.containsMcpAgentMention(messageContent);
265
- let handlingBotIds = mentionResult.mentionedBotIds;
266
- // If no explicit mention found, check if this is a direct message to one of our bots (if enabled)
267
- if (handlingBotIds.length === 0 && this.enableDirectMessages) {
268
- // Check each bot to see if this is a 1:1 conversation with that specific bot
269
- for (const botId of this.mcpAgentIds) {
270
- const directMessageBotId = await this.isDirectMessageToBot(discussion, uid, botId);
271
- if (directMessageBotId) {
272
- handlingBotIds = [directMessageBotId];
273
- break; // Found a 1:1 conversation, no need to check other bots
274
- }
275
- }
276
- }
277
- if (handlingBotIds.length === 0) {
278
- return []; // No mention and not a direct message to our bot
279
- }
280
- // Validate that all bot clients exist
281
- const validBotIds = [];
282
- for (const botId of handlingBotIds) {
283
- const botClient = this.multiBotManager.getBotClient(botId);
284
- if (botClient) {
285
- validBotIds.push(botId);
286
- }
287
- else {
288
- console.warn(`❌ MCP Client: No bot client found for bot ID: ${botId}`);
289
- }
290
- }
291
- if (validBotIds.length === 0) {
292
- console.warn(`❌ MCP Client: No valid bot clients found for processing`);
293
- return [];
294
- }
295
- // Fetch user info if needed
296
- const userName = targetMessage.userName || "Unknown User";
297
- // Create messages for each mentioned bot
298
- const messages = [];
299
- for (const botId of validBotIds) {
300
- const extractedMessage = {
301
- id: targetMessage._id,
302
- content: messageContent,
303
- timestamp: signal.timestamp,
304
- discussionId: discussion,
305
- userId: uid,
306
- userName: userName,
307
- workspaceId: signal.workspaceId,
308
- mentionedOrDirectMessagedBotId: botId,
309
- };
310
- messages.push(extractedMessage);
311
- }
312
- // Clean content for logging (remove hailerTag mentions)
313
- const cleanContent = messageContent
314
- .replace(/\[hailerTag\|[^\]]+\]\([^)]+\)/g, "")
315
- .trim();
316
- const messageType = mentionResult.found ? "mentioned" : "direct message";
317
- console.log(`🤖 MCP Client: Processing ${messageType} from ${messages[0]?.userName} for ${messages.length} bot(s): "${cleanContent}"`);
318
- return messages;
319
- }
320
- catch (error) {
321
- console.error("❌ MCP Client: Failed to extract messenger.new message:", error);
322
- return [];
323
- }
324
- }
325
- async postMessage(discussionId, content, workspaceId, botId) {
326
- try {
327
- // Use the specified bot or fall back to the first available bot
328
- const botClient = botId
329
- ? this.multiBotManager.getBotClient(botId)
330
- : this.multiBotManager.getAllBotClients()[0];
331
- if (!botClient) {
332
- console.error(`❌ MCP Client: No bot client available for posting message (botId: ${botId})`);
333
- return false;
334
- }
335
- // Use the bot's socket client to post the message
336
- const result = await botClient.client.socket.request("messenger.send", [
337
- { msg: content },
338
- discussionId,
339
- ]);
340
- return true;
341
- }
342
- catch (error) {
343
- console.error("❌ MCP Client: Failed to post message via socket:", error);
344
- return false;
345
- }
346
- }
347
- async postInfoMessage(discussionId, toolName, args, workspaceId, botId) {
348
- const infoContent = `🔧 Using tool: **${toolName}**\n\`\`\`json\n${JSON.stringify(args, null, 2)}\n\`\`\``;
349
- return this.postMessage(discussionId, infoContent, workspaceId, botId);
350
- }
351
- async postErrorMessage(discussionId, error, workspaceId, botId) {
352
- const errorContent = `❌ Error: ${error}`;
353
- return this.postMessage(discussionId, errorContent, workspaceId, botId);
354
- }
355
- }
356
- exports.HailerMessageProcessor = HailerMessageProcessor;
357
- //# sourceMappingURL=message-processor.js.map
@@ -1,19 +0,0 @@
1
- import { ChatMessage, McpResponse } from "../types";
2
- import { LlmProvider } from "./llm-provider";
3
- export declare class AnthropicProvider extends LlmProvider {
4
- private client;
5
- private contextManager;
6
- private toolSchemaLoader;
7
- private toolExecutor;
8
- private toolSchemaCache;
9
- constructor(config: any);
10
- generateConfirmationMessage(userMessage: ChatMessage): Promise<string>;
11
- processMessage(userMessage: ChatMessage, mcpServerUrl: string, botMcpApiKey: string, botEmail: string): Promise<McpResponse>;
12
- /**
13
- * Fetch specific tool schema on-demand
14
- * Token-efficient: ~690 tokens per tool vs loading all tools upfront
15
- */
16
- private fetchMcpToolSchema;
17
- protected callMcpTool(mcpServerUrl: string, mcpServerApiKey: string, request: any): Promise<any>;
18
- }
19
- //# sourceMappingURL=anthropic-provider.d.ts.map