@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.
- package/.claude/agents/agent-dmitri-activity-crud.md +3 -1
- package/.claude/agents/agent-giuseppe-app-builder.md +11 -12
- package/.claude/agents/agent-kenji-data-reader.md +5 -3
- package/.claude/hooks/sync-marketplace-agents.cjs +117 -56
- package/.claude/skills/hailer-app-builder/SKILL.md +506 -0
- package/.claude/skills/publish-hailer-app/SKILL.md +169 -0
- package/.claude/skills/tool-parameter-usage/SKILL.md +112 -0
- package/CHANGELOG.md +20 -0
- package/CLAUDE.md +37 -16
- package/REFACTOR_STATUS.md +127 -0
- package/dist/cli.js +0 -0
- package/dist/client/agents/base.d.ts +202 -0
- package/dist/client/agents/base.js +737 -0
- package/dist/client/agents/definitions.d.ts +53 -0
- package/dist/client/agents/definitions.js +178 -0
- package/dist/client/agents/orchestrator.d.ts +119 -0
- package/dist/client/agents/orchestrator.js +760 -0
- package/dist/client/agents/specialist.d.ts +86 -0
- package/dist/client/agents/specialist.js +340 -0
- package/dist/client/bot-manager.d.ts +44 -0
- package/dist/client/bot-manager.js +173 -0
- package/dist/client/chat-agent-daemon.d.ts +464 -0
- package/dist/client/chat-agent-daemon.js +1774 -0
- package/dist/client/daemon-factory.d.ts +106 -0
- package/dist/client/daemon-factory.js +301 -0
- package/dist/client/factory.d.ts +107 -0
- package/dist/client/factory.js +304 -0
- package/dist/client/index.d.ts +17 -0
- package/dist/client/index.js +38 -0
- package/dist/client/multi-bot-manager.d.ts +18 -0
- package/dist/client/multi-bot-manager.js +88 -1
- package/dist/client/orchestrator-daemon.d.ts +87 -0
- package/dist/client/orchestrator-daemon.js +444 -0
- package/dist/client/services/agent-registry.d.ts +108 -0
- package/dist/client/services/agent-registry.js +630 -0
- package/dist/client/services/conversation-manager.d.ts +50 -0
- package/dist/client/services/conversation-manager.js +136 -0
- package/dist/client/services/mcp-client.d.ts +48 -0
- package/dist/client/services/mcp-client.js +105 -0
- package/dist/client/services/message-classifier.d.ts +37 -0
- package/dist/client/services/message-classifier.js +187 -0
- package/dist/client/services/message-formatter.d.ts +84 -0
- package/dist/client/services/message-formatter.js +353 -0
- package/dist/client/services/session-logger.d.ts +106 -0
- package/dist/client/services/session-logger.js +446 -0
- package/dist/client/services/tool-executor.d.ts +41 -0
- package/dist/client/services/tool-executor.js +169 -0
- package/dist/client/services/workspace-schema-cache.d.ts +149 -0
- package/dist/client/services/workspace-schema-cache.js +732 -0
- package/dist/client/specialist-daemon.d.ts +77 -0
- package/dist/client/specialist-daemon.js +197 -0
- package/dist/client/specialists.d.ts +53 -0
- package/dist/client/specialists.js +178 -0
- package/dist/client/tool-schema-loader.d.ts +4 -3
- package/dist/client/tool-schema-loader.js +54 -8
- package/dist/client/types.d.ts +283 -55
- package/dist/client/types.js +113 -2
- package/dist/config.d.ts +1 -1
- package/dist/config.js +1 -1
- package/dist/core.d.ts +10 -2
- package/dist/core.js +43 -27
- package/dist/lib/logger.js +15 -3
- package/dist/mcp/UserContextCache.js +2 -2
- package/dist/mcp/hailer-clients.js +5 -5
- package/dist/mcp/signal-handler.js +27 -5
- package/dist/mcp/tools/activity.js +137 -65
- package/dist/mcp/tools/app-core.js +4 -140
- package/dist/mcp/tools/app-marketplace.js +15 -260
- package/dist/mcp/tools/app-member.js +2 -73
- package/dist/mcp/tools/app-scaffold.js +146 -87
- package/dist/mcp/tools/discussion.js +348 -73
- package/dist/mcp/tools/insight.js +74 -190
- package/dist/mcp/tools/workflow.js +20 -94
- package/dist/mcp/utils/hailer-api-client.d.ts +4 -2
- package/dist/mcp/utils/hailer-api-client.js +24 -10
- package/dist/mcp-server.d.ts +4 -0
- package/dist/mcp-server.js +24 -4
- package/dist/routes/agents.d.ts +44 -0
- package/dist/routes/agents.js +311 -0
- package/dist/services/agent-credential-store.d.ts +73 -0
- package/dist/services/agent-credential-store.js +212 -0
- package/lineup-manager/dist/assets/index-8ce6041d.css +1 -0
- package/lineup-manager/dist/assets/index-e168f265.js +600 -0
- package/lineup-manager/dist/index.html +15 -0
- package/lineup-manager/dist/manifest.json +17 -0
- package/lineup-manager/dist/vite.svg +1 -0
- package/package.json +1 -1
- package/dist/client/adaptive-documentation-bot.d.ts +0 -106
- package/dist/client/adaptive-documentation-bot.js +0 -464
- package/dist/client/adaptive-documentation-types.d.ts +0 -66
- package/dist/client/adaptive-documentation-types.js +0 -9
- package/dist/client/agent-activity-bot.d.ts +0 -51
- package/dist/client/agent-activity-bot.js +0 -166
- package/dist/client/agent-tracker.d.ts +0 -499
- package/dist/client/agent-tracker.js +0 -659
- package/dist/client/description-updater.d.ts +0 -56
- package/dist/client/description-updater.js +0 -259
- package/dist/client/log-parser.d.ts +0 -72
- package/dist/client/log-parser.js +0 -387
- package/dist/client/mcp-assistant.d.ts +0 -21
- package/dist/client/mcp-assistant.js +0 -58
- package/dist/client/mcp-client.d.ts +0 -50
- package/dist/client/mcp-client.js +0 -538
- package/dist/client/message-processor.d.ts +0 -35
- package/dist/client/message-processor.js +0 -357
- package/dist/client/providers/anthropic-provider.d.ts +0 -19
- package/dist/client/providers/anthropic-provider.js +0 -645
- package/dist/client/providers/assistant-provider.d.ts +0 -17
- package/dist/client/providers/assistant-provider.js +0 -51
- package/dist/client/providers/llm-provider.d.ts +0 -47
- package/dist/client/providers/llm-provider.js +0 -367
- package/dist/client/providers/openai-provider.d.ts +0 -23
- package/dist/client/providers/openai-provider.js +0 -630
- package/dist/client/simple-llm-caller.d.ts +0 -19
- package/dist/client/simple-llm-caller.js +0 -100
- package/dist/client/skill-generator.d.ts +0 -81
- package/dist/client/skill-generator.js +0 -386
- package/dist/client/test-adaptive-bot.d.ts +0 -9
- package/dist/client/test-adaptive-bot.js +0 -82
- package/dist/client/token-pricing.d.ts +0 -38
- package/dist/client/token-pricing.js +0 -127
- package/dist/client/token-tracker.d.ts +0 -232
- package/dist/client/token-tracker.js +0 -457
- package/dist/client/token-usage-bot.d.ts +0 -53
- package/dist/client/token-usage-bot.js +0 -153
- package/dist/client/tool-executor.d.ts +0 -69
- package/dist/client/tool-executor.js +0 -159
- package/dist/lib/materialize.d.ts +0 -3
- package/dist/lib/materialize.js +0 -101
- package/dist/lib/normalizedName.d.ts +0 -7
- package/dist/lib/normalizedName.js +0 -48
- package/dist/lib/terminal-prompt.d.ts +0 -9
- package/dist/lib/terminal-prompt.js +0 -108
- package/dist/mcp/tools/skill.d.ts +0 -10
- package/dist/mcp/tools/skill.js +0 -279
- package/dist/mcp/tools/workflow-template.d.ts +0 -19
- package/dist/mcp/tools/workflow-template.js +0 -822
|
@@ -95,7 +95,7 @@ class UserContextCache {
|
|
|
95
95
|
* Separated from getContext to support Promise memoization.
|
|
96
96
|
*/
|
|
97
97
|
static async createContextInternal(apiKey) {
|
|
98
|
-
logger.
|
|
98
|
+
logger.debug('Creating new user context', { apiKey: apiKey.substring(0, 8) + '...' });
|
|
99
99
|
try {
|
|
100
100
|
// Create client connection (uses existing connection pool)
|
|
101
101
|
const client = await (0, hailer_clients_1.createHailerClientByApiKey)(apiKey);
|
|
@@ -124,7 +124,7 @@ class UserContextCache {
|
|
|
124
124
|
const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
|
|
125
125
|
const workspaceCacheSize = Buffer.byteLength(JSON.stringify(workspaceCache), 'utf8');
|
|
126
126
|
const totalContextSize = Buffer.byteLength(JSON.stringify({ init, workspaceCache }), 'utf8');
|
|
127
|
-
logger.
|
|
127
|
+
logger.debug('User context created and cached', {
|
|
128
128
|
apiKey: apiKey.substring(0, 8) + '...',
|
|
129
129
|
workflowCount: init.processes?.length || 0,
|
|
130
130
|
userCount: workspaceCache.users.length,
|
|
@@ -20,7 +20,7 @@ async function getCurrentUserId(client) {
|
|
|
20
20
|
]));
|
|
21
21
|
// Check if there's a user field (this is the current authenticated user)
|
|
22
22
|
if (init.user && init.user._id) {
|
|
23
|
-
logger.
|
|
23
|
+
logger.debug('Retrieved user ID from init', { userId: init.user._id });
|
|
24
24
|
return init.user._id;
|
|
25
25
|
}
|
|
26
26
|
// Fallback: Try with different parameters if the above doesn't work
|
|
@@ -33,7 +33,7 @@ async function getCurrentUserId(client) {
|
|
|
33
33
|
if (profileResponse.ok) {
|
|
34
34
|
const profile = (await profileResponse.json());
|
|
35
35
|
if (profile && profile._id) {
|
|
36
|
-
logger.
|
|
36
|
+
logger.debug('Retrieved user ID from profile endpoint', { userId: profile._id });
|
|
37
37
|
return profile._id;
|
|
38
38
|
}
|
|
39
39
|
}
|
|
@@ -52,7 +52,7 @@ async function getCurrentUserId(client) {
|
|
|
52
52
|
const member = members[0];
|
|
53
53
|
const userId = member.uid || member._id || member.id;
|
|
54
54
|
if (userId) {
|
|
55
|
-
logger.
|
|
55
|
+
logger.debug('Inferred user ID from single workspace member', { userId });
|
|
56
56
|
return userId;
|
|
57
57
|
}
|
|
58
58
|
}
|
|
@@ -205,7 +205,7 @@ const createHailerClientByApiKey = async (apiKey) => {
|
|
|
205
205
|
// Check if we have an existing connection
|
|
206
206
|
let clientManager = connectionPool.get(connectionKey);
|
|
207
207
|
if (clientManager && clientManager.isConnected()) {
|
|
208
|
-
logger.
|
|
208
|
+
logger.debug('Reusing existing connection', {
|
|
209
209
|
apiKey: apiKey.substring(0, 8) + '...',
|
|
210
210
|
email: account.email
|
|
211
211
|
});
|
|
@@ -213,7 +213,7 @@ const createHailerClientByApiKey = async (apiKey) => {
|
|
|
213
213
|
}
|
|
214
214
|
// Clear any stale connection
|
|
215
215
|
if (clientManager) {
|
|
216
|
-
logger.
|
|
216
|
+
logger.debug('Cleaning up stale connection', {
|
|
217
217
|
apiKey: apiKey.substring(0, 8) + '...',
|
|
218
218
|
email: account.email
|
|
219
219
|
});
|
|
@@ -24,11 +24,34 @@ class SignalHandler {
|
|
|
24
24
|
handleIncomingSignal(rawSignal) {
|
|
25
25
|
try {
|
|
26
26
|
const [eventType, eventData] = rawSignal;
|
|
27
|
+
// Extract workspaceId from signal data (sid) - required for multi-workspace isolation
|
|
28
|
+
// Falls back to currentWorkspace only if signal doesn't include workspace info
|
|
29
|
+
const signalWorkspaceId = eventData.sid;
|
|
30
|
+
const workspaceId = signalWorkspaceId || this.workspaceCache?.currentWorkspace._id;
|
|
31
|
+
// Log if workspace mismatch detected (signal from different workspace)
|
|
32
|
+
if (signalWorkspaceId && this.workspaceCache?.currentWorkspace._id &&
|
|
33
|
+
signalWorkspaceId !== this.workspaceCache.currentWorkspace._id) {
|
|
34
|
+
logger.debug('Signal from different workspace', {
|
|
35
|
+
signalType: eventType,
|
|
36
|
+
signalWorkspaceId,
|
|
37
|
+
currentWorkspaceId: this.workspaceCache.currentWorkspace._id,
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
// Debug: log raw signal data to discover available fields
|
|
41
|
+
if (eventType === 'messenger.new') {
|
|
42
|
+
const data = eventData;
|
|
43
|
+
logger.debug('Raw messenger.new signal data', {
|
|
44
|
+
eventType,
|
|
45
|
+
dataKeys: Object.keys(eventData),
|
|
46
|
+
sid: String(data.sid || ''),
|
|
47
|
+
workspaceId: String(data.workspaceId || ''),
|
|
48
|
+
});
|
|
49
|
+
}
|
|
27
50
|
const signal = {
|
|
28
51
|
type: eventType,
|
|
29
52
|
data: eventData,
|
|
30
53
|
timestamp: Date.now(),
|
|
31
|
-
workspaceId
|
|
54
|
+
workspaceId,
|
|
32
55
|
};
|
|
33
56
|
// Add to history
|
|
34
57
|
this.addToHistory(signal);
|
|
@@ -134,14 +157,13 @@ class SignalHandler {
|
|
|
134
157
|
logger.debug('No workspace cache available, skipping cache invalidation');
|
|
135
158
|
return;
|
|
136
159
|
}
|
|
137
|
-
const meta = signal.data || {};
|
|
138
160
|
logger.debug('Cache invalidate signal received', {
|
|
139
161
|
workspaceId: signal.workspaceId,
|
|
140
|
-
meta
|
|
162
|
+
meta: signal.data
|
|
141
163
|
});
|
|
142
164
|
try {
|
|
143
|
-
// Refresh cache by fetching fresh data -
|
|
144
|
-
const init = await this.client.socket.request('v2.core.init', [
|
|
165
|
+
// Refresh cache by fetching fresh data - API requires array of keys to fetch
|
|
166
|
+
const init = await this.client.socket.request('v2.core.init', [['users', 'network', 'networks']]);
|
|
145
167
|
// Update workspace cache with fresh data
|
|
146
168
|
if (init.processes) {
|
|
147
169
|
this.workspaceCache.rawInit.processes = init.processes;
|
|
@@ -318,21 +318,74 @@ function buildActivityUpdate(args, context) {
|
|
|
318
318
|
throw new Error(`Invalid fields JSON: ${args.fields}`);
|
|
319
319
|
}
|
|
320
320
|
}
|
|
321
|
-
if
|
|
322
|
-
|
|
323
|
-
|
|
321
|
+
// Auto-fix activitylink fields: if LLM passes an object instead of just the ID string
|
|
322
|
+
if (parsedFields && typeof parsedFields === "object") {
|
|
323
|
+
for (const [fieldId, fieldValue] of Object.entries(parsedFields)) {
|
|
324
|
+
if (fieldValue && typeof fieldValue === "object" && !Array.isArray(fieldValue)) {
|
|
325
|
+
const obj = fieldValue;
|
|
326
|
+
let extractedId = null;
|
|
327
|
+
// Case 1: {type: "activitylink", value: {_id: "...", name: "..."}}
|
|
328
|
+
if (obj.type && obj.value && typeof obj.value === "object" && obj.value._id) {
|
|
329
|
+
extractedId = obj.value._id;
|
|
330
|
+
}
|
|
331
|
+
// Case 2: {_id: "...", name: "..."}
|
|
332
|
+
else if (obj._id && typeof obj._id === "string") {
|
|
333
|
+
extractedId = obj._id;
|
|
334
|
+
}
|
|
335
|
+
// Case 3: {value: "..."} where value is the ID string
|
|
336
|
+
else if (obj.value && typeof obj.value === "string" && /^[a-f0-9]{24}$/i.test(obj.value)) {
|
|
337
|
+
extractedId = obj.value;
|
|
338
|
+
}
|
|
339
|
+
if (extractedId) {
|
|
340
|
+
logger.warn("Auto-fixing activitylink field - extracting ID from object", {
|
|
341
|
+
fieldId,
|
|
342
|
+
original: JSON.stringify(fieldValue),
|
|
343
|
+
extracted: extractedId,
|
|
344
|
+
});
|
|
345
|
+
parsedFields[fieldId] = extractedId;
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
// Field-type aware validation: only validate user fields as users
|
|
351
|
+
if (parsedFields && typeof parsedFields === "object" && context.workspaceCache) {
|
|
324
352
|
const invalidUsers = [];
|
|
325
353
|
for (const [fieldId, fieldValue] of Object.entries(parsedFields)) {
|
|
326
|
-
|
|
327
|
-
|
|
354
|
+
// Only check string values that look like IDs
|
|
355
|
+
if (typeof fieldValue !== "string" || !/^[a-f0-9]{24}$/i.test(fieldValue)) {
|
|
356
|
+
continue;
|
|
357
|
+
}
|
|
358
|
+
// Find the field definition to check its type
|
|
359
|
+
let fieldType;
|
|
360
|
+
for (const workflow of context.workspaceCache.rawInit.processes) {
|
|
361
|
+
const workflowFields = workflow.fields;
|
|
362
|
+
if (!workflowFields)
|
|
363
|
+
continue;
|
|
364
|
+
// Handle both array and object formats for fields
|
|
365
|
+
let field;
|
|
366
|
+
if (Array.isArray(workflowFields)) {
|
|
367
|
+
field = workflowFields.find((f) => f._id === fieldId || f.id === fieldId);
|
|
368
|
+
}
|
|
369
|
+
else if (typeof workflowFields === 'object') {
|
|
370
|
+
// Fields as object: { fieldId: fieldData, ... }
|
|
371
|
+
field = workflowFields[fieldId];
|
|
372
|
+
}
|
|
373
|
+
if (field) {
|
|
374
|
+
fieldType = field.type;
|
|
375
|
+
break;
|
|
376
|
+
}
|
|
377
|
+
}
|
|
378
|
+
// Only validate as user ID if the field type is 'user'
|
|
379
|
+
if (fieldType === 'user') {
|
|
328
380
|
const user = (0, workspace_cache_1.getUserById)(context.workspaceCache, fieldValue);
|
|
329
381
|
if (!user) {
|
|
330
382
|
invalidUsers.push(`${fieldId}: ${fieldValue}`);
|
|
331
383
|
}
|
|
332
384
|
}
|
|
385
|
+
// Skip validation for 'activitylink' and other field types
|
|
333
386
|
}
|
|
334
387
|
if (invalidUsers.length > 0) {
|
|
335
|
-
throw new Error(`Invalid user IDs in fields: ${invalidUsers.join(", ")}. Use search_workspace_users to find valid user IDs.`);
|
|
388
|
+
throw new Error(`Invalid user IDs in user fields: ${invalidUsers.join(", ")}. Use search_workspace_users to find valid user IDs.`);
|
|
336
389
|
}
|
|
337
390
|
}
|
|
338
391
|
return {
|
|
@@ -369,7 +422,7 @@ function formatUpdateActivityResponse(args, result) {
|
|
|
369
422
|
exports.listActivitiesTool = {
|
|
370
423
|
name: 'list_activities',
|
|
371
424
|
group: tool_registry_1.ToolGroup.READ,
|
|
372
|
-
description:
|
|
425
|
+
description: `List activities from workflow phase`,
|
|
373
426
|
schema: zod_1.z.object({
|
|
374
427
|
workflowId: zod_1.z.string().describe("Workflow ID to list activities from"),
|
|
375
428
|
phaseId: zod_1.z.string().describe("Phase ID to filter activities (required - use list_workflow_phases to get available phases)"),
|
|
@@ -399,7 +452,7 @@ exports.listActivitiesTool = {
|
|
|
399
452
|
value: zod_1.z.union([zod_1.z.string(), zod_1.z.number()]).optional().describe("Value to filter by - REQUIRED for text_search, equals, not_equals, contains, greater_than, less_than operators. NOT USED for range operator (use start/end instead). For text_search: use the text/name to search for. For equals: use the entity ID."),
|
|
400
453
|
start: zod_1.z.coerce.number().optional().describe("Start value - ONLY for range operator (NOT in 'value' field). Unix timestamp in milliseconds for date ranges. Example: for June 2025 date range, use start=1717200000000 directly here, NOT nested in 'value'."),
|
|
401
454
|
end: zod_1.z.coerce.number().optional().describe("End value - ONLY for range operator (NOT in 'value' field). Unix timestamp in milliseconds for date ranges. Example: for June 2025 date range, use end=1719791999000 directly here, NOT nested in 'value'."),
|
|
402
|
-
})).optional().describe("
|
|
455
|
+
})).optional().describe("Filter by field values. STRUCTURE: {\"fieldId\": {\"operator\": \"...\", \"value\": \"...\"}}. EXAMPLE: To find player named 'Harry Kane', use: {\"691ffdf84217e9e8434e5694\": {\"operator\": \"text_search\", \"value\": \"Harry Kane\"}}. WRONG: {\"playerName\": \"Harry Kane\"} or {\"fieldId\": \"Harry Kane\"}. Operators: text_search (partial text match), equals (exact ID match), range (use start/end instead of value)."),
|
|
403
456
|
search: zod_1.z.string().optional().describe("Text search across activity content - USE FILTERS INSTEAD when possible for reliable results. Only use search for truly exploratory queries."),
|
|
404
457
|
limit: zod_1.z.coerce.number().optional().default(50).describe("Maximum number of activities to return (default: 50, max: 20000)"),
|
|
405
458
|
page: zod_1.z.coerce.number().optional().default(0).describe("Page number for pagination (0-based)"),
|
|
@@ -500,7 +553,7 @@ exports.listActivitiesTool = {
|
|
|
500
553
|
exports.showActivityByIdTool = {
|
|
501
554
|
name: 'show_activity_by_id',
|
|
502
555
|
group: tool_registry_1.ToolGroup.READ,
|
|
503
|
-
description:
|
|
556
|
+
description: `Get activity by ID`,
|
|
504
557
|
schema: zod_1.z.object({
|
|
505
558
|
activityId: zod_1.z.string().describe("Activity ID to load"),
|
|
506
559
|
}),
|
|
@@ -535,33 +588,7 @@ exports.showActivityByIdTool = {
|
|
|
535
588
|
// ============================================================================
|
|
536
589
|
// TOOL 3: CREATE ACTIVITY
|
|
537
590
|
// ============================================================================
|
|
538
|
-
const createActivityDescription = `Create
|
|
539
|
-
|
|
540
|
-
**BULK CREATION (3+ activities):**
|
|
541
|
-
Use the 'activities' parameter to create multiple activities efficiently:
|
|
542
|
-
- activities: [{ name, fields, phaseId }, { name, fields, phaseId }, ...]
|
|
543
|
-
- All activities created in one API call
|
|
544
|
-
- Much faster for creating 10, 50, or 100+ activities
|
|
545
|
-
- Recommended for sample data generation and batch imports
|
|
546
|
-
|
|
547
|
-
**SINGLE CREATION:**
|
|
548
|
-
Use individual parameters (name, fields, phaseId) for creating one activity
|
|
549
|
-
|
|
550
|
-
**IMPORTANT - Field Type Rules:**
|
|
551
|
-
- textpredefinedoptions: MUST be a single STRING value (e.g., "Option1"), NOT an array
|
|
552
|
-
- activitylink: MUST be a STRING (activity ID), NOT an array
|
|
553
|
-
- users/teams: STRING user/team ID
|
|
554
|
-
- numeric/date: NUMBER (timestamp for dates)
|
|
555
|
-
- text/textarea: STRING
|
|
556
|
-
|
|
557
|
-
**Field Keys vs Field IDs:**
|
|
558
|
-
If workflow uses field 'key' (recommended), you can use readable names:
|
|
559
|
-
- With keys: { "vendor": "activity-id", "priority": "High" }
|
|
560
|
-
- Without keys: { "507f191e810c19729de860ea": "activity-id" }
|
|
561
|
-
|
|
562
|
-
**Examples:**
|
|
563
|
-
Single: { workflowId, name: "Customer Lead", fields: { customerName: "John" } }
|
|
564
|
-
Bulk: { workflowId, activities: [{ name: "Lead 1", fields: {...} }, { name: "Lead 2", fields: {...} }] }`;
|
|
591
|
+
const createActivityDescription = `Create activity (single or bulk via activities array)`;
|
|
565
592
|
exports.createActivityTool = {
|
|
566
593
|
name: 'create_activity',
|
|
567
594
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
@@ -835,31 +862,7 @@ ${createdActivities.slice(0, 10).map((act, idx) => `${idx + 1}. "${act.name}" (I
|
|
|
835
862
|
// ============================================================================
|
|
836
863
|
// TOOL 4: UPDATE ACTIVITY
|
|
837
864
|
// ============================================================================
|
|
838
|
-
const updateActivityDescription = `Update
|
|
839
|
-
|
|
840
|
-
**BULK UPDATE (3+ activities):**
|
|
841
|
-
Use the 'activities' parameter to update multiple activities efficiently:
|
|
842
|
-
- activities: [{ _id, name?, fields? }, { _id, name?, fields? }, ...]
|
|
843
|
-
- All activities updated in one API call
|
|
844
|
-
- Much faster for updating 10, 50, or 100+ activities
|
|
845
|
-
- Recommended for batch operations and data migrations
|
|
846
|
-
|
|
847
|
-
**SINGLE UPDATE:**
|
|
848
|
-
Use individual parameters (_id or activityId, name, fields, phaseId) for updating one activity
|
|
849
|
-
|
|
850
|
-
**IMPORTANT - ActivityLink Field Values:**
|
|
851
|
-
When updating activitylink fields, use STRING values (the activity ID to link to), NOT arrays:
|
|
852
|
-
- ✅ Correct: { "vendorField": "507f1f77bcf86cd799439011" }
|
|
853
|
-
- ❌ Wrong: { "vendorField": ["507f1f77bcf86cd799439011"] }
|
|
854
|
-
|
|
855
|
-
**Field Keys vs Field IDs:**
|
|
856
|
-
If workflow uses field 'key' (recommended), you can use readable names:
|
|
857
|
-
- With keys: { "vendor": "new-activity-id", "priority": "High" }
|
|
858
|
-
- Without keys: { "507f191e810c19729de860ea": "new-activity-id" }
|
|
859
|
-
|
|
860
|
-
**Examples:**
|
|
861
|
-
Single: update_activity({ activityId: "xxx", fields: { status: "Active" } })
|
|
862
|
-
Bulk: update_activity({ activities: [{ _id: "xxx", fields: { status: "Active" } }, { _id: "yyy", fields: { status: "Inactive" } }] })`;
|
|
865
|
+
const updateActivityDescription = `Update activity (single or bulk via activities array)`;
|
|
863
866
|
exports.updateActivityTool = {
|
|
864
867
|
name: 'update_activity',
|
|
865
868
|
group: tool_registry_1.ToolGroup.WRITE,
|
|
@@ -876,7 +879,7 @@ exports.updateActivityTool = {
|
|
|
876
879
|
}))
|
|
877
880
|
.min(1))
|
|
878
881
|
.optional()
|
|
879
|
-
.describe("BULK: Array of activities to update.
|
|
882
|
+
.describe("BULK: Array of activities to update. Each object MUST have: '_id' (NOT 'activityId'), and optionally 'fields' (NOT 'fieldsAndValues'). ❌ WRONG: {activityId: 'x', fieldsAndValues: {...}} ✅ CORRECT: {_id: 'x', fields: {...}}"),
|
|
880
883
|
// SINGLE: Individual activity parameters
|
|
881
884
|
activityId: zod_1.z
|
|
882
885
|
.string()
|
|
@@ -887,7 +890,7 @@ exports.updateActivityTool = {
|
|
|
887
890
|
fields: zod_1.z
|
|
888
891
|
.union([zod_1.z.record(zod_1.z.any()), zod_1.z.string()])
|
|
889
892
|
.optional()
|
|
890
|
-
.describe("SINGLE: Updated field values as key-value pairs.
|
|
893
|
+
.describe("SINGLE: Updated field values as key-value pairs. CRITICAL for activitylink fields: pass ONLY the activity ID as a plain STRING, NOT an object! ❌ WRONG: {'fieldId': {'_id': 'abc', 'name': 'X'}} ✅ CORRECT: {'fieldId': 'abc123...'}. Example: { '691fff...': '691ffe...' } for linking to another activity. Can be object or JSON string."),
|
|
891
894
|
phaseId: zod_1.z
|
|
892
895
|
.string()
|
|
893
896
|
.optional()
|
|
@@ -897,7 +900,35 @@ exports.updateActivityTool = {
|
|
|
897
900
|
try {
|
|
898
901
|
// BULK MODE: Update multiple activities
|
|
899
902
|
if (args.activities && Array.isArray(args.activities)) {
|
|
900
|
-
logger.debug("Bulk update mode", {
|
|
903
|
+
logger.debug("Bulk update mode", {
|
|
904
|
+
activityCount: args.activities.length,
|
|
905
|
+
activities: JSON.stringify(args.activities, null, 2)
|
|
906
|
+
});
|
|
907
|
+
// Auto-fix common parameter name mistakes
|
|
908
|
+
for (const activity of args.activities) {
|
|
909
|
+
// Fix activityId -> _id
|
|
910
|
+
if (!activity._id && activity.activityId) {
|
|
911
|
+
logger.warn("Auto-fixing: activityId -> _id", { activityId: activity.activityId });
|
|
912
|
+
activity._id = activity.activityId;
|
|
913
|
+
delete activity.activityId;
|
|
914
|
+
}
|
|
915
|
+
// Fix fieldsAndValues -> fields
|
|
916
|
+
if (!activity.fields && activity.fieldsAndValues) {
|
|
917
|
+
logger.warn("Auto-fixing: fieldsAndValues -> fields", { activityId: activity._id });
|
|
918
|
+
activity.fields = activity.fieldsAndValues;
|
|
919
|
+
delete activity.fieldsAndValues;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
// Validate that all activities have _id
|
|
923
|
+
const invalidActivities = args.activities.filter((a) => !a._id || typeof a._id !== 'string' || a._id.length < 24);
|
|
924
|
+
if (invalidActivities.length > 0) {
|
|
925
|
+
return {
|
|
926
|
+
content: [{
|
|
927
|
+
type: "text",
|
|
928
|
+
text: `❌ **Error: Invalid bulk update request**\n\nEach activity in the "activities" array MUST have a valid "_id" field (24-character activity ID).\n\n**What you sent:** ${JSON.stringify(args.activities, null, 2)}\n\n**Correct format:**\n\`\`\`json\n{\n "activities": [\n { "_id": "691ffe654217e9e8434e577c", "fields": { "fieldId": "value" } },\n { "_id": "691ffe654217e9e8434e5774", "name": "New Name" }\n ]\n}\n\`\`\`\n\n**Tip:** First use list_activities to get the activity IDs, then pass them in the _id field.`,
|
|
929
|
+
}],
|
|
930
|
+
};
|
|
931
|
+
}
|
|
901
932
|
// Build updates for each activity
|
|
902
933
|
const updates = args.activities.map((activity) => {
|
|
903
934
|
const activityUpdate = { _id: activity._id };
|
|
@@ -905,9 +936,26 @@ exports.updateActivityTool = {
|
|
|
905
936
|
activityUpdate.name = activity.name;
|
|
906
937
|
if (activity.fields) {
|
|
907
938
|
// Parse fields if string
|
|
908
|
-
|
|
939
|
+
let parsedFields = typeof activity.fields === 'string'
|
|
909
940
|
? JSON.parse(activity.fields)
|
|
910
941
|
: activity.fields;
|
|
942
|
+
// Auto-fix activitylink fields: if LLM passes object with _id, extract just the ID
|
|
943
|
+
if (parsedFields && typeof parsedFields === "object") {
|
|
944
|
+
for (const [fieldId, fieldValue] of Object.entries(parsedFields)) {
|
|
945
|
+
if (fieldValue &&
|
|
946
|
+
typeof fieldValue === "object" &&
|
|
947
|
+
!Array.isArray(fieldValue) &&
|
|
948
|
+
"_id" in fieldValue &&
|
|
949
|
+
typeof fieldValue._id === "string") {
|
|
950
|
+
logger.warn("Auto-fixing activitylink field in bulk mode", {
|
|
951
|
+
activityId: activity._id,
|
|
952
|
+
fieldId,
|
|
953
|
+
extracted: fieldValue._id,
|
|
954
|
+
});
|
|
955
|
+
parsedFields[fieldId] = fieldValue._id;
|
|
956
|
+
}
|
|
957
|
+
}
|
|
958
|
+
}
|
|
911
959
|
activityUpdate.fields = parsedFields;
|
|
912
960
|
}
|
|
913
961
|
return activityUpdate;
|
|
@@ -931,6 +979,30 @@ exports.updateActivityTool = {
|
|
|
931
979
|
};
|
|
932
980
|
}
|
|
933
981
|
// SINGLE MODE: Update one activity
|
|
982
|
+
// Validate activityId is provided for single mode
|
|
983
|
+
if (!args.activityId || typeof args.activityId !== 'string' || args.activityId.length < 24) {
|
|
984
|
+
return {
|
|
985
|
+
content: [{
|
|
986
|
+
type: "text",
|
|
987
|
+
text: `❌ **Error: Missing or invalid activityId**\n\nFor single activity updates, you must provide a valid "activityId" (24-character hex string).\n\n**What you sent:** activityId = ${JSON.stringify(args.activityId)}\n\n**Correct format:**\n\`\`\`json\n{\n "activityId": "691ffe654217e9e8434e577c",\n "name": "New Name",\n "fields": { "fieldId": "value" }\n}\n\`\`\`\n\n**For bulk updates (3+ activities), use:**\n\`\`\`json\n{\n "activities": [\n { "_id": "activity-id-1", "fields": {...} },\n { "_id": "activity-id-2", "name": "New Name" }\n ]\n}\n\`\`\`\n\n**Tip:** Use list_activities or show_activity_by_id to find activity IDs first.`,
|
|
988
|
+
}],
|
|
989
|
+
};
|
|
990
|
+
}
|
|
991
|
+
// Validate that at least one update field is provided
|
|
992
|
+
if (!args.name && !args.fields && !args.phaseId) {
|
|
993
|
+
return {
|
|
994
|
+
content: [{
|
|
995
|
+
type: "text",
|
|
996
|
+
text: `❌ **Error: No update data provided**\n\nYou called update_activity with activityId="${args.activityId}" but didn't provide any data to update.\n\n**You must provide at least one of:**\n- "name" - to update the activity name\n- "fields" - to update field values\n- "phaseId" - to move to a different phase\n\n**Example:**\n\`\`\`json\n{\n "activityId": "${args.activityId}",\n "fields": {\n "fieldId123": "new value"\n }\n}\n\`\`\``,
|
|
997
|
+
}],
|
|
998
|
+
};
|
|
999
|
+
}
|
|
1000
|
+
logger.debug("Single update mode", {
|
|
1001
|
+
activityId: args.activityId,
|
|
1002
|
+
name: args.name,
|
|
1003
|
+
fields: JSON.stringify(args.fields, null, 2),
|
|
1004
|
+
phaseId: args.phaseId
|
|
1005
|
+
});
|
|
934
1006
|
const updates = buildActivityUpdate(args, context);
|
|
935
1007
|
const options = args.phaseId ? { phaseId: args.phaseId } : undefined;
|
|
936
1008
|
const result = await context.hailer.updateActivities([updates], options);
|
|
@@ -18,47 +18,7 @@ const logger = (0, logger_1.createLogger)({ component: 'app-core' });
|
|
|
18
18
|
// ============================================================================
|
|
19
19
|
// CREATE APP TOOL
|
|
20
20
|
// ============================================================================
|
|
21
|
-
const createAppDescription =
|
|
22
|
-
|
|
23
|
-
**What are Apps?**
|
|
24
|
-
Apps are custom web applications that extend Hailer workspace functionality. Think React/vanilla JS apps running within Hailer.
|
|
25
|
-
|
|
26
|
-
**App Types**:
|
|
27
|
-
- **Development Apps**: URL points to localhost (e.g., http://localhost:3000)
|
|
28
|
-
- **Published Apps**: URL empty or points to production
|
|
29
|
-
|
|
30
|
-
**Example 1 - Development App**:
|
|
31
|
-
\`\`\`javascript
|
|
32
|
-
create_app({
|
|
33
|
-
name: 'Local Development',
|
|
34
|
-
description: 'App pointing to local server',
|
|
35
|
-
url: 'http://localhost:3000'
|
|
36
|
-
})
|
|
37
|
-
\`\`\`
|
|
38
|
-
|
|
39
|
-
**Example 2 - Published App**:
|
|
40
|
-
\`\`\`javascript
|
|
41
|
-
create_app({
|
|
42
|
-
name: 'Task Manager',
|
|
43
|
-
description: 'Production task management app',
|
|
44
|
-
url: '' // Empty for auto URL
|
|
45
|
-
})
|
|
46
|
-
\`\`\`
|
|
47
|
-
|
|
48
|
-
**Properties**:
|
|
49
|
-
- \`name\` (required) - App display name
|
|
50
|
-
- \`description\` (optional) - App description
|
|
51
|
-
- \`url\` (optional) - App URL or empty for auto-generated
|
|
52
|
-
- \`image\` (optional) - Image/icon ID
|
|
53
|
-
|
|
54
|
-
**Requirements**:
|
|
55
|
-
- User must be workspace administrator
|
|
56
|
-
- For published apps, leave URL empty
|
|
57
|
-
|
|
58
|
-
**Tips**:
|
|
59
|
-
- Use development apps for local testing
|
|
60
|
-
- Published apps get automatic URLs from Hailer
|
|
61
|
-
- Use \`add_app_member\` to share with others`;
|
|
21
|
+
const createAppDescription = `Create Hailer app entry in workspace`;
|
|
62
22
|
exports.createAppTool = {
|
|
63
23
|
name: 'create_app',
|
|
64
24
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
@@ -176,28 +136,7 @@ exports.createAppTool = {
|
|
|
176
136
|
// ============================================================================
|
|
177
137
|
// LIST APPS TOOL
|
|
178
138
|
// ============================================================================
|
|
179
|
-
const listAppsDescription =
|
|
180
|
-
|
|
181
|
-
**What it does**:
|
|
182
|
-
Lists all Hailer apps in the current workspace that you have access to.
|
|
183
|
-
|
|
184
|
-
**Example**:
|
|
185
|
-
\`\`\`javascript
|
|
186
|
-
list_apps()
|
|
187
|
-
\`\`\`
|
|
188
|
-
|
|
189
|
-
**Shows**:
|
|
190
|
-
- App name and description
|
|
191
|
-
- App ID
|
|
192
|
-
- URL (localhost for dev, auto for published)
|
|
193
|
-
- Creator and timestamps
|
|
194
|
-
- Configuration
|
|
195
|
-
|
|
196
|
-
**Use Cases**:
|
|
197
|
-
- See all workspace apps
|
|
198
|
-
- Find app IDs for updates
|
|
199
|
-
- Check which apps are development vs published
|
|
200
|
-
- Audit app configuration`;
|
|
139
|
+
const listAppsDescription = `List all apps in workspace`;
|
|
201
140
|
exports.listAppsTool = {
|
|
202
141
|
name: 'list_apps',
|
|
203
142
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
@@ -290,41 +229,7 @@ exports.listAppsTool = {
|
|
|
290
229
|
// ============================================================================
|
|
291
230
|
// UPDATE APP TOOL
|
|
292
231
|
// ============================================================================
|
|
293
|
-
const updateAppDescription =
|
|
294
|
-
|
|
295
|
-
**What it does**:
|
|
296
|
-
Updates an existing app's properties (name, description, URL, etc.).
|
|
297
|
-
|
|
298
|
-
**Example - Update name and description**:
|
|
299
|
-
\`\`\`javascript
|
|
300
|
-
update_app({
|
|
301
|
-
appId: '<app-id>',
|
|
302
|
-
name: 'Updated App Name',
|
|
303
|
-
description: 'New description'
|
|
304
|
-
})
|
|
305
|
-
\`\`\`
|
|
306
|
-
|
|
307
|
-
**Example - Change URL (dev to prod)**:
|
|
308
|
-
\`\`\`javascript
|
|
309
|
-
update_app({
|
|
310
|
-
appId: '<app-id>',
|
|
311
|
-
url: '' // Empty for published
|
|
312
|
-
})
|
|
313
|
-
\`\`\`
|
|
314
|
-
|
|
315
|
-
**Updatable Properties**:
|
|
316
|
-
- \`name\` - App display name
|
|
317
|
-
- \`description\` - App description
|
|
318
|
-
- \`url\` - App URL
|
|
319
|
-
- \`image\` - App icon ID
|
|
320
|
-
- \`config\` - App configuration
|
|
321
|
-
|
|
322
|
-
**Requirements**:
|
|
323
|
-
- User must be app creator or workspace admin
|
|
324
|
-
|
|
325
|
-
**Tips**:
|
|
326
|
-
- Only specified properties are updated
|
|
327
|
-
- Use \`list_apps\` to get app IDs`;
|
|
232
|
+
const updateAppDescription = `Update app properties`;
|
|
328
233
|
exports.updateAppTool = {
|
|
329
234
|
name: 'update_app',
|
|
330
235
|
group: tool_registry_1.ToolGroup.PLAYGROUND,
|
|
@@ -437,48 +342,7 @@ exports.updateAppTool = {
|
|
|
437
342
|
// ============================================================================
|
|
438
343
|
// REMOVE APP TOOL
|
|
439
344
|
// ============================================================================
|
|
440
|
-
const removeAppDescription =
|
|
441
|
-
|
|
442
|
-
⚠️ **MANDATORY: GATHER COMPLETE CONTEXT BEFORE CALLING THIS TOOL**
|
|
443
|
-
**BEFORE calling this tool, you are REQUIRED to:**
|
|
444
|
-
1. Load the skill: \`get_skill({ skillName: "remove-app-skill" })\`
|
|
445
|
-
2. Fetch app details with \`list_apps\` to get:
|
|
446
|
-
- App name and ID
|
|
447
|
-
- Workspace ID and name
|
|
448
|
-
- App type (development/published)
|
|
449
|
-
- App configuration
|
|
450
|
-
3. Show comprehensive confirmation message including:
|
|
451
|
-
- Workspace ID and name
|
|
452
|
-
- App ID and name
|
|
453
|
-
- What will be deleted (entry, permissions, configuration)
|
|
454
|
-
- What won't be deleted (published code remains)
|
|
455
|
-
- Clear irreversibility warning
|
|
456
|
-
4. Wait for explicit user confirmation
|
|
457
|
-
**FAILURE TO GATHER AND SHOW THIS CONTEXT IS AN ERROR**
|
|
458
|
-
|
|
459
|
-
**Required**: appId
|
|
460
|
-
**Permission**: App creator or workspace administrator
|
|
461
|
-
|
|
462
|
-
**What gets deleted**:
|
|
463
|
-
- App entry (name, description, URL, metadata)
|
|
464
|
-
- App permissions
|
|
465
|
-
- App configuration
|
|
466
|
-
|
|
467
|
-
**What doesn't get deleted**:
|
|
468
|
-
- Published app code (remains on server)
|
|
469
|
-
- App data (if any)
|
|
470
|
-
|
|
471
|
-
**Example**:
|
|
472
|
-
\`\`\`javascript
|
|
473
|
-
remove_app({
|
|
474
|
-
appId: '<app-id>'
|
|
475
|
-
})
|
|
476
|
-
\`\`\`
|
|
477
|
-
|
|
478
|
-
**Tips**:
|
|
479
|
-
- Use \`list_apps\` to get app IDs
|
|
480
|
-
- Published app code must be removed separately
|
|
481
|
-
- Operation cannot be undone`;
|
|
345
|
+
const removeAppDescription = `Delete app permanently`;
|
|
482
346
|
exports.removeAppTool = {
|
|
483
347
|
name: 'remove_app',
|
|
484
348
|
group: tool_registry_1.ToolGroup.NUCLEAR,
|