@hailer/mcp 1.1.11 → 1.1.13
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/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +12 -1
- package/dist/bot/bot-config.js +98 -14
- package/dist/bot/bot-manager.d.ts +13 -3
- package/dist/bot/bot-manager.js +80 -25
- package/dist/bot/bot.d.ts +46 -0
- package/dist/bot/bot.js +542 -166
- package/dist/bot/services/message-classifier.js +17 -0
- package/dist/bot/services/permission-guard.d.ts +52 -0
- package/dist/bot/services/permission-guard.js +149 -0
- package/dist/bot/services/types.d.ts +5 -0
- package/dist/bot/services/typing-indicator.d.ts +6 -1
- package/dist/bot/services/typing-indicator.js +19 -3
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/mcp/UserContextCache.d.ts +5 -0
- package/dist/mcp/UserContextCache.js +51 -19
- package/dist/mcp/hailer-clients.d.ts +19 -1
- package/dist/mcp/hailer-clients.js +157 -20
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +12 -12
- package/dist/mcp/tool-registry.d.ts +17 -4
- package/dist/mcp/tool-registry.js +37 -7
- package/dist/mcp/tools/activity.js +99 -7
- package/dist/mcp/tools/app-scaffold.js +304 -336
- package/dist/mcp/tools/company.d.ts +9 -0
- package/dist/mcp/tools/company.js +88 -0
- package/dist/mcp/tools/discussion.js +68 -0
- package/dist/mcp/tools/workflow-permissions.d.ts +15 -0
- package/dist/mcp/tools/workflow-permissions.js +204 -0
- package/dist/mcp/tools/workflow.js +57 -18
- package/dist/mcp/utils/index.d.ts +2 -0
- package/dist/mcp/utils/index.js +12 -1
- package/dist/mcp/utils/role-utils.d.ts +74 -0
- package/dist/mcp/utils/role-utils.js +151 -0
- package/dist/mcp/utils/types.d.ts +43 -1
- package/dist/mcp/utils/types.js +14 -0
- package/dist/mcp/webhook-handler.d.ts +6 -0
- package/dist/mcp/webhook-handler.js +11 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -111
- package/dist/plugins/vipunen/client.d.ts +150 -0
- package/dist/plugins/vipunen/client.js +535 -0
- package/dist/plugins/vipunen/config/schema-config.json +19 -0
- package/dist/plugins/vipunen/config/schema-doc.json +22 -0
- package/dist/plugins/vipunen/index.d.ts +41 -0
- package/dist/plugins/vipunen/index.js +88 -0
- package/dist/plugins/vipunen/tools.d.ts +26 -0
- package/dist/plugins/vipunen/tools.js +501 -0
- package/package.json +2 -1
- package/.claude/.context-watchdog.json +0 -1
- package/.claude/.session-checked +0 -1
- package/.claude/CLAUDE.md +0 -370
- package/.claude/agents/agent-ada-skill-builder.md +0 -94
- package/.claude/agents/agent-alejandro-function-fields.md +0 -342
- package/.claude/agents/agent-bjorn-config-audit.md +0 -103
- package/.claude/agents/agent-builder-agent-creator.md +0 -130
- package/.claude/agents/agent-code-simplifier.md +0 -53
- package/.claude/agents/agent-dmitri-activity-crud.md +0 -159
- package/.claude/agents/agent-giuseppe-app-builder.md +0 -247
- package/.claude/agents/agent-gunther-mcp-tools.md +0 -39
- package/.claude/agents/agent-helga-workflow-config.md +0 -204
- package/.claude/agents/agent-igor-activity-mover-automation.md +0 -125
- package/.claude/agents/agent-ingrid-doc-templates.md +0 -261
- package/.claude/agents/agent-ivan-monolith.md +0 -154
- package/.claude/agents/agent-kenji-data-reader.md +0 -86
- package/.claude/agents/agent-lars-code-inspector.md +0 -102
- package/.claude/agents/agent-marco-mockup-builder.md +0 -110
- package/.claude/agents/agent-marcus-api-documenter.md +0 -323
- package/.claude/agents/agent-marketplace-publisher.md +0 -280
- package/.claude/agents/agent-marketplace-reviewer.md +0 -309
- package/.claude/agents/agent-permissions-handler.md +0 -208
- package/.claude/agents/agent-simple-writer.md +0 -48
- package/.claude/agents/agent-svetlana-code-review.md +0 -171
- package/.claude/agents/agent-tanya-test-runner.md +0 -333
- package/.claude/agents/agent-ui-designer.md +0 -100
- package/.claude/agents/agent-viktor-sql-insights.md +0 -212
- package/.claude/agents/agent-web-search.md +0 -55
- package/.claude/agents/agent-yevgeni-discussions.md +0 -45
- package/.claude/agents/agent-zara-zapier.md +0 -159
- package/.claude/commands/app-squad.md +0 -135
- package/.claude/commands/audit-squad.md +0 -158
- package/.claude/commands/autoplan.md +0 -563
- package/.claude/commands/cleanup-squad.md +0 -98
- package/.claude/commands/config-squad.md +0 -106
- package/.claude/commands/crud-squad.md +0 -87
- package/.claude/commands/data-squad.md +0 -97
- package/.claude/commands/debug-squad.md +0 -303
- package/.claude/commands/doc-squad.md +0 -65
- package/.claude/commands/handoff.md +0 -137
- package/.claude/commands/health.md +0 -49
- package/.claude/commands/help.md +0 -29
- package/.claude/commands/help:agents.md +0 -151
- package/.claude/commands/help:commands.md +0 -78
- package/.claude/commands/help:faq.md +0 -79
- package/.claude/commands/help:plugins.md +0 -50
- package/.claude/commands/help:skills.md +0 -93
- package/.claude/commands/help:tools.md +0 -75
- package/.claude/commands/hotfix-squad.md +0 -112
- package/.claude/commands/integration-squad.md +0 -82
- package/.claude/commands/janitor-squad.md +0 -167
- package/.claude/commands/learn-auto.md +0 -120
- package/.claude/commands/learn.md +0 -120
- package/.claude/commands/mcp-list.md +0 -27
- package/.claude/commands/onboard-squad.md +0 -140
- package/.claude/commands/plan-workspace.md +0 -732
- package/.claude/commands/prd.md +0 -130
- package/.claude/commands/project-status.md +0 -82
- package/.claude/commands/publish.md +0 -138
- package/.claude/commands/recap.md +0 -69
- package/.claude/commands/restore.md +0 -64
- package/.claude/commands/review-squad.md +0 -152
- package/.claude/commands/save.md +0 -24
- package/.claude/commands/stats.md +0 -19
- package/.claude/commands/swarm.md +0 -210
- package/.claude/commands/tool-builder.md +0 -39
- package/.claude/commands/ws-pull.md +0 -44
- package/.claude/hooks/_shared-memory.cjs +0 -305
- package/.claude/hooks/_utils.cjs +0 -108
- package/.claude/hooks/agent-failure-detector.cjs +0 -383
- package/.claude/hooks/agent-usage-logger.cjs +0 -204
- package/.claude/hooks/app-edit-guard.cjs +0 -494
- package/.claude/hooks/auto-learn.cjs +0 -304
- package/.claude/hooks/bash-guard.cjs +0 -272
- package/.claude/hooks/builder-mode-manager.cjs +0 -354
- package/.claude/hooks/bulk-activity-guard.cjs +0 -271
- package/.claude/hooks/context-watchdog.cjs +0 -230
- package/.claude/hooks/delegation-reminder.cjs +0 -465
- package/.claude/hooks/design-system-lint.cjs +0 -271
- package/.claude/hooks/post-scaffold-hook.cjs +0 -181
- package/.claude/hooks/prompt-guard.cjs +0 -354
- package/.claude/hooks/publish-template-guard.cjs +0 -147
- package/.claude/hooks/session-start.cjs +0 -35
- package/.claude/hooks/shared-memory-writer.cjs +0 -147
- package/.claude/hooks/skill-injector.cjs +0 -140
- package/.claude/hooks/skill-usage-logger.cjs +0 -258
- package/.claude/hooks/src-edit-guard.cjs +0 -240
- package/.claude/hooks/sync-marketplace-agents.cjs +0 -346
- package/.claude/settings.json +0 -257
- package/.claude/skills/SDK-activity-patterns/SKILL.md +0 -428
- package/.claude/skills/SDK-document-templates/SKILL.md +0 -1033
- package/.claude/skills/SDK-function-fields/SKILL.md +0 -542
- package/.claude/skills/SDK-generate-skill/SKILL.md +0 -92
- package/.claude/skills/SDK-init-skill/SKILL.md +0 -127
- package/.claude/skills/SDK-insight-queries/SKILL.md +0 -787
- package/.claude/skills/SDK-ws-config-skill/SKILL.md +0 -1139
- package/.claude/skills/agent-structure/SKILL.md +0 -98
- package/.claude/skills/api-documentation-patterns/SKILL.md +0 -474
- package/.claude/skills/chrome-mcp-reference/SKILL.md +0 -370
- package/.claude/skills/delegation-routing/SKILL.md +0 -202
- package/.claude/skills/frontend-design/SKILL.md +0 -254
- package/.claude/skills/hailer-activity-mover/SKILL.md +0 -213
- package/.claude/skills/hailer-api-client/SKILL.md +0 -518
- package/.claude/skills/hailer-app-builder/SKILL.md +0 -1434
- package/.claude/skills/hailer-apps-pictures/SKILL.md +0 -269
- package/.claude/skills/hailer-design-system/SKILL.md +0 -235
- package/.claude/skills/hailer-monolith-automations/SKILL.md +0 -686
- package/.claude/skills/hailer-permissions-system/SKILL.md +0 -121
- package/.claude/skills/hailer-project-protocol/SKILL.md +0 -488
- package/.claude/skills/hailer-rest-api/SKILL.md +0 -61
- package/.claude/skills/hailer-rest-api/hailer-activities.md +0 -184
- package/.claude/skills/hailer-rest-api/hailer-admin.md +0 -473
- package/.claude/skills/hailer-rest-api/hailer-calendar.md +0 -256
- package/.claude/skills/hailer-rest-api/hailer-feed.md +0 -249
- package/.claude/skills/hailer-rest-api/hailer-insights.md +0 -195
- package/.claude/skills/hailer-rest-api/hailer-messaging.md +0 -276
- package/.claude/skills/hailer-rest-api/hailer-workflows.md +0 -283
- package/.claude/skills/insight-join-patterns/SKILL.md +0 -174
- package/.claude/skills/integration-patterns/SKILL.md +0 -421
- package/.claude/skills/json-only-output/SKILL.md +0 -72
- package/.claude/skills/lsp-setup/SKILL.md +0 -160
- package/.claude/skills/mcp-direct-tools/SKILL.md +0 -153
- package/.claude/skills/optional-parameters/SKILL.md +0 -72
- package/.claude/skills/publish-hailer-app/SKILL.md +0 -244
- package/.claude/skills/testing-patterns/SKILL.md +0 -630
- package/.claude/skills/tool-builder/SKILL.md +0 -250
- package/.claude/skills/tool-parameter-usage/SKILL.md +0 -126
- package/.claude/skills/tool-response-verification/SKILL.md +0 -92
- package/.claude/skills/zapier-hailer-patterns/SKILL.md +0 -581
- package/.hailer-mcp-port +0 -1
- package/.mcp.json +0 -13
- package/.opencode/agent/agent-ada-skill-builder.md +0 -35
- package/.opencode/agent/agent-alejandro-function-fields.md +0 -39
- package/.opencode/agent/agent-bjorn-config-audit.md +0 -36
- package/.opencode/agent/agent-builder-agent-creator.md +0 -39
- package/.opencode/agent/agent-code-simplifier.md +0 -31
- package/.opencode/agent/agent-dmitri-activity-crud.md +0 -40
- package/.opencode/agent/agent-giuseppe-app-builder.md +0 -37
- package/.opencode/agent/agent-gunther-mcp-tools.md +0 -39
- package/.opencode/agent/agent-helga-workflow-config.md +0 -204
- package/.opencode/agent/agent-igor-activity-mover-automation.md +0 -46
- package/.opencode/agent/agent-ingrid-doc-templates.md +0 -39
- package/.opencode/agent/agent-ivan-monolith.md +0 -46
- package/.opencode/agent/agent-kenji-data-reader.md +0 -53
- package/.opencode/agent/agent-lars-code-inspector.md +0 -28
- package/.opencode/agent/agent-marco-mockup-builder.md +0 -42
- package/.opencode/agent/agent-marcus-api-documenter.md +0 -53
- package/.opencode/agent/agent-marketplace-publisher.md +0 -44
- package/.opencode/agent/agent-marketplace-reviewer.md +0 -42
- package/.opencode/agent/agent-permissions-handler.md +0 -50
- package/.opencode/agent/agent-simple-writer.md +0 -45
- package/.opencode/agent/agent-svetlana-code-review.md +0 -39
- package/.opencode/agent/agent-tanya-test-runner.md +0 -57
- package/.opencode/agent/agent-ui-designer.md +0 -56
- package/.opencode/agent/agent-viktor-sql-insights.md +0 -34
- package/.opencode/agent/agent-web-search.md +0 -42
- package/.opencode/agent/agent-yevgeni-discussions.md +0 -37
- package/.opencode/agent/agent-zara-zapier.md +0 -53
- package/.opencode/commands/app-squad.md +0 -135
- package/.opencode/commands/audit-squad.md +0 -158
- package/.opencode/commands/autoplan.md +0 -563
- package/.opencode/commands/cleanup-squad.md +0 -98
- package/.opencode/commands/config-squad.md +0 -106
- package/.opencode/commands/crud-squad.md +0 -87
- package/.opencode/commands/data-squad.md +0 -97
- package/.opencode/commands/debug-squad.md +0 -303
- package/.opencode/commands/doc-squad.md +0 -65
- package/.opencode/commands/handoff.md +0 -137
- package/.opencode/commands/health.md +0 -49
- package/.opencode/commands/help-agents.md +0 -151
- package/.opencode/commands/help-commands.md +0 -32
- package/.opencode/commands/help-faq.md +0 -29
- package/.opencode/commands/help-plugins.md +0 -28
- package/.opencode/commands/help-skills.md +0 -7
- package/.opencode/commands/help-tools.md +0 -40
- package/.opencode/commands/help.md +0 -28
- package/.opencode/commands/hotfix-squad.md +0 -112
- package/.opencode/commands/integration-squad.md +0 -82
- package/.opencode/commands/janitor-squad.md +0 -167
- package/.opencode/commands/learn-auto.md +0 -120
- package/.opencode/commands/learn.md +0 -120
- package/.opencode/commands/mcp-list.md +0 -27
- package/.opencode/commands/onboard-squad.md +0 -140
- package/.opencode/commands/plan-workspace.md +0 -732
- package/.opencode/commands/prd.md +0 -131
- package/.opencode/commands/project-status.md +0 -82
- package/.opencode/commands/publish.md +0 -138
- package/.opencode/commands/recap.md +0 -69
- package/.opencode/commands/restore.md +0 -64
- package/.opencode/commands/review-squad.md +0 -152
- package/.opencode/commands/save.md +0 -24
- package/.opencode/commands/stats.md +0 -19
- package/.opencode/commands/swarm.md +0 -210
- package/.opencode/commands/tool-builder.md +0 -39
- package/.opencode/commands/ws-pull.md +0 -44
- package/.opencode/opencode.json +0 -21
- package/inbox/failures.log +0 -1
- package/inbox/usage.jsonl +0 -4
- package/scripts/postinstall.cjs +0 -64
- package/scripts/test-hal-tools.ts +0 -154
|
@@ -59,6 +59,22 @@ class MessageClassifier {
|
|
|
59
59
|
messages = retryMessages;
|
|
60
60
|
}
|
|
61
61
|
messageContent = targetMessage.msg || targetMessage.content || "";
|
|
62
|
+
// Extract forwarded message content — forwarded messages have empty msg
|
|
63
|
+
if (!messageContent && targetMessage.forwardMessage) {
|
|
64
|
+
let fwd = targetMessage.forwardMessage;
|
|
65
|
+
const fwdParts = [];
|
|
66
|
+
let depth = 0;
|
|
67
|
+
while (fwd && depth < 10) {
|
|
68
|
+
const fwdText = fwd.msg || fwd.content || '';
|
|
69
|
+
if (fwdText)
|
|
70
|
+
fwdParts.push(fwdText);
|
|
71
|
+
fwd = fwd.forwardMessage;
|
|
72
|
+
depth++;
|
|
73
|
+
}
|
|
74
|
+
if (fwdParts.length > 0) {
|
|
75
|
+
messageContent = `[Forwarded message] ${fwdParts.join(' → ')}`;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
62
78
|
// Extract file attachments if present
|
|
63
79
|
if (targetMessage.files && Array.isArray(targetMessage.files) && targetMessage.files.length > 0) {
|
|
64
80
|
fileAttachments = targetMessage.files.map((file) => ({
|
|
@@ -169,6 +185,7 @@ class MessageClassifier {
|
|
|
169
185
|
isReplyToBot,
|
|
170
186
|
isMention,
|
|
171
187
|
isDirectMessage,
|
|
188
|
+
isPrivateDiscussion: !!(discData?.private),
|
|
172
189
|
fileAttachments: fileAttachments.length > 0 ? fileAttachments : undefined,
|
|
173
190
|
};
|
|
174
191
|
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Unified permission guard for bot tool execution.
|
|
3
|
+
*
|
|
4
|
+
* Enforces the workspace permission system: bot scope ceiling + per-user
|
|
5
|
+
* workflow access from the permission index built from core.init.
|
|
6
|
+
*
|
|
7
|
+
* Three levels of checking:
|
|
8
|
+
* 1. Pre-execution: extract workflow IDs from tool args, deny if user lacks access
|
|
9
|
+
* 2. Post-execution: extract workflow IDs from tool results (when not in args)
|
|
10
|
+
* 3. Post-filter: strip items from list results that the user can't see
|
|
11
|
+
*/
|
|
12
|
+
export interface PermissionDenied {
|
|
13
|
+
workflowId: string;
|
|
14
|
+
reason: 'bot-scope' | string;
|
|
15
|
+
}
|
|
16
|
+
export interface PermissionContext {
|
|
17
|
+
/** Bot's allowed workflows (empty = all) */
|
|
18
|
+
allowedWorkflows: string[];
|
|
19
|
+
/** Workflow ID → Set of user IDs who can access it. Missing = open to all. */
|
|
20
|
+
permissionIndex: Map<string, Set<string>>;
|
|
21
|
+
/** Workspace admin user IDs */
|
|
22
|
+
adminUserIds: Set<string>;
|
|
23
|
+
/** Workspace owner user IDs */
|
|
24
|
+
ownerUserIds: Set<string>;
|
|
25
|
+
/** Insight ID → Set of source workflow IDs */
|
|
26
|
+
insightWorkflowCache: Map<string, Set<string>>;
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* Build the permission index from core.init data.
|
|
30
|
+
*
|
|
31
|
+
* Returns: { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds }
|
|
32
|
+
*
|
|
33
|
+
* permissionIndex only contains RESTRICTED workflows (those with explicit member lists).
|
|
34
|
+
* If a workflow is not in the index, it's open to all workspace members.
|
|
35
|
+
*/
|
|
36
|
+
export declare function buildPermissionIndex(init: any, workspaceId: string): {
|
|
37
|
+
permissionIndex: Map<string, Set<string>>;
|
|
38
|
+
adminUserIds: Set<string>;
|
|
39
|
+
ownerUserIds: Set<string>;
|
|
40
|
+
workspaceMemberIds: Set<string>;
|
|
41
|
+
};
|
|
42
|
+
/**
|
|
43
|
+
* Extract all workflow IDs from tool args, regardless of shape.
|
|
44
|
+
* Handles: top-level workflowId, sources[].workflowId, insightId → cached workflows.
|
|
45
|
+
*/
|
|
46
|
+
export declare function extractWorkflowIdsFromArgs(args: Record<string, unknown>, insightWorkflowCache: Map<string, Set<string>>): string[];
|
|
47
|
+
/**
|
|
48
|
+
* Check if a user can access the given workflow IDs.
|
|
49
|
+
* Returns null if allowed, or { workflowId, reason } if denied.
|
|
50
|
+
*/
|
|
51
|
+
export declare function checkWorkflowAccess(workflowIds: string[], senderId: string, ctx: PermissionContext): PermissionDenied | null;
|
|
52
|
+
//# sourceMappingURL=permission-guard.d.ts.map
|
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Unified permission guard for bot tool execution.
|
|
4
|
+
*
|
|
5
|
+
* Enforces the workspace permission system: bot scope ceiling + per-user
|
|
6
|
+
* workflow access from the permission index built from core.init.
|
|
7
|
+
*
|
|
8
|
+
* Three levels of checking:
|
|
9
|
+
* 1. Pre-execution: extract workflow IDs from tool args, deny if user lacks access
|
|
10
|
+
* 2. Post-execution: extract workflow IDs from tool results (when not in args)
|
|
11
|
+
* 3. Post-filter: strip items from list results that the user can't see
|
|
12
|
+
*/
|
|
13
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
14
|
+
exports.buildPermissionIndex = buildPermissionIndex;
|
|
15
|
+
exports.extractWorkflowIdsFromArgs = extractWorkflowIdsFromArgs;
|
|
16
|
+
exports.checkWorkflowAccess = checkWorkflowAccess;
|
|
17
|
+
/**
|
|
18
|
+
* Build the permission index from core.init data.
|
|
19
|
+
*
|
|
20
|
+
* Returns: { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds }
|
|
21
|
+
*
|
|
22
|
+
* permissionIndex only contains RESTRICTED workflows (those with explicit member lists).
|
|
23
|
+
* If a workflow is not in the index, it's open to all workspace members.
|
|
24
|
+
*/
|
|
25
|
+
function buildPermissionIndex(init, workspaceId) {
|
|
26
|
+
const permissionIndex = new Map();
|
|
27
|
+
const adminUserIds = new Set();
|
|
28
|
+
const ownerUserIds = new Set();
|
|
29
|
+
const workspaceMemberIds = new Set();
|
|
30
|
+
if (!init?.processes || !workspaceId) {
|
|
31
|
+
return { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds };
|
|
32
|
+
}
|
|
33
|
+
// Extract workspace members, admins, owners from network.members
|
|
34
|
+
const network = init.network;
|
|
35
|
+
if (network?.members) {
|
|
36
|
+
const members = Array.isArray(network.members) ? network.members : Object.values(network.members);
|
|
37
|
+
for (const member of members) {
|
|
38
|
+
if (member.uid)
|
|
39
|
+
workspaceMemberIds.add(member.uid);
|
|
40
|
+
if (member.admin === true)
|
|
41
|
+
adminUserIds.add(member.uid);
|
|
42
|
+
if (member.owner === true)
|
|
43
|
+
ownerUserIds.add(member.uid);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
const teams = init.teams?.[workspaceId] || {};
|
|
47
|
+
const groups = init.groups?.[workspaceId] || {};
|
|
48
|
+
for (const proc of init.processes) {
|
|
49
|
+
const members = proc.members || [];
|
|
50
|
+
// network_ member = all workspace members have access → skip (open)
|
|
51
|
+
if (members.some((m) => m.id?.startsWith('network_')))
|
|
52
|
+
continue;
|
|
53
|
+
// Public team = all workspace members have access → skip (open)
|
|
54
|
+
let hasPublicTeam = false;
|
|
55
|
+
for (const m of members) {
|
|
56
|
+
if (!m.id?.startsWith('team_'))
|
|
57
|
+
continue;
|
|
58
|
+
const team = teams[m.id.slice(5)];
|
|
59
|
+
if (team?.public) {
|
|
60
|
+
hasPublicTeam = true;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
if (hasPublicTeam)
|
|
65
|
+
continue;
|
|
66
|
+
// Restricted workflow — build explicit user set
|
|
67
|
+
const userIds = new Set();
|
|
68
|
+
for (const member of members) {
|
|
69
|
+
const id = member.id;
|
|
70
|
+
if (!id)
|
|
71
|
+
continue;
|
|
72
|
+
if (id.startsWith('user_')) {
|
|
73
|
+
userIds.add(id.slice(5));
|
|
74
|
+
}
|
|
75
|
+
else if (id.startsWith('team_')) {
|
|
76
|
+
const team = teams[id.slice(5)];
|
|
77
|
+
if (team?.members) {
|
|
78
|
+
for (const uid of team.members) {
|
|
79
|
+
userIds.add(typeof uid === 'string' ? uid : uid._id || uid.id || uid.uid);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
else if (id.startsWith('group_')) {
|
|
84
|
+
const group = groups[id.slice(6)];
|
|
85
|
+
if (group?.members) {
|
|
86
|
+
for (const uid of group.members) {
|
|
87
|
+
userIds.add(typeof uid === 'string' ? uid : uid._id || uid.id || uid.uid);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
// Workflow creator always has access
|
|
93
|
+
if (proc.uid)
|
|
94
|
+
userIds.add(proc.uid);
|
|
95
|
+
permissionIndex.set(proc._id, userIds);
|
|
96
|
+
}
|
|
97
|
+
return { permissionIndex, adminUserIds, ownerUserIds, workspaceMemberIds };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Extract all workflow IDs from tool args, regardless of shape.
|
|
101
|
+
* Handles: top-level workflowId, sources[].workflowId, insightId → cached workflows.
|
|
102
|
+
*/
|
|
103
|
+
function extractWorkflowIdsFromArgs(args, insightWorkflowCache) {
|
|
104
|
+
const ids = [];
|
|
105
|
+
// Direct workflowId arg
|
|
106
|
+
if (typeof args.workflowId === 'string') {
|
|
107
|
+
ids.push(args.workflowId);
|
|
108
|
+
}
|
|
109
|
+
// sources[].workflowId (insight tools)
|
|
110
|
+
if (Array.isArray(args.sources)) {
|
|
111
|
+
for (const src of args.sources) {
|
|
112
|
+
if (src && typeof src === 'object' && typeof src.workflowId === 'string') {
|
|
113
|
+
ids.push(src.workflowId);
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
// insightId → cached workflow mapping
|
|
118
|
+
if (typeof args.insightId === 'string') {
|
|
119
|
+
const cached = insightWorkflowCache.get(args.insightId);
|
|
120
|
+
if (cached)
|
|
121
|
+
ids.push(...cached);
|
|
122
|
+
}
|
|
123
|
+
return ids;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Check if a user can access the given workflow IDs.
|
|
127
|
+
* Returns null if allowed, or { workflowId, reason } if denied.
|
|
128
|
+
*/
|
|
129
|
+
function checkWorkflowAccess(workflowIds, senderId, ctx) {
|
|
130
|
+
if (workflowIds.length === 0)
|
|
131
|
+
return null;
|
|
132
|
+
for (const wfId of workflowIds) {
|
|
133
|
+
// Bot scope ceiling
|
|
134
|
+
if (ctx.allowedWorkflows.length > 0 && !ctx.allowedWorkflows.includes(wfId)) {
|
|
135
|
+
return { workflowId: wfId, reason: 'bot-scope' };
|
|
136
|
+
}
|
|
137
|
+
// Admins and owners bypass user-level checks
|
|
138
|
+
if (ctx.adminUserIds.has(senderId) || ctx.ownerUserIds.has(senderId)) {
|
|
139
|
+
continue;
|
|
140
|
+
}
|
|
141
|
+
// User permission — restricted workflow, user not in member list
|
|
142
|
+
const allowed = ctx.permissionIndex.get(wfId);
|
|
143
|
+
if (allowed && !allowed.has(senderId)) {
|
|
144
|
+
return { workflowId: wfId, reason: senderId };
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
//# sourceMappingURL=permission-guard.js.map
|
|
@@ -62,6 +62,8 @@ export interface BotConnection {
|
|
|
62
62
|
};
|
|
63
63
|
}
|
|
64
64
|
export type MessagePriority = "high" | "normal" | "low";
|
|
65
|
+
/** Controls when the bot processes a message */
|
|
66
|
+
export type ResponseMode = 'always' | 'mention_only' | 'reply_only' | 'mention_or_reply';
|
|
65
67
|
/** File attachment in a Hailer message */
|
|
66
68
|
export interface HailerFileAttachment {
|
|
67
69
|
_id: string;
|
|
@@ -82,6 +84,8 @@ export interface HailerMessage {
|
|
|
82
84
|
replyTo?: string;
|
|
83
85
|
created?: number;
|
|
84
86
|
files?: HailerFileAttachment[];
|
|
87
|
+
forwardMessageId?: string;
|
|
88
|
+
forwardMessage?: HailerMessage;
|
|
85
89
|
}
|
|
86
90
|
/** Hailer user from search/workspace cache */
|
|
87
91
|
export interface HailerUser {
|
|
@@ -114,6 +118,7 @@ export interface IncomingMessage {
|
|
|
114
118
|
isReplyToBot: boolean;
|
|
115
119
|
isMention: boolean;
|
|
116
120
|
isDirectMessage: boolean;
|
|
121
|
+
isPrivateDiscussion: boolean;
|
|
117
122
|
fileAttachments?: IncomingFileAttachment[];
|
|
118
123
|
}
|
|
119
124
|
export interface DiscussionState {
|
|
@@ -9,11 +9,16 @@ export declare class TypingIndicatorService {
|
|
|
9
9
|
private logger;
|
|
10
10
|
private static TYPING_REFRESH_MS;
|
|
11
11
|
private typingIntervals;
|
|
12
|
+
private currentStatus;
|
|
12
13
|
constructor(botConnection: BotConnection, logger: Logger);
|
|
13
14
|
/**
|
|
14
15
|
* Start typing indicator with auto-refresh interval for a discussion
|
|
15
16
|
*/
|
|
16
|
-
start(discussionId: string): void;
|
|
17
|
+
start(discussionId: string, statusText?: string): void;
|
|
18
|
+
/**
|
|
19
|
+
* Update the status text for an active typing indicator
|
|
20
|
+
*/
|
|
21
|
+
updateStatus(discussionId: string, statusText: string): void;
|
|
17
22
|
/**
|
|
18
23
|
* Stop typing indicator for a specific discussion, or all discussions (shutdown)
|
|
19
24
|
*/
|
|
@@ -10,6 +10,7 @@ class TypingIndicatorService {
|
|
|
10
10
|
logger;
|
|
11
11
|
static TYPING_REFRESH_MS = 3000;
|
|
12
12
|
typingIntervals = new Map();
|
|
13
|
+
currentStatus = new Map();
|
|
13
14
|
constructor(botConnection, logger) {
|
|
14
15
|
this.botConnection = botConnection;
|
|
15
16
|
this.logger = logger;
|
|
@@ -17,7 +18,9 @@ class TypingIndicatorService {
|
|
|
17
18
|
/**
|
|
18
19
|
* Start typing indicator with auto-refresh interval for a discussion
|
|
19
20
|
*/
|
|
20
|
-
start(discussionId) {
|
|
21
|
+
start(discussionId, statusText) {
|
|
22
|
+
if (statusText)
|
|
23
|
+
this.currentStatus.set(discussionId, statusText);
|
|
21
24
|
if (this.typingIntervals.has(discussionId))
|
|
22
25
|
return;
|
|
23
26
|
this.sendSignal(discussionId, true);
|
|
@@ -26,6 +29,15 @@ class TypingIndicatorService {
|
|
|
26
29
|
}, TypingIndicatorService.TYPING_REFRESH_MS);
|
|
27
30
|
this.typingIntervals.set(discussionId, interval);
|
|
28
31
|
}
|
|
32
|
+
/**
|
|
33
|
+
* Update the status text for an active typing indicator
|
|
34
|
+
*/
|
|
35
|
+
updateStatus(discussionId, statusText) {
|
|
36
|
+
this.currentStatus.set(discussionId, statusText);
|
|
37
|
+
if (this.typingIntervals.has(discussionId)) {
|
|
38
|
+
this.sendSignal(discussionId, true);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
29
41
|
/**
|
|
30
42
|
* Stop typing indicator for a specific discussion, or all discussions (shutdown)
|
|
31
43
|
*/
|
|
@@ -35,6 +47,7 @@ class TypingIndicatorService {
|
|
|
35
47
|
if (interval) {
|
|
36
48
|
clearInterval(interval);
|
|
37
49
|
this.typingIntervals.delete(discussionId);
|
|
50
|
+
this.currentStatus.delete(discussionId);
|
|
38
51
|
this.sendSignal(discussionId, false);
|
|
39
52
|
}
|
|
40
53
|
}
|
|
@@ -45,14 +58,17 @@ class TypingIndicatorService {
|
|
|
45
58
|
this.sendSignal(discId, false);
|
|
46
59
|
}
|
|
47
60
|
this.typingIntervals.clear();
|
|
61
|
+
this.currentStatus.clear();
|
|
48
62
|
}
|
|
49
63
|
}
|
|
50
64
|
sendSignal(discussionId, isTyping) {
|
|
65
|
+
const statusText = isTyping ? this.currentStatus.get(discussionId) : undefined;
|
|
51
66
|
this.botConnection.client.socket.request("messenger.set_discussion_typing_state", [
|
|
52
67
|
discussionId,
|
|
53
|
-
isTyping
|
|
68
|
+
isTyping,
|
|
69
|
+
...(statusText ? [statusText] : []),
|
|
54
70
|
]).catch((error) => {
|
|
55
|
-
this.logger.debug("Typing indicator failed", { discussionId, isTyping, error: error?.message });
|
|
71
|
+
this.logger.debug("Typing indicator failed", { discussionId, isTyping, statusText, error: error?.message });
|
|
56
72
|
});
|
|
57
73
|
}
|
|
58
74
|
}
|
package/dist/config.d.ts
CHANGED
|
@@ -20,12 +20,13 @@ export declare const APP_VERSION: string;
|
|
|
20
20
|
*/
|
|
21
21
|
export declare const environment: {
|
|
22
22
|
NODE_ENV: "development" | "production" | "test";
|
|
23
|
-
LOG_LEVEL: "
|
|
23
|
+
LOG_LEVEL: "error" | "debug" | "info" | "warn";
|
|
24
24
|
DISABLE_MCP_SERVER: boolean;
|
|
25
25
|
MCP_CLIENT_ENABLED: boolean;
|
|
26
26
|
ENABLE_NUCLEAR_TOOLS: boolean;
|
|
27
27
|
PORT: number;
|
|
28
28
|
CORS_ORIGINS: string[];
|
|
29
|
+
HAILER_APP_URL: string;
|
|
29
30
|
CLIENT_CONFIGS: Record<string, {
|
|
30
31
|
email: string;
|
|
31
32
|
password: string;
|
|
@@ -50,6 +51,8 @@ export declare const environment: {
|
|
|
50
51
|
CONTEXT_ENABLE_AUTO_SUMMARIZATION: boolean;
|
|
51
52
|
CONTEXT_MAX_SUMMARIZATION_CHUNKS: number;
|
|
52
53
|
TOKEN_BILLING_ENABLED: boolean;
|
|
54
|
+
VIPUNEN_API_KEYS: Record<string, "admin" | "dev" | "readonly">;
|
|
55
|
+
MCP_PUBLIC_URL?: string | undefined;
|
|
53
56
|
WORKSPACE_CONFIG_PATH?: string | undefined;
|
|
54
57
|
DEV_APPS_PATH?: string | undefined;
|
|
55
58
|
DEV_APPS_PATHS?: string | undefined;
|
|
@@ -57,6 +60,8 @@ export declare const environment: {
|
|
|
57
60
|
OPENAI_API_BASE?: string | undefined;
|
|
58
61
|
OPENAI_MODEL?: string | undefined;
|
|
59
62
|
ANTHROPIC_API_KEY?: string | undefined;
|
|
63
|
+
WEAVIATE_HOST?: string | undefined;
|
|
64
|
+
WEAVIATE_API_KEY?: string | undefined;
|
|
60
65
|
};
|
|
61
66
|
/**
|
|
62
67
|
* Mask sensitive data for safe logging
|
package/dist/config.js
CHANGED
|
@@ -102,6 +102,10 @@ const environmentSchema = zod_1.z.object({
|
|
|
102
102
|
// Server
|
|
103
103
|
PORT: zod_1.z.string().transform(v => parseInt(v) || 3030).default('3030'),
|
|
104
104
|
CORS_ORIGINS: jsonArraySchema(zod_1.z.string().url()).default('[]'),
|
|
105
|
+
// OAuth - public URL for redirects (e.g., https://mcp.hailer.com)
|
|
106
|
+
MCP_PUBLIC_URL: zod_1.z.string().url().optional(),
|
|
107
|
+
// Hailer frontend URL for OAuth login (e.g., https://app.hailer.com)
|
|
108
|
+
HAILER_APP_URL: zod_1.z.string().url().default('https://app.hailer.com'),
|
|
105
109
|
// Hailer integration - now as efficient Map
|
|
106
110
|
CLIENT_CONFIGS: clientConfigsSchema,
|
|
107
111
|
// Workspace configuration path (for reading workspace configs from external projects)
|
|
@@ -142,6 +146,45 @@ const environmentSchema = zod_1.z.object({
|
|
|
142
146
|
CONTEXT_MAX_SUMMARIZATION_CHUNKS: zod_1.z.string().transform(v => parseInt(v) || 10).default('10'),
|
|
143
147
|
// Token billing (real-time usage billing per workspace via Hailer API)
|
|
144
148
|
TOKEN_BILLING_ENABLED: zod_1.z.string().transform(v => v === 'true').default('false'),
|
|
149
|
+
// Vipunen (Weaviate RAG knowledge base)
|
|
150
|
+
WEAVIATE_HOST: zod_1.z.string().url().optional(),
|
|
151
|
+
WEAVIATE_API_KEY: zod_1.z.string().min(1).optional(),
|
|
152
|
+
// Vipunen client API keys — JSON map of key → group
|
|
153
|
+
// Format: { "my-api-key": "admin", "dev-key": "dev", "reader-key": "readonly" }
|
|
154
|
+
// Groups: admin (full access), dev (VipunenConfig read-only), readonly (read-only everywhere)
|
|
155
|
+
VIPUNEN_API_KEYS: zod_1.z.string()
|
|
156
|
+
.optional()
|
|
157
|
+
.default('{}')
|
|
158
|
+
.transform((value, ctx) => {
|
|
159
|
+
try {
|
|
160
|
+
const parsed = JSON.parse(value);
|
|
161
|
+
const validGroups = ['admin', 'dev', 'readonly'];
|
|
162
|
+
for (const [key, group] of Object.entries(parsed)) {
|
|
163
|
+
if (key.length < 32) {
|
|
164
|
+
ctx.addIssue({
|
|
165
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
166
|
+
message: `Vipunen API key too short (min 32 chars): "${key.substring(0, 4)}..."`,
|
|
167
|
+
});
|
|
168
|
+
return zod_1.z.NEVER;
|
|
169
|
+
}
|
|
170
|
+
if (!validGroups.includes(group)) {
|
|
171
|
+
ctx.addIssue({
|
|
172
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
173
|
+
message: `Invalid group "${group}" for key "${key.substring(0, 4)}...". Must be one of: ${validGroups.join(', ')}`,
|
|
174
|
+
});
|
|
175
|
+
return zod_1.z.NEVER;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return parsed;
|
|
179
|
+
}
|
|
180
|
+
catch (error) {
|
|
181
|
+
ctx.addIssue({
|
|
182
|
+
code: zod_1.z.ZodIssueCode.custom,
|
|
183
|
+
message: `Invalid VIPUNEN_API_KEYS JSON: ${error.message}`,
|
|
184
|
+
});
|
|
185
|
+
return zod_1.z.NEVER;
|
|
186
|
+
}
|
|
187
|
+
}),
|
|
145
188
|
});
|
|
146
189
|
/**
|
|
147
190
|
* Get process environment
|
package/dist/core.js
CHANGED
|
@@ -49,15 +49,13 @@ class Core {
|
|
|
49
49
|
this.logger.debug('Starting Hailer MCP application');
|
|
50
50
|
try {
|
|
51
51
|
// Start MCP Server FIRST (daemon/client needs it for tool schemas)
|
|
52
|
+
// Server starts even without CLIENT_CONFIGS - OAuth sessions are dynamic
|
|
52
53
|
if (this.appConfig.server.enableMcpServer) {
|
|
53
54
|
const hasAccounts = Object.keys(this.appConfig.hailerAccounts).length > 0;
|
|
54
55
|
if (!hasAccounts) {
|
|
55
|
-
this.logger.
|
|
56
|
-
this.logger.warn('Add at least one account to CLIENT_CONFIGS in .env.local to use the MCP Server.');
|
|
57
|
-
}
|
|
58
|
-
else {
|
|
59
|
-
await this.startMCPServer();
|
|
56
|
+
this.logger.info('No accounts in CLIENT_CONFIGS - server will accept OAuth sessions only');
|
|
60
57
|
}
|
|
58
|
+
await this.startMCPServer();
|
|
61
59
|
}
|
|
62
60
|
// Start bot client ONLY if MCP_CLIENT_ENABLED=true
|
|
63
61
|
if (this.appConfig.server.enableClient) {
|
|
@@ -78,7 +76,6 @@ class Core {
|
|
|
78
76
|
port: this.appConfig.server.port,
|
|
79
77
|
corsOrigins: this.appConfig.server.corsOrigins,
|
|
80
78
|
toolRegistry: this.toolRegistry,
|
|
81
|
-
getDaemonStatus: () => this.getDaemonStatus() // Pass callback for daemon monitoring
|
|
82
79
|
});
|
|
83
80
|
await this.mcpServer.start();
|
|
84
81
|
this.logger.debug('MCP Server service started');
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { HailerClient } from './hailer-clients';
|
|
2
2
|
import { WorkspaceCache } from './workspace-cache';
|
|
3
3
|
import { HailerV2CoreInitResponse, HailerApiClient } from './utils/index';
|
|
4
|
+
import { ToolGroup } from './tool-registry';
|
|
5
|
+
import { UserRole } from './utils/index';
|
|
4
6
|
export interface UserContext {
|
|
5
7
|
client: HailerClient;
|
|
6
8
|
hailer: HailerApiClient;
|
|
@@ -10,6 +12,9 @@ export interface UserContext {
|
|
|
10
12
|
createdAt: number;
|
|
11
13
|
email: string;
|
|
12
14
|
password: string;
|
|
15
|
+
workspaceRoles: Record<string, UserRole>;
|
|
16
|
+
currentWorkspaceId: string;
|
|
17
|
+
allowedGroups: ToolGroup[];
|
|
13
18
|
}
|
|
14
19
|
/**
|
|
15
20
|
* Cache for user-specific data (client connections, init data, workspace cache)
|
|
@@ -6,6 +6,7 @@ const workspace_cache_1 = require("./workspace-cache");
|
|
|
6
6
|
const config_1 = require("../config");
|
|
7
7
|
const logger_1 = require("../lib/logger");
|
|
8
8
|
const index_1 = require("./utils/index");
|
|
9
|
+
const index_2 = require("./utils/index");
|
|
9
10
|
const logger = (0, logger_1.createLogger)({ component: 'user-context-cache' });
|
|
10
11
|
/**
|
|
11
12
|
* Cache for user-specific data (client connections, init data, workspace cache)
|
|
@@ -57,7 +58,7 @@ class UserContextCache {
|
|
|
57
58
|
else {
|
|
58
59
|
// Cache expired, remove stale entry
|
|
59
60
|
this.cache.delete(apiKey);
|
|
60
|
-
logger.
|
|
61
|
+
logger.info('Cache expired, refreshing user context', {
|
|
61
62
|
apiKey: apiKey.substring(0, 8) + '...',
|
|
62
63
|
ageMinutes: Math.round(age / (1000 * 60))
|
|
63
64
|
});
|
|
@@ -66,7 +67,7 @@ class UserContextCache {
|
|
|
66
67
|
else if (forceRefresh && this.cache.has(apiKey)) {
|
|
67
68
|
// Force refresh requested, clear existing cache
|
|
68
69
|
this.cache.delete(apiKey);
|
|
69
|
-
logger.
|
|
70
|
+
logger.info('Force refresh requested, clearing cached user context', {
|
|
70
71
|
apiKey: apiKey.substring(0, 8) + '...'
|
|
71
72
|
});
|
|
72
73
|
}
|
|
@@ -105,29 +106,57 @@ class UserContextCache {
|
|
|
105
106
|
const init = await client.socket.request('v2.core.init', [
|
|
106
107
|
['processes', 'users', 'network', 'networks', 'teams']
|
|
107
108
|
]);
|
|
108
|
-
|
|
109
|
-
|
|
109
|
+
(0, index_1.normalizeInitProcesses)(init);
|
|
110
|
+
// Multi-workspace support: log available workspaces, use first one as default
|
|
111
|
+
// User can switch workspaces using list_my_workspaces tool and specifying workspaceId
|
|
112
|
+
const networks = (init.networks || {});
|
|
113
|
+
const workspaceCount = Object.keys(networks).length;
|
|
110
114
|
if (workspaceCount > 1) {
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
.map((ws) => ws.name)
|
|
115
|
+
const workspaceList = Object.entries(networks)
|
|
116
|
+
.map(([id, ws]) => `${ws.name} (${id})`)
|
|
114
117
|
.join(', ');
|
|
115
|
-
logger.
|
|
118
|
+
logger.info('Multi-workspace user detected', {
|
|
116
119
|
workspaceCount,
|
|
117
|
-
workspaces:
|
|
118
|
-
apiKey: apiKey.substring(0, 8) + '...'
|
|
120
|
+
workspaces: workspaceList,
|
|
121
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
122
|
+
hint: 'Use list_my_workspaces tool to see all workspaces and switch between them'
|
|
119
123
|
});
|
|
120
|
-
// Clean up the connection before throwing - prevents dangling socket
|
|
121
|
-
(0, hailer_clients_1.disconnectHailerClientByApiKey)(apiKey);
|
|
122
|
-
throw new Error(`Multi-workspace credentials detected (${workspaceCount} workspaces: ${workspaceNames}). ` +
|
|
123
|
-
`MCP requires bot credentials with access to a single workspace. ` +
|
|
124
|
-
`Please use the bot account created during 'hailer-sdk init'.`);
|
|
125
124
|
}
|
|
126
125
|
// Create workspace cache from init data
|
|
127
126
|
const appConfig = (0, config_1.createApplicationConfig)();
|
|
128
127
|
const workspaceCache = (0, workspace_cache_1.createWorkspaceCache)(init, appConfig.mcpConfig);
|
|
128
|
+
// Extract user roles from ALL workspaces
|
|
129
|
+
const currentUserId = await (0, hailer_clients_1.getCurrentUserId)(client);
|
|
130
|
+
const workspaceRoles = (0, index_2.extractWorkspaceRoles)(networks, currentUserId);
|
|
131
|
+
// Get current workspace ID
|
|
132
|
+
const currentWorkspaceId = init.network?._id || Object.keys(networks)[0] || '';
|
|
133
|
+
// Get role for current workspace (for backward compatibility and initial tool filtering)
|
|
134
|
+
const userRole = workspaceRoles[currentWorkspaceId] || 'guest';
|
|
135
|
+
const allowedGroups = (0, index_2.getAllowedGroups)(userRole, config_1.environment.ENABLE_NUCLEAR_TOOLS);
|
|
136
|
+
logger.info('User roles extracted from all workspaces', {
|
|
137
|
+
apiKey: apiKey.substring(0, 8) + '...',
|
|
138
|
+
workspaceCount: Object.keys(workspaceRoles).length,
|
|
139
|
+
currentWorkspaceId,
|
|
140
|
+
currentRole: userRole,
|
|
141
|
+
allRoles: Object.entries(workspaceRoles).map(([id, role]) => `${id.slice(-6)}:${role}`).join(', ')
|
|
142
|
+
});
|
|
129
143
|
// Get credentials from config (for tools like publish_hailer_app that need external auth)
|
|
130
|
-
|
|
144
|
+
// User API Keys (OAuth users) don't have bot credentials - use empty strings
|
|
145
|
+
const isUserApiKey = apiKey.startsWith('userapikey_');
|
|
146
|
+
let email = '';
|
|
147
|
+
let password = '';
|
|
148
|
+
if (!isUserApiKey) {
|
|
149
|
+
try {
|
|
150
|
+
const accountConfig = appConfig.getClientConfig(apiKey);
|
|
151
|
+
email = accountConfig.email;
|
|
152
|
+
password = accountConfig.password;
|
|
153
|
+
}
|
|
154
|
+
catch (error) {
|
|
155
|
+
logger.warn('No client config found for API key, using empty credentials', {
|
|
156
|
+
apiKey: apiKey.substring(0, 8) + '...'
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
131
160
|
const context = {
|
|
132
161
|
client,
|
|
133
162
|
hailer,
|
|
@@ -135,8 +164,11 @@ class UserContextCache {
|
|
|
135
164
|
workspaceCache,
|
|
136
165
|
apiKey,
|
|
137
166
|
createdAt: Date.now(),
|
|
138
|
-
email
|
|
139
|
-
password
|
|
167
|
+
email,
|
|
168
|
+
password,
|
|
169
|
+
workspaceRoles, // NEW: Map of workspaceId → role
|
|
170
|
+
currentWorkspaceId, // NEW: Current workspace ID
|
|
171
|
+
allowedGroups, // Keep for stdio-server compatibility
|
|
140
172
|
};
|
|
141
173
|
// Calculate and log cache sizes
|
|
142
174
|
const rawInitSize = Buffer.byteLength(JSON.stringify(init), 'utf8');
|
|
@@ -176,7 +208,7 @@ class UserContextCache {
|
|
|
176
208
|
static clearAll() {
|
|
177
209
|
const size = this.cache.size;
|
|
178
210
|
this.cache.clear();
|
|
179
|
-
logger.
|
|
211
|
+
logger.info('Cleared all user contexts from cache', { clearedCount: size });
|
|
180
212
|
}
|
|
181
213
|
/**
|
|
182
214
|
* Get cache statistics (for monitoring and debugging)
|
|
@@ -5,10 +5,28 @@ export interface HailerRestClient {
|
|
|
5
5
|
sessionKey: string;
|
|
6
6
|
}
|
|
7
7
|
export interface HailerClient {
|
|
8
|
-
socket: Client;
|
|
8
|
+
socket: Client | RestOnlySocket;
|
|
9
9
|
rest: HailerRestClient;
|
|
10
10
|
sessionKey: string;
|
|
11
11
|
}
|
|
12
|
+
/**
|
|
13
|
+
* REST-only socket mock for User API Key sessions
|
|
14
|
+
* User API Keys require IP for validation, which socket.resume() doesn't provide.
|
|
15
|
+
* This mock implements the socket.request() interface using REST API calls.
|
|
16
|
+
*/
|
|
17
|
+
export declare class RestOnlySocket {
|
|
18
|
+
host: string;
|
|
19
|
+
private apiKey;
|
|
20
|
+
constructor(baseUrl: string, apiKey: string);
|
|
21
|
+
/**
|
|
22
|
+
* Make RPC request via REST API instead of socket
|
|
23
|
+
* Hailer's /api endpoint accepts the same RPC format as socket
|
|
24
|
+
*/
|
|
25
|
+
request(method: string, args?: unknown[]): Promise<unknown>;
|
|
26
|
+
on(_event: string, _handler: (...args: any[]) => void): void;
|
|
27
|
+
off(_event: string, _handler: (...args: any[]) => void): void;
|
|
28
|
+
disconnect(): void;
|
|
29
|
+
}
|
|
12
30
|
/**
|
|
13
31
|
* Get the current user ID from the authenticated session
|
|
14
32
|
* This is called after authentication to retrieve the user ID automatically
|