@hailer/mcp 0.0.1
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/commands/tool-builder.md +37 -0
- package/.claude/commands/ws-pull.md +44 -0
- package/.claude/settings.json +8 -0
- package/.claude/settings.local.json +49 -0
- package/.claude/skills/activity-api/SKILL.md +96 -0
- package/.claude/skills/activity-api/references/activity-endpoints.md +845 -0
- package/.claude/skills/add-app-member-skill/SKILL.md +977 -0
- package/.claude/skills/agent-building/SKILL.md +243 -0
- package/.claude/skills/agent-building/references/architecture-patterns.md +446 -0
- package/.claude/skills/agent-building/references/code-examples.md +587 -0
- package/.claude/skills/agent-building/references/implementation-guide.md +619 -0
- package/.claude/skills/app-api/SKILL.md +219 -0
- package/.claude/skills/app-api/references/app-endpoints.md +759 -0
- package/.claude/skills/building-hailer-apps-skill/SKILL.md +548 -0
- package/.claude/skills/create-app-skill/SKILL.md +1101 -0
- package/.claude/skills/create-insight-skill/SKILL.md +1317 -0
- package/.claude/skills/get-insight-data-skill/SKILL.md +1053 -0
- package/.claude/skills/hailer-api/SKILL.md +283 -0
- package/.claude/skills/hailer-api/references/activities.md +620 -0
- package/.claude/skills/hailer-api/references/authentication.md +216 -0
- package/.claude/skills/hailer-api/references/datasets.md +437 -0
- package/.claude/skills/hailer-api/references/files.md +301 -0
- package/.claude/skills/hailer-api/references/insights.md +469 -0
- package/.claude/skills/hailer-api/references/workflows.md +720 -0
- package/.claude/skills/hailer-api/references/workspaces-users.md +445 -0
- package/.claude/skills/insight-api/SKILL.md +185 -0
- package/.claude/skills/insight-api/references/insight-endpoints.md +514 -0
- package/.claude/skills/install-workflow-skill/SKILL.md +1056 -0
- package/.claude/skills/list-apps-skill/SKILL.md +1010 -0
- package/.claude/skills/list-workflows-minimal-skill/SKILL.md +992 -0
- package/.claude/skills/local-first-skill/SKILL.md +570 -0
- package/.claude/skills/mcp-tools/SKILL.md +419 -0
- package/.claude/skills/mcp-tools/references/api-endpoints.md +499 -0
- package/.claude/skills/mcp-tools/references/data-structures.md +554 -0
- package/.claude/skills/mcp-tools/references/implementation-patterns.md +717 -0
- package/.claude/skills/preview-insight-skill/SKILL.md +1290 -0
- package/.claude/skills/publish-hailer-app-skill/SKILL.md +453 -0
- package/.claude/skills/remove-app-member-skill/SKILL.md +671 -0
- package/.claude/skills/remove-app-skill/SKILL.md +985 -0
- package/.claude/skills/remove-insight-skill/SKILL.md +1011 -0
- package/.claude/skills/remove-workflow-skill/SKILL.md +920 -0
- package/.claude/skills/scaffold-hailer-app-skill/SKILL.md +1034 -0
- package/.claude/skills/skill-testing/README.md +137 -0
- package/.claude/skills/skill-testing/SKILL.md +348 -0
- package/.claude/skills/skill-testing/references/test-patterns.md +705 -0
- package/.claude/skills/skill-testing/references/testing-guide.md +603 -0
- package/.claude/skills/skill-testing/references/validation-checklist.md +537 -0
- package/.claude/skills/tool-builder/SKILL.md +328 -0
- package/.claude/skills/update-app-skill/SKILL.md +970 -0
- package/.claude/skills/update-workflow-field-skill/SKILL.md +1098 -0
- package/.env.example +81 -0
- package/.mcp.json +13 -0
- package/README.md +297 -0
- package/dist/app.d.ts +4 -0
- package/dist/app.js +74 -0
- package/dist/cli.d.ts +3 -0
- package/dist/cli.js +5 -0
- package/dist/client/adaptive-documentation-bot.d.ts +108 -0
- package/dist/client/adaptive-documentation-bot.js +475 -0
- package/dist/client/adaptive-documentation-types.d.ts +66 -0
- package/dist/client/adaptive-documentation-types.js +9 -0
- package/dist/client/agent-activity-bot.d.ts +51 -0
- package/dist/client/agent-activity-bot.js +166 -0
- package/dist/client/agent-tracker.d.ts +499 -0
- package/dist/client/agent-tracker.js +659 -0
- package/dist/client/description-updater.d.ts +56 -0
- package/dist/client/description-updater.js +259 -0
- package/dist/client/log-parser.d.ts +72 -0
- package/dist/client/log-parser.js +387 -0
- package/dist/client/mcp-client.d.ts +50 -0
- package/dist/client/mcp-client.js +532 -0
- package/dist/client/message-processor.d.ts +35 -0
- package/dist/client/message-processor.js +352 -0
- package/dist/client/multi-bot-manager.d.ts +24 -0
- package/dist/client/multi-bot-manager.js +74 -0
- package/dist/client/providers/anthropic-provider.d.ts +19 -0
- package/dist/client/providers/anthropic-provider.js +631 -0
- package/dist/client/providers/llm-provider.d.ts +47 -0
- package/dist/client/providers/llm-provider.js +367 -0
- package/dist/client/providers/openai-provider.d.ts +23 -0
- package/dist/client/providers/openai-provider.js +621 -0
- package/dist/client/simple-llm-caller.d.ts +19 -0
- package/dist/client/simple-llm-caller.js +100 -0
- package/dist/client/skill-generator.d.ts +81 -0
- package/dist/client/skill-generator.js +386 -0
- package/dist/client/test-adaptive-bot.d.ts +9 -0
- package/dist/client/test-adaptive-bot.js +82 -0
- package/dist/client/token-pricing.d.ts +38 -0
- package/dist/client/token-pricing.js +127 -0
- package/dist/client/token-tracker.d.ts +232 -0
- package/dist/client/token-tracker.js +457 -0
- package/dist/client/token-usage-bot.d.ts +53 -0
- package/dist/client/token-usage-bot.js +153 -0
- package/dist/client/tool-executor.d.ts +69 -0
- package/dist/client/tool-executor.js +159 -0
- package/dist/client/tool-schema-loader.d.ts +60 -0
- package/dist/client/tool-schema-loader.js +178 -0
- package/dist/client/types.d.ts +69 -0
- package/dist/client/types.js +7 -0
- package/dist/config.d.ts +162 -0
- package/dist/config.js +296 -0
- package/dist/core.d.ts +26 -0
- package/dist/core.js +147 -0
- package/dist/lib/context-manager.d.ts +111 -0
- package/dist/lib/context-manager.js +431 -0
- package/dist/lib/logger.d.ts +74 -0
- package/dist/lib/logger.js +277 -0
- package/dist/lib/materialize.d.ts +3 -0
- package/dist/lib/materialize.js +101 -0
- package/dist/lib/normalizedName.d.ts +7 -0
- package/dist/lib/normalizedName.js +48 -0
- package/dist/lib/prompt-length-manager.d.ts +81 -0
- package/dist/lib/prompt-length-manager.js +457 -0
- package/dist/lib/terminal-prompt.d.ts +9 -0
- package/dist/lib/terminal-prompt.js +108 -0
- package/dist/mcp/UserContextCache.d.ts +56 -0
- package/dist/mcp/UserContextCache.js +163 -0
- package/dist/mcp/auth.d.ts +2 -0
- package/dist/mcp/auth.js +29 -0
- package/dist/mcp/hailer-clients.d.ts +42 -0
- package/dist/mcp/hailer-clients.js +246 -0
- package/dist/mcp/signal-handler.d.ts +45 -0
- package/dist/mcp/signal-handler.js +317 -0
- package/dist/mcp/tool-registry.d.ts +100 -0
- package/dist/mcp/tool-registry.js +306 -0
- package/dist/mcp/tools/activity.d.ts +15 -0
- package/dist/mcp/tools/activity.js +955 -0
- package/dist/mcp/tools/app.d.ts +20 -0
- package/dist/mcp/tools/app.js +1488 -0
- package/dist/mcp/tools/discussion.d.ts +19 -0
- package/dist/mcp/tools/discussion.js +950 -0
- package/dist/mcp/tools/file.d.ts +15 -0
- package/dist/mcp/tools/file.js +119 -0
- package/dist/mcp/tools/insight.d.ts +17 -0
- package/dist/mcp/tools/insight.js +806 -0
- package/dist/mcp/tools/skill.d.ts +10 -0
- package/dist/mcp/tools/skill.js +279 -0
- package/dist/mcp/tools/user.d.ts +10 -0
- package/dist/mcp/tools/user.js +108 -0
- package/dist/mcp/tools/workflow-template.d.ts +19 -0
- package/dist/mcp/tools/workflow-template.js +822 -0
- package/dist/mcp/tools/workflow.d.ts +18 -0
- package/dist/mcp/tools/workflow.js +1362 -0
- package/dist/mcp/utils/api-errors.d.ts +45 -0
- package/dist/mcp/utils/api-errors.js +160 -0
- package/dist/mcp/utils/data-transformers.d.ts +102 -0
- package/dist/mcp/utils/data-transformers.js +194 -0
- package/dist/mcp/utils/file-upload.d.ts +33 -0
- package/dist/mcp/utils/file-upload.js +148 -0
- package/dist/mcp/utils/hailer-api-client.d.ts +120 -0
- package/dist/mcp/utils/hailer-api-client.js +323 -0
- package/dist/mcp/utils/index.d.ts +13 -0
- package/dist/mcp/utils/index.js +39 -0
- package/dist/mcp/utils/logger.d.ts +42 -0
- package/dist/mcp/utils/logger.js +103 -0
- package/dist/mcp/utils/types.d.ts +286 -0
- package/dist/mcp/utils/types.js +7 -0
- package/dist/mcp/workspace-cache.d.ts +42 -0
- package/dist/mcp/workspace-cache.js +97 -0
- package/dist/mcp-server.d.ts +42 -0
- package/dist/mcp-server.js +280 -0
- package/package.json +56 -0
- package/tsconfig.json +23 -0
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.UserContextCache = void 0;
|
|
4
|
+
const hailer_clients_1 = require("./hailer-clients");
|
|
5
|
+
const workspace_cache_1 = require("./workspace-cache");
|
|
6
|
+
const config_1 = require("../config");
|
|
7
|
+
const logger_1 = require("../lib/logger");
|
|
8
|
+
const index_1 = require("./utils/index");
|
|
9
|
+
const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
|
|
10
|
+
/**
|
|
11
|
+
* Cache for user-specific data (client connections, init data, workspace cache)
|
|
12
|
+
*
|
|
13
|
+
* Replaces per-user ClassBasedToolManager instances with efficient caching:
|
|
14
|
+
* - Reuses shared connection pool from hailer-clients.ts
|
|
15
|
+
* - Caches user-specific data (init, workspaceCache) separately from tool logic
|
|
16
|
+
* - Used by both MCP Server and Client for consistent user context
|
|
17
|
+
*/
|
|
18
|
+
class UserContextCache {
|
|
19
|
+
static cache = new Map();
|
|
20
|
+
/** Life of each cache unit. */
|
|
21
|
+
static DEFAULT_TTL_MS = 15 * 60 * 1000; // 15 minutes
|
|
22
|
+
/**
|
|
23
|
+
* Get or create user context (client, init, workspaceCache) for an API key
|
|
24
|
+
* This data is user-specific and should be cached per user
|
|
25
|
+
*
|
|
26
|
+
* @param apiKey - Hailer API key for authentication
|
|
27
|
+
* @param forceRefresh - If true, bypasses cache and creates fresh context (useful for testing or after permission changes)
|
|
28
|
+
* @param ttlMs - Custom TTL in milliseconds, defaults to 15 minutes
|
|
29
|
+
*/
|
|
30
|
+
static async getContext(apiKey, forceRefresh = false, ttlMs = this.DEFAULT_TTL_MS) {
|
|
31
|
+
console.log(`🔍 getContext called: apiKey=${apiKey.substring(0, 8)}..., forceRefresh=${forceRefresh}, cacheHas=${this.cache.has(apiKey)}`);
|
|
32
|
+
// Check cache first (unless forceRefresh is requested)
|
|
33
|
+
if (!forceRefresh && this.cache.has(apiKey)) {
|
|
34
|
+
const cachedContext = this.cache.get(apiKey);
|
|
35
|
+
const age = Date.now() - cachedContext.createdAt;
|
|
36
|
+
// Check if cache is still fresh
|
|
37
|
+
if (age < ttlMs) {
|
|
38
|
+
logger.debug('Using cached user context', {
|
|
39
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
40
|
+
ageMinutes: Math.round(age / (1000 * 60)),
|
|
41
|
+
ttlMinutes: Math.round(ttlMs / (1000 * 60))
|
|
42
|
+
});
|
|
43
|
+
return cachedContext;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
// Cache expired, remove stale entry
|
|
47
|
+
this.cache.delete(apiKey);
|
|
48
|
+
logger.info('Cache expired, refreshing user context', {
|
|
49
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
50
|
+
ageMinutes: Math.round(age / (1000 * 60))
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
else if (forceRefresh && this.cache.has(apiKey)) {
|
|
55
|
+
// Force refresh requested, clear existing cache
|
|
56
|
+
this.cache.delete(apiKey);
|
|
57
|
+
logger.info('Force refresh requested, clearing cached user context', {
|
|
58
|
+
apiKey: apiKey.substring(0, 8) + '...'
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
logger.info('Creating new user context', { apiKey: apiKey.substring(0, 8) + '...' });
|
|
62
|
+
try {
|
|
63
|
+
// Create client connection (uses existing connection pool)
|
|
64
|
+
const client = await (0, hailer_clients_1.createHailerClientByApiKey)(apiKey);
|
|
65
|
+
// Create API client (host extracted from client automatically)
|
|
66
|
+
const hailer = new index_1.HailerApiClient(client);
|
|
67
|
+
// Fetch user's workspace initialization data
|
|
68
|
+
const init = await client.socket.request('v2.core.init', [
|
|
69
|
+
['processes', 'users', 'network', 'networks']
|
|
70
|
+
]);
|
|
71
|
+
// Create workspace cache from init data
|
|
72
|
+
const appConfig = (0, config_1.createApplicationConfig)(); // TODO: small concern here: it creates a new instance of config instaed of reusing it, is it wise?
|
|
73
|
+
const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
|
|
74
|
+
const context = {
|
|
75
|
+
client,
|
|
76
|
+
hailer,
|
|
77
|
+
init,
|
|
78
|
+
workspaceCache,
|
|
79
|
+
apiKey,
|
|
80
|
+
createdAt: Date.now()
|
|
81
|
+
};
|
|
82
|
+
// Calculate cache sizes
|
|
83
|
+
const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
|
|
84
|
+
const workspaceCacheSize = Buffer.byteLength(JSON.stringify(workspaceCache), 'utf8');
|
|
85
|
+
const totalContextSize = Buffer.byteLength(JSON.stringify({
|
|
86
|
+
init,
|
|
87
|
+
workspaceCache
|
|
88
|
+
}), 'utf8');
|
|
89
|
+
// Log cache sizes to console
|
|
90
|
+
console.log('\n📊 === WORKSPACE CACHE SIZE ANALYSIS ===');
|
|
91
|
+
console.log(`Raw init response: ${rawInitSize.toLocaleString()} bytes (${(rawInitSize / 1024).toFixed(2)} KB)`);
|
|
92
|
+
console.log(`Workspace cache: ${workspaceCacheSize.toLocaleString()} bytes (${(workspaceCacheSize / 1024).toFixed(2)} KB)`);
|
|
93
|
+
console.log(`Total context: ${totalContextSize.toLocaleString()} bytes (${(totalContextSize / 1024).toFixed(2)} KB)`);
|
|
94
|
+
console.log(`Reduction: ${((1 - workspaceCacheSize / rawInitSize) * 100).toFixed(1)}%`);
|
|
95
|
+
console.log(`Workflows: ${init.processes?.length || 0}`);
|
|
96
|
+
console.log(`Users: ${workspaceCache.users.length}`);
|
|
97
|
+
console.log(`Workspaces: ${Object.keys(workspaceCache.allWorkspaces).length}`);
|
|
98
|
+
console.log('=====================================\n');
|
|
99
|
+
// Cache for future requests
|
|
100
|
+
this.cache.set(apiKey, context);
|
|
101
|
+
logger.info('User context created and cached', {
|
|
102
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
103
|
+
workflowCount: init.processes?.length || 0,
|
|
104
|
+
userCount: workspaceCache.users.length,
|
|
105
|
+
rawInitBytes: rawInitSize,
|
|
106
|
+
workspaceCacheBytes: workspaceCacheSize,
|
|
107
|
+
totalContextBytes: totalContextSize,
|
|
108
|
+
rawInitKB: (rawInitSize / 1024).toFixed(2),
|
|
109
|
+
workspaceCacheKB: (workspaceCacheSize / 1024).toFixed(2),
|
|
110
|
+
totalContextKB: (totalContextSize / 1024).toFixed(2)
|
|
111
|
+
});
|
|
112
|
+
return context;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
logger.error('Failed to create user context', error, {
|
|
116
|
+
apiKey: apiKey.substring(0, 8) + '...'
|
|
117
|
+
});
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Clear cache entry for specific API key (for cleanup/testing)
|
|
123
|
+
*/
|
|
124
|
+
static clearContext(apiKey) {
|
|
125
|
+
this.cache.delete(apiKey);
|
|
126
|
+
logger.debug('Cleared user context from cache', {
|
|
127
|
+
apiKey: apiKey.substring(0, 8) + '...'
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Clear all cached contexts (for cleanup/testing)
|
|
132
|
+
*/
|
|
133
|
+
static clearAll() {
|
|
134
|
+
const size = this.cache.size;
|
|
135
|
+
this.cache.clear();
|
|
136
|
+
logger.info('Cleared all user contexts from cache', { clearedCount: size });
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Get cache statistics (for monitoring and debugging)
|
|
140
|
+
*
|
|
141
|
+
* @returns Cache stats including size, API keys, and age information
|
|
142
|
+
*/
|
|
143
|
+
static getCacheStats() {
|
|
144
|
+
const now = Date.now();
|
|
145
|
+
const entries = Array.from(this.cache.entries()).map(([key, context]) => {
|
|
146
|
+
const ageMs = now - context.createdAt;
|
|
147
|
+
const ageMinutes = Math.round(ageMs / (1000 * 60));
|
|
148
|
+
const isExpired = ageMs > this.DEFAULT_TTL_MS;
|
|
149
|
+
return {
|
|
150
|
+
apiKey: key.substring(0, 8) + '...',
|
|
151
|
+
ageMinutes,
|
|
152
|
+
isExpired
|
|
153
|
+
};
|
|
154
|
+
});
|
|
155
|
+
return {
|
|
156
|
+
size: this.cache.size,
|
|
157
|
+
apiKeys: Array.from(this.cache.keys()).map(key => key.substring(0, 8) + '...'),
|
|
158
|
+
entries
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
exports.UserContextCache = UserContextCache;
|
|
163
|
+
//# sourceMappingURL=UserContextCache.js.map
|
package/dist/mcp/auth.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.safeFetch = void 0;
|
|
4
|
+
// Custom fetch function that handles self-signed certificates
|
|
5
|
+
const safeFetch = async (url, options = {}) => {
|
|
6
|
+
const isLocalDev = url.includes('local.gd') || url.includes('localhost');
|
|
7
|
+
if (isLocalDev) {
|
|
8
|
+
// For Next.js/Node.js environment with self-signed certs
|
|
9
|
+
// We need to set the environment variable before making the request
|
|
10
|
+
const originalValue = process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
11
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = '0';
|
|
12
|
+
try {
|
|
13
|
+
const response = await fetch(url, options);
|
|
14
|
+
return response;
|
|
15
|
+
}
|
|
16
|
+
finally {
|
|
17
|
+
// Restore the original value
|
|
18
|
+
if (originalValue !== undefined) {
|
|
19
|
+
process.env.NODE_TLS_REJECT_UNAUTHORIZED = originalValue;
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
delete process.env.NODE_TLS_REJECT_UNAUTHORIZED;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
return fetch(url, options);
|
|
27
|
+
};
|
|
28
|
+
exports.safeFetch = safeFetch;
|
|
29
|
+
//# sourceMappingURL=auth.js.map
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { Client } from "@hailer/cli";
|
|
2
|
+
export interface HailerRestClient {
|
|
3
|
+
fetch: (url: string, options?: RequestInit) => Promise<Response>;
|
|
4
|
+
sessionKey: string;
|
|
5
|
+
}
|
|
6
|
+
export interface HailerClient {
|
|
7
|
+
socket: Client;
|
|
8
|
+
rest: HailerRestClient;
|
|
9
|
+
sessionKey: string;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Get the current user ID from the authenticated session
|
|
13
|
+
* This is called after authentication to retrieve the user ID automatically
|
|
14
|
+
*/
|
|
15
|
+
export declare function getCurrentUserId(client: HailerClient): Promise<string>;
|
|
16
|
+
/** Hailer Client for a specific account with listeners and basic handling logic */
|
|
17
|
+
export declare class HailerClientManager {
|
|
18
|
+
private host;
|
|
19
|
+
private username;
|
|
20
|
+
private password;
|
|
21
|
+
private socketClient;
|
|
22
|
+
private restClient;
|
|
23
|
+
private signalHandlers;
|
|
24
|
+
constructor(host: string, username: string, password: string);
|
|
25
|
+
connect(): Promise<HailerClient>;
|
|
26
|
+
private setupSignalHandling;
|
|
27
|
+
onSignal(eventType: string, handler: (data: any) => void): void;
|
|
28
|
+
offSignal(eventType: string, handler: (data: any) => void): void;
|
|
29
|
+
disconnect(): void;
|
|
30
|
+
isConnected(): boolean;
|
|
31
|
+
getClient(): HailerClient | null;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create Hailer client by API key (O(1) lookup)
|
|
35
|
+
* This is the new efficient method for MCP Server to get connections
|
|
36
|
+
*/
|
|
37
|
+
export declare const createHailerClientByApiKey: (apiKey: string) => Promise<HailerClient>;
|
|
38
|
+
/**
|
|
39
|
+
* Disconnect client by API key (new unified approach)
|
|
40
|
+
*/
|
|
41
|
+
export declare const disconnectHailerClientByApiKey: (apiKey: string) => void;
|
|
42
|
+
//# sourceMappingURL=hailer-clients.d.ts.map
|
|
@@ -0,0 +1,246 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.disconnectHailerClientByApiKey = exports.createHailerClientByApiKey = exports.HailerClientManager = void 0;
|
|
4
|
+
exports.getCurrentUserId = getCurrentUserId;
|
|
5
|
+
const cli_1 = require("@hailer/cli");
|
|
6
|
+
const auth_1 = require("./auth");
|
|
7
|
+
const logger_1 = require("../lib/logger");
|
|
8
|
+
const config_1 = require("../config");
|
|
9
|
+
const logger = (0, logger_1.createLogger)({ component: 'hailer-clients' });
|
|
10
|
+
/**
|
|
11
|
+
* Get the current user ID from the authenticated session
|
|
12
|
+
* This is called after authentication to retrieve the user ID automatically
|
|
13
|
+
*/
|
|
14
|
+
async function getCurrentUserId(client) {
|
|
15
|
+
try {
|
|
16
|
+
// Use the socket client to get initialization data with the current user
|
|
17
|
+
// According to the API docs, requesting "user" returns the current authenticated user
|
|
18
|
+
const init = (await client.socket.request("v2.core.init", [
|
|
19
|
+
["user"],
|
|
20
|
+
]));
|
|
21
|
+
// Check if there's a user field (this is the current authenticated user)
|
|
22
|
+
if (init.user && init.user._id) {
|
|
23
|
+
logger.info('Retrieved user ID from init', { userId: init.user._id });
|
|
24
|
+
return init.user._id;
|
|
25
|
+
}
|
|
26
|
+
// Fallback: Try with different parameters if the above doesn't work
|
|
27
|
+
logger.warn('User not found in init.user, trying alternative approach');
|
|
28
|
+
// Method 2: Try to get current user from a profile/me endpoint
|
|
29
|
+
try {
|
|
30
|
+
const profileResponse = await client.rest.fetch("/api/user/me", {
|
|
31
|
+
method: "GET",
|
|
32
|
+
});
|
|
33
|
+
if (profileResponse.ok) {
|
|
34
|
+
const profile = (await profileResponse.json());
|
|
35
|
+
if (profile && profile._id) {
|
|
36
|
+
logger.info('Retrieved user ID from profile endpoint', { userId: profile._id });
|
|
37
|
+
return profile._id;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
logger.warn('Profile endpoint not available, trying network method');
|
|
43
|
+
}
|
|
44
|
+
// Method 3: Try with network data to infer from workspace membership
|
|
45
|
+
const initWithNetwork = (await client.socket.request("v2.core.init", [
|
|
46
|
+
["network"],
|
|
47
|
+
]));
|
|
48
|
+
if (initWithNetwork.network && initWithNetwork.network.members) {
|
|
49
|
+
const members = initWithNetwork.network.members;
|
|
50
|
+
if (members.length === 1) {
|
|
51
|
+
// If there's only one member, it's likely the current user
|
|
52
|
+
const member = members[0];
|
|
53
|
+
const userId = member.uid || member._id || member.id;
|
|
54
|
+
if (userId) {
|
|
55
|
+
logger.info('Inferred user ID from single workspace member', { userId });
|
|
56
|
+
return userId;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
// If all methods fail, throw an error with helpful information
|
|
61
|
+
throw new Error("Could not determine current user ID from any available method. Check API credentials and permissions.");
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
logger.error('Failed to get current user ID', error);
|
|
65
|
+
throw new Error(`Failed to retrieve current user ID: ${error instanceof Error ? error.message : String(error)}`);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
// manager is supposed to manage many instances, isn't it? a bit weird naming as for me but not sure
|
|
69
|
+
/** Hailer Client for a specific account with listeners and basic handling logic */
|
|
70
|
+
class HailerClientManager {
|
|
71
|
+
host;
|
|
72
|
+
username;
|
|
73
|
+
password;
|
|
74
|
+
socketClient = null;
|
|
75
|
+
restClient = null;
|
|
76
|
+
signalHandlers = new Map();
|
|
77
|
+
constructor(host, username, password) {
|
|
78
|
+
this.host = host;
|
|
79
|
+
this.username = username;
|
|
80
|
+
this.password = password;
|
|
81
|
+
}
|
|
82
|
+
async connect() {
|
|
83
|
+
// Check if this is a local development environment
|
|
84
|
+
const isLocalDev = this.host.includes("local.gd") || this.host.includes("localhost");
|
|
85
|
+
const clientOptions = {
|
|
86
|
+
host: this.host,
|
|
87
|
+
username: this.username,
|
|
88
|
+
password: this.password,
|
|
89
|
+
...(isLocalDev && { rejectUnauthorized: false }), // Add for local dev with self-signed certs
|
|
90
|
+
};
|
|
91
|
+
try {
|
|
92
|
+
// Create socket client using @hailer/cli with timeout
|
|
93
|
+
this.socketClient = (await Promise.race([
|
|
94
|
+
cli_1.Client.create(clientOptions),
|
|
95
|
+
new Promise((_, reject) => setTimeout(() => reject(new Error("Socket connection timeout after 30s")), 30000)),
|
|
96
|
+
]));
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
logger.error('Failed to create socket client', error, { username: this.username });
|
|
100
|
+
throw error;
|
|
101
|
+
}
|
|
102
|
+
if (!this.socketClient.sessionKey) {
|
|
103
|
+
throw new Error("Socket client connected but no session key received");
|
|
104
|
+
}
|
|
105
|
+
// Create REST client that uses the same session key
|
|
106
|
+
this.restClient = {
|
|
107
|
+
fetch: (url, options = {}) => {
|
|
108
|
+
const headers = {
|
|
109
|
+
...options.headers,
|
|
110
|
+
hlrkey: this.socketClient.sessionKey,
|
|
111
|
+
"Content-Type": "application/json",
|
|
112
|
+
};
|
|
113
|
+
return (0, auth_1.safeFetch)(url, { ...options, headers });
|
|
114
|
+
},
|
|
115
|
+
sessionKey: this.socketClient.sessionKey,
|
|
116
|
+
};
|
|
117
|
+
// Set up signal handling
|
|
118
|
+
this.setupSignalHandling();
|
|
119
|
+
return {
|
|
120
|
+
socket: this.socketClient,
|
|
121
|
+
rest: this.restClient,
|
|
122
|
+
sessionKey: this.socketClient.sessionKey,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
setupSignalHandling() {
|
|
126
|
+
if (!this.socketClient)
|
|
127
|
+
return;
|
|
128
|
+
this.socketClient.on("signals", (signal) => {
|
|
129
|
+
const [eventType, eventData] = signal;
|
|
130
|
+
// Dispatch to registered handlers
|
|
131
|
+
const handlers = this.signalHandlers.get(eventType);
|
|
132
|
+
if (handlers) {
|
|
133
|
+
handlers.forEach((handler) => {
|
|
134
|
+
try {
|
|
135
|
+
handler(eventData);
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
logger.error('Signal handler failed', error);
|
|
139
|
+
}
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
// Register a handler for specific signal types
|
|
145
|
+
onSignal(eventType, handler) {
|
|
146
|
+
if (!this.signalHandlers.has(eventType)) {
|
|
147
|
+
this.signalHandlers.set(eventType, []);
|
|
148
|
+
}
|
|
149
|
+
this.signalHandlers.get(eventType).push(handler);
|
|
150
|
+
}
|
|
151
|
+
// Remove a signal handler
|
|
152
|
+
offSignal(eventType, handler) {
|
|
153
|
+
const handlers = this.signalHandlers.get(eventType);
|
|
154
|
+
if (handlers) {
|
|
155
|
+
const index = handlers.indexOf(handler);
|
|
156
|
+
if (index > -1) {
|
|
157
|
+
handlers.splice(index, 1);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
disconnect() {
|
|
162
|
+
if (this.socketClient) {
|
|
163
|
+
this.socketClient.disconnect();
|
|
164
|
+
this.socketClient = null;
|
|
165
|
+
}
|
|
166
|
+
this.restClient = null;
|
|
167
|
+
this.signalHandlers.clear();
|
|
168
|
+
}
|
|
169
|
+
isConnected() {
|
|
170
|
+
return this.socketClient !== null && this.restClient !== null;
|
|
171
|
+
}
|
|
172
|
+
getClient() {
|
|
173
|
+
if (!this.socketClient ||
|
|
174
|
+
!this.restClient ||
|
|
175
|
+
!this.socketClient.sessionKey) {
|
|
176
|
+
return null;
|
|
177
|
+
}
|
|
178
|
+
return {
|
|
179
|
+
socket: this.socketClient,
|
|
180
|
+
rest: this.restClient,
|
|
181
|
+
sessionKey: this.socketClient.sessionKey,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
exports.HailerClientManager = HailerClientManager;
|
|
186
|
+
// Connection pool for the MCP server
|
|
187
|
+
const connectionPool = new Map();
|
|
188
|
+
/**
|
|
189
|
+
* Create Hailer client by API key (O(1) lookup)
|
|
190
|
+
* This is the new efficient method for MCP Server to get connections
|
|
191
|
+
*/
|
|
192
|
+
const createHailerClientByApiKey = async (apiKey) => {
|
|
193
|
+
// O(1) lookup in Map-based CLIENT_CONFIGS
|
|
194
|
+
const account = config_1.environment.CLIENT_CONFIGS[apiKey];
|
|
195
|
+
if (!account) {
|
|
196
|
+
throw new Error(`No Hailer account found for API key: ${apiKey}`);
|
|
197
|
+
}
|
|
198
|
+
logger.debug('Creating clients for API key', {
|
|
199
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
200
|
+
email: account.email,
|
|
201
|
+
host: account.apiBaseUrl
|
|
202
|
+
});
|
|
203
|
+
// Use API key as connection key for direct mapping
|
|
204
|
+
const connectionKey = apiKey;
|
|
205
|
+
// Check if we have an existing connection
|
|
206
|
+
let clientManager = connectionPool.get(connectionKey);
|
|
207
|
+
if (clientManager && clientManager.isConnected()) {
|
|
208
|
+
logger.info('Reusing existing connection', {
|
|
209
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
210
|
+
email: account.email
|
|
211
|
+
});
|
|
212
|
+
return clientManager.getClient();
|
|
213
|
+
}
|
|
214
|
+
// Clear any stale connection
|
|
215
|
+
if (clientManager) {
|
|
216
|
+
logger.info('Cleaning up stale connection', {
|
|
217
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
218
|
+
email: account.email
|
|
219
|
+
});
|
|
220
|
+
clientManager.disconnect();
|
|
221
|
+
connectionPool.delete(connectionKey);
|
|
222
|
+
}
|
|
223
|
+
// Create new connection
|
|
224
|
+
logger.info('Creating new connection', {
|
|
225
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
226
|
+
email: account.email,
|
|
227
|
+
host: account.apiBaseUrl
|
|
228
|
+
});
|
|
229
|
+
clientManager = new HailerClientManager(account.apiBaseUrl, account.email, account.password);
|
|
230
|
+
await clientManager.connect();
|
|
231
|
+
connectionPool.set(connectionKey, clientManager);
|
|
232
|
+
return clientManager.getClient();
|
|
233
|
+
};
|
|
234
|
+
exports.createHailerClientByApiKey = createHailerClientByApiKey;
|
|
235
|
+
/**
|
|
236
|
+
* Disconnect client by API key (new unified approach)
|
|
237
|
+
*/
|
|
238
|
+
const disconnectHailerClientByApiKey = (apiKey) => {
|
|
239
|
+
const clientManager = connectionPool.get(apiKey);
|
|
240
|
+
if (clientManager) {
|
|
241
|
+
clientManager.disconnect();
|
|
242
|
+
connectionPool.delete(apiKey);
|
|
243
|
+
}
|
|
244
|
+
};
|
|
245
|
+
exports.disconnectHailerClientByApiKey = disconnectHailerClientByApiKey;
|
|
246
|
+
//# sourceMappingURL=hailer-clients.js.map
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { WorkspaceCache } from './workspace-cache';
|
|
2
|
+
import { HailerClient } from './hailer-clients';
|
|
3
|
+
/**
|
|
4
|
+
* Signal types that Hailer emits via socket.io
|
|
5
|
+
*/
|
|
6
|
+
export type HailerSignalType = 'activities.updated' | 'activities.created' | 'activities.deleted' | 'discussion.message' | 'messenger.new' | 'user.joined' | 'user.left' | 'workspace.updated' | 'process.updated' | 'cache.invalidate';
|
|
7
|
+
export interface HailerSignal {
|
|
8
|
+
type: HailerSignalType;
|
|
9
|
+
data: any;
|
|
10
|
+
timestamp: number;
|
|
11
|
+
workspaceId?: string;
|
|
12
|
+
}
|
|
13
|
+
export interface SignalSubscription {
|
|
14
|
+
id: string;
|
|
15
|
+
types: HailerSignalType[];
|
|
16
|
+
handler: (signal: HailerSignal) => void;
|
|
17
|
+
workspaceId?: string;
|
|
18
|
+
}
|
|
19
|
+
export declare class SignalHandler {
|
|
20
|
+
private client;
|
|
21
|
+
private workspaceCache?;
|
|
22
|
+
private subscriptions;
|
|
23
|
+
private signalHistory;
|
|
24
|
+
private maxHistorySize;
|
|
25
|
+
constructor(client: HailerClient, workspaceCache?: WorkspaceCache | undefined);
|
|
26
|
+
private initializeSignalHandling;
|
|
27
|
+
private handleIncomingSignal;
|
|
28
|
+
private addToHistory;
|
|
29
|
+
private dispatchSignal;
|
|
30
|
+
private processBuiltinSignals;
|
|
31
|
+
private handleActivityUpdated;
|
|
32
|
+
private handleActivityCreated;
|
|
33
|
+
private handleDiscussionMessage;
|
|
34
|
+
private handleWorkspaceUpdated;
|
|
35
|
+
private handleMessengerNew;
|
|
36
|
+
private handleCacheInvalidate;
|
|
37
|
+
subscribe(id: string, types: HailerSignalType[], handler: (signal: HailerSignal) => void, workspaceId?: string): void;
|
|
38
|
+
unsubscribe(id: string): boolean;
|
|
39
|
+
getSignalHistory(types?: HailerSignalType[], limit?: number, workspaceId?: string): HailerSignal[];
|
|
40
|
+
getActiveSubscriptions(): SignalSubscription[];
|
|
41
|
+
clearHistory(): void;
|
|
42
|
+
createMcpSignalTools(server: any): void;
|
|
43
|
+
}
|
|
44
|
+
export declare const createSignalHandler: (clients: HailerClient, workspaceCache?: WorkspaceCache) => SignalHandler;
|
|
45
|
+
//# sourceMappingURL=signal-handler.d.ts.map
|