@hailer/mcp 1.1.12 → 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/CHANGELOG.md +0 -7
- package/{.claude → dist}/CLAUDE.md +2 -2
- package/dist/app.js +18 -5
- package/dist/bot/bot-config.d.ts +10 -1
- package/dist/bot/bot-config.js +64 -3
- package/dist/bot/bot-manager.d.ts +2 -0
- package/dist/bot/bot-manager.js +9 -2
- package/dist/bot/bot.d.ts +33 -0
- package/dist/bot/bot.js +461 -160
- 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/cli.js +0 -0
- package/dist/config.d.ts +6 -1
- package/dist/config.js +43 -0
- package/dist/core.js +3 -6
- package/dist/lib/discussion-lock.d.ts +42 -0
- package/dist/lib/discussion-lock.js +110 -0
- 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 +158 -24
- package/dist/mcp/session-store.d.ts +68 -0
- package/dist/mcp/session-store.js +169 -0
- package/dist/mcp/signal-handler.js +2 -0
- 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/bot-config/constants.d.ts +23 -0
- package/dist/mcp/tools/bot-config/constants.js +94 -0
- package/dist/mcp/tools/bot-config/core.d.ts +253 -0
- package/dist/mcp/tools/bot-config/core.js +2456 -0
- package/dist/mcp/tools/bot-config/index.d.ts +10 -0
- package/dist/mcp/tools/bot-config/index.js +59 -0
- package/dist/mcp/tools/bot-config/tools.d.ts +7 -0
- package/dist/mcp/tools/bot-config/tools.js +15 -0
- package/dist/mcp/tools/bot-config/types.d.ts +50 -0
- package/dist/mcp/tools/bot-config/types.js +6 -0
- package/dist/mcp/tools/bug-fixer-tools.d.ts +45 -0
- package/dist/mcp/tools/bug-fixer-tools.js +1096 -0
- 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/document.d.ts +11 -0
- package/dist/mcp/tools/document.js +741 -0
- package/dist/mcp/tools/investigate.d.ts +9 -0
- package/dist/mcp/tools/investigate.js +254 -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 +4 -0
- package/dist/mcp/webhook-handler.js +8 -0
- package/dist/mcp-server.d.ts +23 -2
- package/dist/mcp-server.js +639 -127
- 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/dist/stdio-server.d.ts +14 -0
- package/dist/stdio-server.js +101 -0
- package/package.json +2 -1
- 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/.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 -203
- 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 -28
- package/SESSION-HANDOFF.md +0 -68
- package/inbox/2026-03-04-bot-config-patterns.md +0 -24
- 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/cli.js
CHANGED
|
File without changes
|
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');
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discussion Lock Registry
|
|
3
|
+
*
|
|
4
|
+
* Prevents multiple bots from responding to the same discussion.
|
|
5
|
+
* When a specialist bot (like Giuseppe) is handling a discussion,
|
|
6
|
+
* it acquires a lock. Other bots (like Orchestrator) check the lock
|
|
7
|
+
* before responding.
|
|
8
|
+
*/
|
|
9
|
+
/**
|
|
10
|
+
* Acquire a lock on a discussion
|
|
11
|
+
* @param discussionId - The discussion to lock
|
|
12
|
+
* @param botName - Name of the bot acquiring the lock (for logging)
|
|
13
|
+
* @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
|
|
14
|
+
* @returns true if lock acquired, false if already locked by another bot
|
|
15
|
+
*/
|
|
16
|
+
export declare function acquireDiscussionLock(discussionId: string, botName: string, ttlMs?: number): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Release a lock on a discussion
|
|
19
|
+
* @param discussionId - The discussion to unlock
|
|
20
|
+
* @param botName - Name of the bot releasing (must match acquirer)
|
|
21
|
+
*/
|
|
22
|
+
export declare function releaseDiscussionLock(discussionId: string, botName: string): void;
|
|
23
|
+
/**
|
|
24
|
+
* Check if a discussion is locked by another bot
|
|
25
|
+
* @param discussionId - The discussion to check
|
|
26
|
+
* @param myBotName - Name of the checking bot (own locks don't block)
|
|
27
|
+
* @returns true if locked by ANOTHER bot, false if free or own lock
|
|
28
|
+
*/
|
|
29
|
+
export declare function isDiscussionLocked(discussionId: string, myBotName: string): boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Clean up expired locks (call periodically)
|
|
32
|
+
*/
|
|
33
|
+
export declare function cleanupExpiredLocks(): number;
|
|
34
|
+
/**
|
|
35
|
+
* Get current lock status (for debugging)
|
|
36
|
+
*/
|
|
37
|
+
export declare function getLockStatus(): Map<string, {
|
|
38
|
+
botName: string;
|
|
39
|
+
acquiredAt: number;
|
|
40
|
+
expiresAt: number;
|
|
41
|
+
}>;
|
|
42
|
+
//# sourceMappingURL=discussion-lock.d.ts.map
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* Discussion Lock Registry
|
|
4
|
+
*
|
|
5
|
+
* Prevents multiple bots from responding to the same discussion.
|
|
6
|
+
* When a specialist bot (like Giuseppe) is handling a discussion,
|
|
7
|
+
* it acquires a lock. Other bots (like Orchestrator) check the lock
|
|
8
|
+
* before responding.
|
|
9
|
+
*/
|
|
10
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
11
|
+
exports.acquireDiscussionLock = acquireDiscussionLock;
|
|
12
|
+
exports.releaseDiscussionLock = releaseDiscussionLock;
|
|
13
|
+
exports.isDiscussionLocked = isDiscussionLocked;
|
|
14
|
+
exports.cleanupExpiredLocks = cleanupExpiredLocks;
|
|
15
|
+
exports.getLockStatus = getLockStatus;
|
|
16
|
+
const logger_1 = require("./logger");
|
|
17
|
+
const logger = (0, logger_1.createLogger)({ component: 'discussion-lock' });
|
|
18
|
+
// Singleton map of discussionId -> { botName, acquiredAt, expiresAt }
|
|
19
|
+
const locks = new Map();
|
|
20
|
+
// Default lock TTL: 5 minutes (allows for LLM processing time)
|
|
21
|
+
const DEFAULT_LOCK_TTL_MS = 5 * 60 * 1000;
|
|
22
|
+
/**
|
|
23
|
+
* Acquire a lock on a discussion
|
|
24
|
+
* @param discussionId - The discussion to lock
|
|
25
|
+
* @param botName - Name of the bot acquiring the lock (for logging)
|
|
26
|
+
* @param ttlMs - Lock duration in milliseconds (default: 5 minutes)
|
|
27
|
+
* @returns true if lock acquired, false if already locked by another bot
|
|
28
|
+
*/
|
|
29
|
+
function acquireDiscussionLock(discussionId, botName, ttlMs = DEFAULT_LOCK_TTL_MS) {
|
|
30
|
+
const now = Date.now();
|
|
31
|
+
const existing = locks.get(discussionId);
|
|
32
|
+
// Check if existing lock is still valid
|
|
33
|
+
if (existing && existing.expiresAt > now) {
|
|
34
|
+
if (existing.botName === botName) {
|
|
35
|
+
// Same bot - extend the lock
|
|
36
|
+
existing.expiresAt = now + ttlMs;
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
// Different bot has the lock
|
|
40
|
+
logger.debug('Discussion already locked', {
|
|
41
|
+
discussionId,
|
|
42
|
+
lockedBy: existing.botName,
|
|
43
|
+
requestedBy: botName
|
|
44
|
+
});
|
|
45
|
+
return false;
|
|
46
|
+
}
|
|
47
|
+
// Acquire lock
|
|
48
|
+
locks.set(discussionId, {
|
|
49
|
+
botName,
|
|
50
|
+
acquiredAt: now,
|
|
51
|
+
expiresAt: now + ttlMs
|
|
52
|
+
});
|
|
53
|
+
logger.debug('Discussion lock acquired', { discussionId, botName, ttlMs });
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Release a lock on a discussion
|
|
58
|
+
* @param discussionId - The discussion to unlock
|
|
59
|
+
* @param botName - Name of the bot releasing (must match acquirer)
|
|
60
|
+
*/
|
|
61
|
+
function releaseDiscussionLock(discussionId, botName) {
|
|
62
|
+
const existing = locks.get(discussionId);
|
|
63
|
+
if (existing && existing.botName === botName) {
|
|
64
|
+
locks.delete(discussionId);
|
|
65
|
+
logger.debug('Discussion lock released', { discussionId, botName });
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* Check if a discussion is locked by another bot
|
|
70
|
+
* @param discussionId - The discussion to check
|
|
71
|
+
* @param myBotName - Name of the checking bot (own locks don't block)
|
|
72
|
+
* @returns true if locked by ANOTHER bot, false if free or own lock
|
|
73
|
+
*/
|
|
74
|
+
function isDiscussionLocked(discussionId, myBotName) {
|
|
75
|
+
const now = Date.now();
|
|
76
|
+
const existing = locks.get(discussionId);
|
|
77
|
+
// No lock or expired
|
|
78
|
+
if (!existing || existing.expiresAt <= now) {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
// Own lock doesn't block
|
|
82
|
+
if (existing.botName === myBotName) {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return true;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Clean up expired locks (call periodically)
|
|
89
|
+
*/
|
|
90
|
+
function cleanupExpiredLocks() {
|
|
91
|
+
const now = Date.now();
|
|
92
|
+
let cleaned = 0;
|
|
93
|
+
for (const [discussionId, lock] of locks.entries()) {
|
|
94
|
+
if (lock.expiresAt <= now) {
|
|
95
|
+
locks.delete(discussionId);
|
|
96
|
+
cleaned++;
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
if (cleaned > 0) {
|
|
100
|
+
logger.debug('Cleaned up expired locks', { count: cleaned });
|
|
101
|
+
}
|
|
102
|
+
return cleaned;
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Get current lock status (for debugging)
|
|
106
|
+
*/
|
|
107
|
+
function getLockStatus() {
|
|
108
|
+
return new Map(locks);
|
|
109
|
+
}
|
|
110
|
+
//# sourceMappingURL=discussion-lock.js.map
|
|
@@ -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)
|