octo-vec 1.0.0
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/README.md +646 -0
- package/core/prompts/architect.md +124 -0
- package/core/prompts/ba.md +117 -0
- package/core/prompts/backend.md +154 -0
- package/core/prompts/compliance.md +127 -0
- package/core/prompts/dataanalyst.md +126 -0
- package/core/prompts/dataengineer.md +155 -0
- package/core/prompts/dba.md +155 -0
- package/core/prompts/designer.md +145 -0
- package/core/prompts/dev.md +148 -0
- package/core/prompts/devops.md +127 -0
- package/core/prompts/frontend.md +151 -0
- package/core/prompts/mlengineer.md +156 -0
- package/core/prompts/mobile.md +155 -0
- package/core/prompts/pm.md +182 -0
- package/core/prompts/productowner.md +122 -0
- package/core/prompts/qa.md +135 -0
- package/core/prompts/releasemanager.md +138 -0
- package/core/prompts/researcher.md +122 -0
- package/core/prompts/scrummaster.md +125 -0
- package/core/prompts/security.md +127 -0
- package/core/prompts/sre.md +141 -0
- package/core/prompts/support.md +138 -0
- package/core/prompts/techwriter.md +123 -0
- package/core/roster.json +1161 -0
- package/dashboard/dist/assets/index--L-aFRgh.css +1 -0
- package/dashboard/dist/assets/index-BoOVmAFf.js +523 -0
- package/dashboard/dist/icons/integrations/gitleaks.svg +6 -0
- package/dashboard/dist/icons/integrations/searxng.svg +5 -0
- package/dashboard/dist/icons/integrations/semgrep.svg +4 -0
- package/dashboard/dist/icons/integrations/slack.svg +6 -0
- package/dashboard/dist/icons/integrations/sonarqube.svg +5 -0
- package/dashboard/dist/icons/integrations/telegram.svg +4 -0
- package/dashboard/dist/icons/integrations/trivy.svg +5 -0
- package/dashboard/dist/icons/providers/anthropic.svg +1 -0
- package/dashboard/dist/icons/providers/antigravity.svg +1 -0
- package/dashboard/dist/icons/providers/azure.svg +1 -0
- package/dashboard/dist/icons/providers/bedrock.svg +1 -0
- package/dashboard/dist/icons/providers/cerebras.svg +1 -0
- package/dashboard/dist/icons/providers/chatglm.svg +1 -0
- package/dashboard/dist/icons/providers/codex.svg +1 -0
- package/dashboard/dist/icons/providers/gemini.svg +1 -0
- package/dashboard/dist/icons/providers/githubcopilot.svg +1 -0
- package/dashboard/dist/icons/providers/googlecloud.svg +1 -0
- package/dashboard/dist/icons/providers/groq.svg +1 -0
- package/dashboard/dist/icons/providers/huggingface.svg +1 -0
- package/dashboard/dist/icons/providers/kimi.svg +1 -0
- package/dashboard/dist/icons/providers/minimax.svg +1 -0
- package/dashboard/dist/icons/providers/mistral.svg +1 -0
- package/dashboard/dist/icons/providers/openai.svg +1 -0
- package/dashboard/dist/icons/providers/openrouter.svg +1 -0
- package/dashboard/dist/icons/providers/vercel.svg +1 -0
- package/dashboard/dist/icons/providers/xai.svg +1 -0
- package/dashboard/dist/index.html +17 -0
- package/dist/agents/pmAgent.d.ts +40 -0
- package/dist/agents/pmAgent.d.ts.map +1 -0
- package/dist/agents/pmAgent.js +181 -0
- package/dist/agents/pmAgent.js.map +1 -0
- package/dist/ar/baseSpecialist.d.ts +36 -0
- package/dist/ar/baseSpecialist.d.ts.map +1 -0
- package/dist/ar/baseSpecialist.js +292 -0
- package/dist/ar/baseSpecialist.js.map +1 -0
- package/dist/ar/promptLoader.d.ts +10 -0
- package/dist/ar/promptLoader.d.ts.map +1 -0
- package/dist/ar/promptLoader.js +22 -0
- package/dist/ar/promptLoader.js.map +1 -0
- package/dist/ar/registry.d.ts +12 -0
- package/dist/ar/registry.d.ts.map +1 -0
- package/dist/ar/registry.js +22 -0
- package/dist/ar/registry.js.map +1 -0
- package/dist/ar/roster.d.ts +104 -0
- package/dist/ar/roster.d.ts.map +1 -0
- package/dist/ar/roster.js +245 -0
- package/dist/ar/roster.js.map +1 -0
- package/dist/ar/toolProfiles.d.ts +18 -0
- package/dist/ar/toolProfiles.d.ts.map +1 -0
- package/dist/ar/toolProfiles.js +89 -0
- package/dist/ar/toolProfiles.js.map +1 -0
- package/dist/atp/agentGroups.d.ts +39 -0
- package/dist/atp/agentGroups.d.ts.map +1 -0
- package/dist/atp/agentGroups.js +109 -0
- package/dist/atp/agentGroups.js.map +1 -0
- package/dist/atp/agentInterrupt.d.ts +31 -0
- package/dist/atp/agentInterrupt.d.ts.map +1 -0
- package/dist/atp/agentInterrupt.js +51 -0
- package/dist/atp/agentInterrupt.js.map +1 -0
- package/dist/atp/agentMessageQueue.d.ts +74 -0
- package/dist/atp/agentMessageQueue.d.ts.map +1 -0
- package/dist/atp/agentMessageQueue.js +218 -0
- package/dist/atp/agentMessageQueue.js.map +1 -0
- package/dist/atp/agentRuntime.d.ts +67 -0
- package/dist/atp/agentRuntime.d.ts.map +1 -0
- package/dist/atp/agentRuntime.js +279 -0
- package/dist/atp/agentRuntime.js.map +1 -0
- package/dist/atp/agentStreamBus.d.ts +35 -0
- package/dist/atp/agentStreamBus.d.ts.map +1 -0
- package/dist/atp/agentStreamBus.js +159 -0
- package/dist/atp/agentStreamBus.js.map +1 -0
- package/dist/atp/agentToolConfig.d.ts +38 -0
- package/dist/atp/agentToolConfig.d.ts.map +1 -0
- package/dist/atp/agentToolConfig.js +225 -0
- package/dist/atp/agentToolConfig.js.map +1 -0
- package/dist/atp/chatLog.d.ts +34 -0
- package/dist/atp/chatLog.d.ts.map +1 -0
- package/dist/atp/chatLog.js +59 -0
- package/dist/atp/chatLog.js.map +1 -0
- package/dist/atp/codexAuth.d.ts +6 -0
- package/dist/atp/codexAuth.d.ts.map +1 -0
- package/dist/atp/codexAuth.js +44 -0
- package/dist/atp/codexAuth.js.map +1 -0
- package/dist/atp/database.d.ts +54 -0
- package/dist/atp/database.d.ts.map +1 -0
- package/dist/atp/database.js +323 -0
- package/dist/atp/database.js.map +1 -0
- package/dist/atp/eventLog.d.ts +12 -0
- package/dist/atp/eventLog.d.ts.map +1 -0
- package/dist/atp/eventLog.js +60 -0
- package/dist/atp/eventLog.js.map +1 -0
- package/dist/atp/inboxLoop.d.ts +72 -0
- package/dist/atp/inboxLoop.d.ts.map +1 -0
- package/dist/atp/inboxLoop.js +482 -0
- package/dist/atp/inboxLoop.js.map +1 -0
- package/dist/atp/llmDebug.d.ts +18 -0
- package/dist/atp/llmDebug.d.ts.map +1 -0
- package/dist/atp/llmDebug.js +97 -0
- package/dist/atp/llmDebug.js.map +1 -0
- package/dist/atp/messageDebouncer.d.ts +34 -0
- package/dist/atp/messageDebouncer.d.ts.map +1 -0
- package/dist/atp/messageDebouncer.js +60 -0
- package/dist/atp/messageDebouncer.js.map +1 -0
- package/dist/atp/messageQueue.d.ts +17 -0
- package/dist/atp/messageQueue.d.ts.map +1 -0
- package/dist/atp/messageQueue.js +69 -0
- package/dist/atp/messageQueue.js.map +1 -0
- package/dist/atp/modelConfig.d.ts +46 -0
- package/dist/atp/modelConfig.d.ts.map +1 -0
- package/dist/atp/modelConfig.js +232 -0
- package/dist/atp/modelConfig.js.map +1 -0
- package/dist/atp/models.d.ts +87 -0
- package/dist/atp/models.d.ts.map +1 -0
- package/dist/atp/models.js +45 -0
- package/dist/atp/models.js.map +1 -0
- package/dist/atp/postTaskHooks.d.ts +21 -0
- package/dist/atp/postTaskHooks.d.ts.map +1 -0
- package/dist/atp/postTaskHooks.js +89 -0
- package/dist/atp/postTaskHooks.js.map +1 -0
- package/dist/atp/tokenTracker.d.ts +46 -0
- package/dist/atp/tokenTracker.d.ts.map +1 -0
- package/dist/atp/tokenTracker.js +120 -0
- package/dist/atp/tokenTracker.js.map +1 -0
- package/dist/channels/activeChannel.d.ts +14 -0
- package/dist/channels/activeChannel.d.ts.map +1 -0
- package/dist/channels/activeChannel.js +18 -0
- package/dist/channels/activeChannel.js.map +1 -0
- package/dist/channels/channelConfig.d.ts +61 -0
- package/dist/channels/channelConfig.d.ts.map +1 -0
- package/dist/channels/channelConfig.js +130 -0
- package/dist/channels/channelConfig.js.map +1 -0
- package/dist/channels/channelManager.d.ts +22 -0
- package/dist/channels/channelManager.d.ts.map +1 -0
- package/dist/channels/channelManager.js +77 -0
- package/dist/channels/channelManager.js.map +1 -0
- package/dist/channels/discord.d.ts +24 -0
- package/dist/channels/discord.d.ts.map +1 -0
- package/dist/channels/discord.js +276 -0
- package/dist/channels/discord.js.map +1 -0
- package/dist/channels/slack.d.ts +25 -0
- package/dist/channels/slack.d.ts.map +1 -0
- package/dist/channels/slack.js +313 -0
- package/dist/channels/slack.js.map +1 -0
- package/dist/channels/telegram.d.ts +20 -0
- package/dist/channels/telegram.d.ts.map +1 -0
- package/dist/channels/telegram.js +273 -0
- package/dist/channels/telegram.js.map +1 -0
- package/dist/channels/types.d.ts +12 -0
- package/dist/channels/types.d.ts.map +1 -0
- package/dist/channels/types.js +5 -0
- package/dist/channels/types.js.map +1 -0
- package/dist/config.d.ts +82 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +144 -0
- package/dist/config.js.map +1 -0
- package/dist/dashboard/security.d.ts +68 -0
- package/dist/dashboard/security.d.ts.map +1 -0
- package/dist/dashboard/security.js +178 -0
- package/dist/dashboard/security.js.map +1 -0
- package/dist/dashboard/securityHelpers.d.ts +10 -0
- package/dist/dashboard/securityHelpers.d.ts.map +1 -0
- package/dist/dashboard/securityHelpers.js +22 -0
- package/dist/dashboard/securityHelpers.js.map +1 -0
- package/dist/dashboard/server.d.ts +18 -0
- package/dist/dashboard/server.d.ts.map +1 -0
- package/dist/dashboard/server.js +3207 -0
- package/dist/dashboard/server.js.map +1 -0
- package/dist/flows/codeScanFlow.d.ts +14 -0
- package/dist/flows/codeScanFlow.d.ts.map +1 -0
- package/dist/flows/codeScanFlow.js +204 -0
- package/dist/flows/codeScanFlow.js.map +1 -0
- package/dist/flows/gitleaksScanFlow.d.ts +12 -0
- package/dist/flows/gitleaksScanFlow.d.ts.map +1 -0
- package/dist/flows/gitleaksScanFlow.js +205 -0
- package/dist/flows/gitleaksScanFlow.js.map +1 -0
- package/dist/flows/index.d.ts +30 -0
- package/dist/flows/index.d.ts.map +1 -0
- package/dist/flows/index.js +43 -0
- package/dist/flows/index.js.map +1 -0
- package/dist/flows/semgrepScanFlow.d.ts +13 -0
- package/dist/flows/semgrepScanFlow.d.ts.map +1 -0
- package/dist/flows/semgrepScanFlow.js +211 -0
- package/dist/flows/semgrepScanFlow.js.map +1 -0
- package/dist/flows/trivyScanFlow.d.ts +13 -0
- package/dist/flows/trivyScanFlow.d.ts.map +1 -0
- package/dist/flows/trivyScanFlow.js +198 -0
- package/dist/flows/trivyScanFlow.js.map +1 -0
- package/dist/identity.d.ts +22 -0
- package/dist/identity.d.ts.map +1 -0
- package/dist/identity.js +34 -0
- package/dist/identity.js.map +1 -0
- package/dist/init.d.ts +8 -0
- package/dist/init.d.ts.map +1 -0
- package/dist/init.js +27 -0
- package/dist/init.js.map +1 -0
- package/dist/integrations/integrationConfig.d.ts +80 -0
- package/dist/integrations/integrationConfig.d.ts.map +1 -0
- package/dist/integrations/integrationConfig.js +146 -0
- package/dist/integrations/integrationConfig.js.map +1 -0
- package/dist/mcp/mcpBridge.d.ts +36 -0
- package/dist/mcp/mcpBridge.d.ts.map +1 -0
- package/dist/mcp/mcpBridge.js +157 -0
- package/dist/mcp/mcpBridge.js.map +1 -0
- package/dist/memory/agentMemory.d.ts +32 -0
- package/dist/memory/agentMemory.d.ts.map +1 -0
- package/dist/memory/agentMemory.js +116 -0
- package/dist/memory/agentMemory.js.map +1 -0
- package/dist/memory/autoCompaction.d.ts +46 -0
- package/dist/memory/autoCompaction.d.ts.map +1 -0
- package/dist/memory/autoCompaction.js +220 -0
- package/dist/memory/autoCompaction.js.map +1 -0
- package/dist/memory/compaction.d.ts +17 -0
- package/dist/memory/compaction.d.ts.map +1 -0
- package/dist/memory/compaction.js +27 -0
- package/dist/memory/compaction.js.map +1 -0
- package/dist/memory/messageHistory.d.ts +28 -0
- package/dist/memory/messageHistory.d.ts.map +1 -0
- package/dist/memory/messageHistory.js +60 -0
- package/dist/memory/messageHistory.js.map +1 -0
- package/dist/memory/sessionLifecycle.d.ts +30 -0
- package/dist/memory/sessionLifecycle.d.ts.map +1 -0
- package/dist/memory/sessionLifecycle.js +63 -0
- package/dist/memory/sessionLifecycle.js.map +1 -0
- package/dist/migrate.d.ts +8 -0
- package/dist/migrate.d.ts.map +1 -0
- package/dist/migrate.js +83 -0
- package/dist/migrate.js.map +1 -0
- package/dist/onboarding.d.ts +8 -0
- package/dist/onboarding.d.ts.map +1 -0
- package/dist/onboarding.js +188 -0
- package/dist/onboarding.js.map +1 -0
- package/dist/tools/domain/baFileTools.d.ts +7 -0
- package/dist/tools/domain/baFileTools.d.ts.map +1 -0
- package/dist/tools/domain/baFileTools.js +46 -0
- package/dist/tools/domain/baFileTools.js.map +1 -0
- package/dist/tools/domain/baTools.d.ts +6 -0
- package/dist/tools/domain/baTools.d.ts.map +1 -0
- package/dist/tools/domain/baTools.js +160 -0
- package/dist/tools/domain/baTools.js.map +1 -0
- package/dist/tools/domain/baseSpecialistTools.d.ts +22 -0
- package/dist/tools/domain/baseSpecialistTools.d.ts.map +1 -0
- package/dist/tools/domain/baseSpecialistTools.js +183 -0
- package/dist/tools/domain/baseSpecialistTools.js.map +1 -0
- package/dist/tools/domain/devTools.d.ts +6 -0
- package/dist/tools/domain/devTools.d.ts.map +1 -0
- package/dist/tools/domain/devTools.js +191 -0
- package/dist/tools/domain/devTools.js.map +1 -0
- package/dist/tools/domain/gitTools.d.ts +36 -0
- package/dist/tools/domain/gitTools.d.ts.map +1 -0
- package/dist/tools/domain/gitTools.js +279 -0
- package/dist/tools/domain/gitTools.js.map +1 -0
- package/dist/tools/domain/qaTools.d.ts +6 -0
- package/dist/tools/domain/qaTools.d.ts.map +1 -0
- package/dist/tools/domain/qaTools.js +275 -0
- package/dist/tools/domain/qaTools.js.map +1 -0
- package/dist/tools/domain/securityFlowTools.d.ts +6 -0
- package/dist/tools/domain/securityFlowTools.d.ts.map +1 -0
- package/dist/tools/domain/securityFlowTools.js +156 -0
- package/dist/tools/domain/securityFlowTools.js.map +1 -0
- package/dist/tools/pm/employeeTools.d.ts +15 -0
- package/dist/tools/pm/employeeTools.d.ts.map +1 -0
- package/dist/tools/pm/employeeTools.js +117 -0
- package/dist/tools/pm/employeeTools.js.map +1 -0
- package/dist/tools/pm/taskTools.d.ts +31 -0
- package/dist/tools/pm/taskTools.d.ts.map +1 -0
- package/dist/tools/pm/taskTools.js +534 -0
- package/dist/tools/pm/taskTools.js.map +1 -0
- package/dist/tools/shared/dateTools.d.ts +7 -0
- package/dist/tools/shared/dateTools.d.ts.map +1 -0
- package/dist/tools/shared/dateTools.js +35 -0
- package/dist/tools/shared/dateTools.js.map +1 -0
- package/dist/tools/shared/fileTools.d.ts +33 -0
- package/dist/tools/shared/fileTools.d.ts.map +1 -0
- package/dist/tools/shared/fileTools.js +312 -0
- package/dist/tools/shared/fileTools.js.map +1 -0
- package/dist/tools/shared/memoryTools.d.ts +18 -0
- package/dist/tools/shared/memoryTools.d.ts.map +1 -0
- package/dist/tools/shared/memoryTools.js +275 -0
- package/dist/tools/shared/memoryTools.js.map +1 -0
- package/dist/tools/shared/messagingTools.d.ts +14 -0
- package/dist/tools/shared/messagingTools.d.ts.map +1 -0
- package/dist/tools/shared/messagingTools.js +95 -0
- package/dist/tools/shared/messagingTools.js.map +1 -0
- package/dist/tools/shared/webTools.d.ts +12 -0
- package/dist/tools/shared/webTools.d.ts.map +1 -0
- package/dist/tools/shared/webTools.js +140 -0
- package/dist/tools/shared/webTools.js.map +1 -0
- package/dist/tower.d.ts +8 -0
- package/dist/tower.d.ts.map +1 -0
- package/dist/tower.js +774 -0
- package/dist/tower.js.map +1 -0
- package/package.json +71 -0
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord channel for TOWER.
|
|
3
|
+
*
|
|
4
|
+
* Routes messages from an authorized Discord text channel OR direct messages (DMs)
|
|
5
|
+
* to the PM agent and streams the response back. Supports the same slash commands
|
|
6
|
+
* as CLI, Telegram, and Slack.
|
|
7
|
+
*
|
|
8
|
+
* Uses WebSocket Gateway — no public URL required.
|
|
9
|
+
*
|
|
10
|
+
* Required env vars:
|
|
11
|
+
* DISCORD_BOT_TOKEN — Bot token from Discord Developer Portal
|
|
12
|
+
* DISCORD_CHANNEL_ID — Authorized text channel ID (right-click → Copy Channel ID)
|
|
13
|
+
*
|
|
14
|
+
* DMs: The bot also responds to direct messages automatically.
|
|
15
|
+
* Note: Enable "Message Content Intent" in the Discord Developer Portal.
|
|
16
|
+
*/
|
|
17
|
+
import type { PMAgent } from "../agents/pmAgent.js";
|
|
18
|
+
import type { VECChannel } from "./types.js";
|
|
19
|
+
/**
|
|
20
|
+
* Create and return a DiscordChannel if DISCORD_BOT_TOKEN and
|
|
21
|
+
* DISCORD_CHANNEL_ID are set, otherwise returns null (Discord silently disabled).
|
|
22
|
+
*/
|
|
23
|
+
export declare function createDiscordChannel(pmAgent: PMAgent): VECChannel | null;
|
|
24
|
+
//# sourceMappingURL=discord.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.d.ts","sourceRoot":"","sources":["../../src/channels/discord.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAWH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAmR7C;;;GAGG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CASxE"}
|
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Discord channel for TOWER.
|
|
3
|
+
*
|
|
4
|
+
* Routes messages from an authorized Discord text channel OR direct messages (DMs)
|
|
5
|
+
* to the PM agent and streams the response back. Supports the same slash commands
|
|
6
|
+
* as CLI, Telegram, and Slack.
|
|
7
|
+
*
|
|
8
|
+
* Uses WebSocket Gateway — no public URL required.
|
|
9
|
+
*
|
|
10
|
+
* Required env vars:
|
|
11
|
+
* DISCORD_BOT_TOKEN — Bot token from Discord Developer Portal
|
|
12
|
+
* DISCORD_CHANNEL_ID — Authorized text channel ID (right-click → Copy Channel ID)
|
|
13
|
+
*
|
|
14
|
+
* DMs: The bot also responds to direct messages automatically.
|
|
15
|
+
* Note: Enable "Message Content Intent" in the Discord Developer Portal.
|
|
16
|
+
*/
|
|
17
|
+
import { Client, GatewayIntentBits, ChannelType } from "discord.js";
|
|
18
|
+
import { ATPDatabase } from "../atp/database.js";
|
|
19
|
+
import { MessageQueue } from "../atp/messageQueue.js";
|
|
20
|
+
import { EventLog } from "../atp/eventLog.js";
|
|
21
|
+
import { AGENT_DISPLAY_NAMES } from "../atp/agentMessageQueue.js";
|
|
22
|
+
import { founder } from "../identity.js";
|
|
23
|
+
import { loadAgentMemory, isFirstInteraction, markFirstInteractionDone } from "../memory/agentMemory.js";
|
|
24
|
+
import { ActiveChannelState } from "./activeChannel.js";
|
|
25
|
+
// Discord max message length
|
|
26
|
+
const DISCORD_MAX = 2000;
|
|
27
|
+
/** Split a long string into <= DISCORD_MAX chunks, preferring newline boundaries. */
|
|
28
|
+
function splitMessage(text) {
|
|
29
|
+
if (text.length <= DISCORD_MAX)
|
|
30
|
+
return [text];
|
|
31
|
+
const chunks = [];
|
|
32
|
+
let remaining = text;
|
|
33
|
+
while (remaining.length > DISCORD_MAX) {
|
|
34
|
+
const slice = remaining.slice(0, DISCORD_MAX);
|
|
35
|
+
const lastNl = slice.lastIndexOf("\n");
|
|
36
|
+
const cutAt = lastNl > DISCORD_MAX / 2 ? lastNl + 1 : DISCORD_MAX;
|
|
37
|
+
chunks.push(remaining.slice(0, cutAt));
|
|
38
|
+
remaining = remaining.slice(cutAt);
|
|
39
|
+
}
|
|
40
|
+
if (remaining)
|
|
41
|
+
chunks.push(remaining);
|
|
42
|
+
return chunks;
|
|
43
|
+
}
|
|
44
|
+
/** Safe fire-and-forget Discord API call — never throws. */
|
|
45
|
+
async function discordSend(fn) {
|
|
46
|
+
try {
|
|
47
|
+
await fn();
|
|
48
|
+
}
|
|
49
|
+
catch (err) {
|
|
50
|
+
console.error("[Discord]", err?.message ?? err);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
class DiscordChannel {
|
|
54
|
+
pmAgent;
|
|
55
|
+
client;
|
|
56
|
+
channelId;
|
|
57
|
+
botToken;
|
|
58
|
+
// State for capturing the current PM response destined for Discord
|
|
59
|
+
pendingChannel = null;
|
|
60
|
+
buffer = "";
|
|
61
|
+
typingTimer = null;
|
|
62
|
+
constructor(botToken, channelId, pmAgent) {
|
|
63
|
+
this.pmAgent = pmAgent;
|
|
64
|
+
this.botToken = botToken;
|
|
65
|
+
this.channelId = channelId;
|
|
66
|
+
this.client = new Client({
|
|
67
|
+
intents: [
|
|
68
|
+
GatewayIntentBits.Guilds,
|
|
69
|
+
GatewayIntentBits.GuildMessages,
|
|
70
|
+
GatewayIntentBits.MessageContent,
|
|
71
|
+
GatewayIntentBits.DirectMessages,
|
|
72
|
+
],
|
|
73
|
+
});
|
|
74
|
+
// Subscribe to PM events — capture text and fire reply on agent_end
|
|
75
|
+
pmAgent.subscribe((event) => {
|
|
76
|
+
if (this.pendingChannel === null)
|
|
77
|
+
return; // not a Discord-triggered prompt
|
|
78
|
+
if (event.type === "message_update") {
|
|
79
|
+
const ae = event.assistantMessageEvent;
|
|
80
|
+
if (ae.type === "text_delta" && ae.delta) {
|
|
81
|
+
this.buffer += ae.delta;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
else if (event.type === "agent_end") {
|
|
85
|
+
void this.flushReply();
|
|
86
|
+
}
|
|
87
|
+
});
|
|
88
|
+
// Handle incoming messages
|
|
89
|
+
this.client.on("messageCreate", (message) => {
|
|
90
|
+
// Ignore bot's own messages and other bots
|
|
91
|
+
if (message.author.bot)
|
|
92
|
+
return;
|
|
93
|
+
const channel = message.channel;
|
|
94
|
+
const channelId = channel.id;
|
|
95
|
+
const isDM = channel.type === ChannelType.DM;
|
|
96
|
+
console.log(`[Discord] message event — channel: ${channelId}, DM: ${isDM}, text: ${message.content.slice(0, 80)}`);
|
|
97
|
+
if (!this.isAuthorized(channelId, isDM))
|
|
98
|
+
return;
|
|
99
|
+
void this.handleMessage(message.content, channel);
|
|
100
|
+
});
|
|
101
|
+
this.client.on("error", (err) => {
|
|
102
|
+
console.error("[Discord] Client error:", err.message);
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
/** Allow the configured channel AND any direct message to the bot. */
|
|
106
|
+
isAuthorized(channelId, isDM) {
|
|
107
|
+
return channelId === this.channelId || isDM;
|
|
108
|
+
}
|
|
109
|
+
async handleMessage(text, channel) {
|
|
110
|
+
if (!text.trim())
|
|
111
|
+
return;
|
|
112
|
+
const cmd = text.trim();
|
|
113
|
+
// ── Slash-style commands (prefixed with !) ──────────────────────────────
|
|
114
|
+
if (cmd.startsWith("!")) {
|
|
115
|
+
await this.handleCommand(cmd.slice(1).trim(), channel);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// ── Route to PM agent ─────────────────────────────────────────────────
|
|
119
|
+
this.pendingChannel = channel;
|
|
120
|
+
this.buffer = "";
|
|
121
|
+
// Show typing indicator
|
|
122
|
+
await discordSend(() => channel.sendTyping());
|
|
123
|
+
this.typingTimer = setInterval(() => {
|
|
124
|
+
if (this.pendingChannel !== null) {
|
|
125
|
+
void discordSend(() => channel.sendTyping());
|
|
126
|
+
}
|
|
127
|
+
}, 8_000);
|
|
128
|
+
// Inject PM's memory + founder context so PM responds naturally
|
|
129
|
+
const memory = loadAgentMemory("pm");
|
|
130
|
+
const firstTime = isFirstInteraction("pm");
|
|
131
|
+
if (firstTime)
|
|
132
|
+
markFirstInteractionDone("pm");
|
|
133
|
+
const founderPrompt = (memory ? `${memory}\n\n` : "") +
|
|
134
|
+
(firstTime
|
|
135
|
+
? `[FIRST INTERACTION — Sir is messaging you for the first time.]\n` +
|
|
136
|
+
`Introduce yourself briefly and warmly — one sentence. Then respond to what he said. Natural, not robotic.\n\n`
|
|
137
|
+
: "") +
|
|
138
|
+
`[Message from ${founder.name} (Sir) via Discord — agent key: '${founder.agentKey}']\n` +
|
|
139
|
+
`Sir says: ${text}`;
|
|
140
|
+
// Mark this as a Discord-originated prompt so PM replies route back here only
|
|
141
|
+
ActiveChannelState.set("discord");
|
|
142
|
+
try {
|
|
143
|
+
await this.pmAgent.prompt(founderPrompt);
|
|
144
|
+
}
|
|
145
|
+
catch (err) {
|
|
146
|
+
this.clearPending();
|
|
147
|
+
await discordSend(() => channel.send(`Error talking to PM: ${err}`));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
async handleCommand(cmd, channel) {
|
|
151
|
+
if (cmd === "board") {
|
|
152
|
+
const board = ATPDatabase.taskBoard();
|
|
153
|
+
await discordSend(() => channel.send(`\`\`\`\n${board}\n\`\`\``));
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
if (cmd === "queue") {
|
|
157
|
+
const msgs = MessageQueue.peek();
|
|
158
|
+
if (!msgs.length) {
|
|
159
|
+
await discordSend(() => channel.send("[PM Queue] Empty."));
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const lines = [`[PM Queue] ${msgs.length} message(s):`];
|
|
163
|
+
for (const m of msgs) {
|
|
164
|
+
const ref = m.task_id ? ` ${m.task_id}` : "";
|
|
165
|
+
lines.push(` [${m.type}] ${m.from_agent}${ref}: ${m.message.substring(0, 100)}`);
|
|
166
|
+
}
|
|
167
|
+
await discordSend(() => channel.send(lines.join("\n")));
|
|
168
|
+
}
|
|
169
|
+
return;
|
|
170
|
+
}
|
|
171
|
+
if (cmd === "events") {
|
|
172
|
+
const events = EventLog.getEvents(20);
|
|
173
|
+
if (!events.length) {
|
|
174
|
+
await discordSend(() => channel.send("[Events] None recorded yet."));
|
|
175
|
+
}
|
|
176
|
+
else {
|
|
177
|
+
const lines = [`[Events] Last ${events.length}:`];
|
|
178
|
+
for (const e of events) {
|
|
179
|
+
const ts = e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : "?";
|
|
180
|
+
const ref = e.task_id ? ` | ${e.task_id}` : "";
|
|
181
|
+
lines.push(` ${ts} [${e.event_type}] ${e.agent_id || "-"}${ref} — ${(e.message || "").substring(0, 80)}`);
|
|
182
|
+
}
|
|
183
|
+
await discordSend(() => channel.send(lines.join("\n")));
|
|
184
|
+
}
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (cmd === "dir") {
|
|
188
|
+
const dir = ATPDatabase.employeeDirectory();
|
|
189
|
+
await discordSend(() => channel.send(`\`\`\`\n${dir}\n\`\`\``));
|
|
190
|
+
return;
|
|
191
|
+
}
|
|
192
|
+
if (cmd === "agents") {
|
|
193
|
+
const lines = Object.entries(AGENT_DISPLAY_NAMES)
|
|
194
|
+
.filter(([id]) => id !== "user")
|
|
195
|
+
.map(([id, name]) => ` ${id.padEnd(12)} ${name}`);
|
|
196
|
+
await discordSend(() => channel.send(["Agents:", ...lines].join("\n")));
|
|
197
|
+
return;
|
|
198
|
+
}
|
|
199
|
+
if (cmd === "help") {
|
|
200
|
+
const help = [
|
|
201
|
+
"**TOWER — VEC Commands**",
|
|
202
|
+
"`!board` — Task board",
|
|
203
|
+
"`!queue` — PM message queue",
|
|
204
|
+
"`!events` — Recent events (last 20)",
|
|
205
|
+
"`!dir` — Employee directory",
|
|
206
|
+
"`!agents` — Agent list",
|
|
207
|
+
"`!help` — This help",
|
|
208
|
+
"",
|
|
209
|
+
"Send any other text to talk to Arjun (PM).",
|
|
210
|
+
].join("\n");
|
|
211
|
+
await discordSend(() => channel.send(help));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
// Unknown command — treat as regular message
|
|
215
|
+
await this.handleMessage(cmd, channel);
|
|
216
|
+
}
|
|
217
|
+
async flushReply() {
|
|
218
|
+
this.clearTypingTimer();
|
|
219
|
+
const channel = this.pendingChannel;
|
|
220
|
+
const text = this.buffer.trim();
|
|
221
|
+
this.clearPending();
|
|
222
|
+
if (!channel || !text)
|
|
223
|
+
return;
|
|
224
|
+
const chunks = splitMessage(text);
|
|
225
|
+
for (const chunk of chunks) {
|
|
226
|
+
await discordSend(() => channel.send(chunk));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
clearTypingTimer() {
|
|
230
|
+
if (this.typingTimer) {
|
|
231
|
+
clearInterval(this.typingTimer);
|
|
232
|
+
this.typingTimer = null;
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
clearPending() {
|
|
236
|
+
this.clearTypingTimer();
|
|
237
|
+
this.pendingChannel = null;
|
|
238
|
+
this.buffer = "";
|
|
239
|
+
}
|
|
240
|
+
/** Send a proactive message to the authorized channel (e.g. PM -> user forwarding). */
|
|
241
|
+
async sendToUser(text) {
|
|
242
|
+
const channel = this.client.channels.cache.get(this.channelId);
|
|
243
|
+
if (!channel || !("send" in channel))
|
|
244
|
+
return;
|
|
245
|
+
const chunks = splitMessage(text);
|
|
246
|
+
for (const chunk of chunks) {
|
|
247
|
+
await discordSend(() => channel.send(chunk));
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
async start() {
|
|
251
|
+
try {
|
|
252
|
+
await this.client.login(this.botToken);
|
|
253
|
+
console.log(` [Discord] Bot started — channel: ${this.channelId} + DMs`);
|
|
254
|
+
}
|
|
255
|
+
catch (err) {
|
|
256
|
+
console.error("[Discord] Failed to start:", err?.message ?? err);
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
async stop() {
|
|
260
|
+
this.clearPending();
|
|
261
|
+
this.client.destroy();
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Create and return a DiscordChannel if DISCORD_BOT_TOKEN and
|
|
266
|
+
* DISCORD_CHANNEL_ID are set, otherwise returns null (Discord silently disabled).
|
|
267
|
+
*/
|
|
268
|
+
export function createDiscordChannel(pmAgent) {
|
|
269
|
+
const botToken = process.env.DISCORD_BOT_TOKEN?.trim() ?? "";
|
|
270
|
+
const channelId = process.env.DISCORD_CHANNEL_ID?.trim() ?? "";
|
|
271
|
+
if (!botToken || !channelId) {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
return new DiscordChannel(botToken, channelId, pmAgent);
|
|
275
|
+
}
|
|
276
|
+
//# sourceMappingURL=discord.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"discord.js","sourceRoot":"","sources":["../../src/channels/discord.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack channel for TOWER.
|
|
3
|
+
*
|
|
4
|
+
* Routes messages from an authorized Slack channel OR direct messages (DMs)
|
|
5
|
+
* to the PM agent and streams the response back. Supports the same slash
|
|
6
|
+
* commands as CLI and Telegram.
|
|
7
|
+
*
|
|
8
|
+
* Uses Socket Mode (WebSocket) — no public URL required.
|
|
9
|
+
*
|
|
10
|
+
* Required env vars:
|
|
11
|
+
* SLACK_BOT_TOKEN — Bot User OAuth Token (xoxb-...)
|
|
12
|
+
* SLACK_APP_TOKEN — App-Level Token (xapp-..., scope: connections:write)
|
|
13
|
+
* SLACK_CHANNEL_ID — Channel ID where the bot listens and posts (C0123456789)
|
|
14
|
+
*
|
|
15
|
+
* DMs: The bot also responds to direct messages. Add `message.im` to your
|
|
16
|
+
* Slack app's Event Subscriptions alongside `message.channels`.
|
|
17
|
+
*/
|
|
18
|
+
import type { PMAgent } from "../agents/pmAgent.js";
|
|
19
|
+
import type { VECChannel } from "./types.js";
|
|
20
|
+
/**
|
|
21
|
+
* Create and return a SlackChannel if SLACK_BOT_TOKEN, SLACK_APP_TOKEN, and
|
|
22
|
+
* SLACK_CHANNEL_ID are set, otherwise returns null (Slack silently disabled).
|
|
23
|
+
*/
|
|
24
|
+
export declare function createSlackChannel(pmAgent: PMAgent): VECChannel | null;
|
|
25
|
+
//# sourceMappingURL=slack.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slack.d.ts","sourceRoot":"","sources":["../../src/channels/slack.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAQH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AACpD,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AA6T7C;;;GAGG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,UAAU,GAAG,IAAI,CAoBtE"}
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack channel for TOWER.
|
|
3
|
+
*
|
|
4
|
+
* Routes messages from an authorized Slack channel OR direct messages (DMs)
|
|
5
|
+
* to the PM agent and streams the response back. Supports the same slash
|
|
6
|
+
* commands as CLI and Telegram.
|
|
7
|
+
*
|
|
8
|
+
* Uses Socket Mode (WebSocket) — no public URL required.
|
|
9
|
+
*
|
|
10
|
+
* Required env vars:
|
|
11
|
+
* SLACK_BOT_TOKEN — Bot User OAuth Token (xoxb-...)
|
|
12
|
+
* SLACK_APP_TOKEN — App-Level Token (xapp-..., scope: connections:write)
|
|
13
|
+
* SLACK_CHANNEL_ID — Channel ID where the bot listens and posts (C0123456789)
|
|
14
|
+
*
|
|
15
|
+
* DMs: The bot also responds to direct messages. Add `message.im` to your
|
|
16
|
+
* Slack app's Event Subscriptions alongside `message.channels`.
|
|
17
|
+
*/
|
|
18
|
+
import { App, LogLevel } from "@slack/bolt";
|
|
19
|
+
import { ATPDatabase } from "../atp/database.js";
|
|
20
|
+
import { MessageQueue } from "../atp/messageQueue.js";
|
|
21
|
+
import { EventLog } from "../atp/eventLog.js";
|
|
22
|
+
import { AGENT_DISPLAY_NAMES } from "../atp/agentMessageQueue.js";
|
|
23
|
+
import { founder } from "../identity.js";
|
|
24
|
+
import { loadAgentMemory, isFirstInteraction, markFirstInteractionDone } from "../memory/agentMemory.js";
|
|
25
|
+
import { ActiveChannelState } from "./activeChannel.js";
|
|
26
|
+
// Slack max message length
|
|
27
|
+
const SLACK_MAX = 4000;
|
|
28
|
+
/** Split a long string into <= SLACK_MAX chunks, preferring newline boundaries. */
|
|
29
|
+
function splitMessage(text) {
|
|
30
|
+
if (text.length <= SLACK_MAX)
|
|
31
|
+
return [text];
|
|
32
|
+
const chunks = [];
|
|
33
|
+
let remaining = text;
|
|
34
|
+
while (remaining.length > SLACK_MAX) {
|
|
35
|
+
const slice = remaining.slice(0, SLACK_MAX);
|
|
36
|
+
const lastNl = slice.lastIndexOf("\n");
|
|
37
|
+
const cutAt = lastNl > SLACK_MAX / 2 ? lastNl + 1 : SLACK_MAX;
|
|
38
|
+
chunks.push(remaining.slice(0, cutAt));
|
|
39
|
+
remaining = remaining.slice(cutAt);
|
|
40
|
+
}
|
|
41
|
+
if (remaining)
|
|
42
|
+
chunks.push(remaining);
|
|
43
|
+
return chunks;
|
|
44
|
+
}
|
|
45
|
+
/** Safe fire-and-forget Slack API call — never throws. */
|
|
46
|
+
async function slackSend(fn) {
|
|
47
|
+
try {
|
|
48
|
+
await fn();
|
|
49
|
+
}
|
|
50
|
+
catch (err) {
|
|
51
|
+
console.error("[Slack]", err?.message ?? err);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
class SlackChannel {
|
|
55
|
+
pmAgent;
|
|
56
|
+
app;
|
|
57
|
+
channelId;
|
|
58
|
+
// State for capturing the current PM response destined for Slack
|
|
59
|
+
pendingChannel = null;
|
|
60
|
+
pendingThreadTs = undefined;
|
|
61
|
+
buffer = "";
|
|
62
|
+
constructor(botToken, appToken, channelId, pmAgent) {
|
|
63
|
+
this.pmAgent = pmAgent;
|
|
64
|
+
this.channelId = channelId;
|
|
65
|
+
this.app = new App({
|
|
66
|
+
token: botToken,
|
|
67
|
+
socketMode: true,
|
|
68
|
+
appToken,
|
|
69
|
+
logLevel: LogLevel.ERROR, // suppress verbose Bolt logging
|
|
70
|
+
});
|
|
71
|
+
// Subscribe to PM events — capture text and fire reply on agent_end
|
|
72
|
+
pmAgent.subscribe((event) => {
|
|
73
|
+
if (this.pendingChannel === null)
|
|
74
|
+
return; // not a Slack-triggered prompt
|
|
75
|
+
if (event.type === "message_update") {
|
|
76
|
+
const ae = event.assistantMessageEvent;
|
|
77
|
+
if (ae.type === "text_delta" && ae.delta) {
|
|
78
|
+
this.buffer += ae.delta;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
else if (event.type === "agent_end") {
|
|
82
|
+
void this.flushReply();
|
|
83
|
+
}
|
|
84
|
+
});
|
|
85
|
+
// Handle incoming messages
|
|
86
|
+
this.app.message(async ({ message }) => {
|
|
87
|
+
const msg = message;
|
|
88
|
+
console.log(`[Slack] message event — channel: ${msg.channel}, subtype: ${msg.subtype ?? "none"}, text: ${typeof msg.text === "string" ? msg.text.slice(0, 80) : "(no text)"}`);
|
|
89
|
+
// Only handle regular user messages (not bot messages, edits, etc.)
|
|
90
|
+
if (msg.subtype)
|
|
91
|
+
return; // skip bot messages, edits, joins, etc.
|
|
92
|
+
if (!msg.text || typeof msg.text !== "string")
|
|
93
|
+
return;
|
|
94
|
+
await this.handleMessage(msg.text, msg.channel, msg.ts);
|
|
95
|
+
});
|
|
96
|
+
// Handle /vec slash command (optional — if registered in Slack app settings)
|
|
97
|
+
this.app.command("/vec", async ({ command, ack }) => {
|
|
98
|
+
await ack();
|
|
99
|
+
if (!this.isAuthorized(command.channel_id))
|
|
100
|
+
return;
|
|
101
|
+
await this.handleCommand(command.text.trim(), command.channel_id, command.trigger_id);
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
/** Allow the configured channel AND any direct message to the bot. */
|
|
105
|
+
isAuthorized(channel) {
|
|
106
|
+
return channel === this.channelId || channel.startsWith("D");
|
|
107
|
+
}
|
|
108
|
+
async handleMessage(text, channel, threadTs) {
|
|
109
|
+
console.log(`[Slack] handleMessage — channel: ${channel}, authorized: ${this.isAuthorized(channel)}, channelId: ${this.channelId}`);
|
|
110
|
+
if (!this.isAuthorized(channel))
|
|
111
|
+
return;
|
|
112
|
+
const cmd = text.trim();
|
|
113
|
+
// ── Slash-style commands (prefixed with !) ──────────────────────────────
|
|
114
|
+
if (cmd.startsWith("!")) {
|
|
115
|
+
await this.handleCommand(cmd.slice(1).trim(), channel, threadTs);
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
// ── Route to PM agent ─────────────────────────────────────────────────
|
|
119
|
+
this.pendingChannel = channel;
|
|
120
|
+
this.pendingThreadTs = threadTs;
|
|
121
|
+
this.buffer = "";
|
|
122
|
+
// Inject PM's memory + founder context so PM responds naturally
|
|
123
|
+
const memory = loadAgentMemory("pm");
|
|
124
|
+
const firstTime = isFirstInteraction("pm");
|
|
125
|
+
if (firstTime)
|
|
126
|
+
markFirstInteractionDone("pm");
|
|
127
|
+
const founderPrompt = (memory ? `${memory}\n\n` : "") +
|
|
128
|
+
(firstTime
|
|
129
|
+
? `[FIRST INTERACTION — Sir is messaging you for the first time.]\n` +
|
|
130
|
+
`Introduce yourself briefly and warmly — one sentence. Then respond to what he said. Natural, not robotic.\n\n`
|
|
131
|
+
: "") +
|
|
132
|
+
`[Message from ${founder.name} (Sir) via Slack — agent key: '${founder.agentKey}']\n` +
|
|
133
|
+
`Sir says: ${text}`;
|
|
134
|
+
// Mark this as a Slack-originated prompt so PM replies route back here only
|
|
135
|
+
ActiveChannelState.set("slack");
|
|
136
|
+
try {
|
|
137
|
+
await this.pmAgent.prompt(founderPrompt);
|
|
138
|
+
}
|
|
139
|
+
catch (err) {
|
|
140
|
+
this.clearPending();
|
|
141
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
142
|
+
channel,
|
|
143
|
+
text: `Error talking to PM: ${err}`,
|
|
144
|
+
thread_ts: threadTs,
|
|
145
|
+
}));
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
async handleCommand(cmd, channel, threadTs) {
|
|
149
|
+
if (cmd === "board") {
|
|
150
|
+
const board = ATPDatabase.taskBoard();
|
|
151
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
152
|
+
channel,
|
|
153
|
+
text: `\`\`\`\n${board}\n\`\`\``,
|
|
154
|
+
thread_ts: threadTs,
|
|
155
|
+
}));
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
if (cmd === "queue") {
|
|
159
|
+
const msgs = MessageQueue.peek();
|
|
160
|
+
if (!msgs.length) {
|
|
161
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
162
|
+
channel,
|
|
163
|
+
text: "[PM Queue] Empty.",
|
|
164
|
+
thread_ts: threadTs,
|
|
165
|
+
}));
|
|
166
|
+
}
|
|
167
|
+
else {
|
|
168
|
+
const lines = [`[PM Queue] ${msgs.length} message(s):`];
|
|
169
|
+
for (const m of msgs) {
|
|
170
|
+
const ref = m.task_id ? ` ${m.task_id}` : "";
|
|
171
|
+
lines.push(` [${m.type}] ${m.from_agent}${ref}: ${m.message.substring(0, 100)}`);
|
|
172
|
+
}
|
|
173
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
174
|
+
channel,
|
|
175
|
+
text: lines.join("\n"),
|
|
176
|
+
thread_ts: threadTs,
|
|
177
|
+
}));
|
|
178
|
+
}
|
|
179
|
+
return;
|
|
180
|
+
}
|
|
181
|
+
if (cmd === "events") {
|
|
182
|
+
const events = EventLog.getEvents(20);
|
|
183
|
+
if (!events.length) {
|
|
184
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
185
|
+
channel,
|
|
186
|
+
text: "[Events] None recorded yet.",
|
|
187
|
+
thread_ts: threadTs,
|
|
188
|
+
}));
|
|
189
|
+
}
|
|
190
|
+
else {
|
|
191
|
+
const lines = [`[Events] Last ${events.length}:`];
|
|
192
|
+
for (const e of events) {
|
|
193
|
+
const ts = e.timestamp ? new Date(e.timestamp).toLocaleTimeString() : "?";
|
|
194
|
+
const ref = e.task_id ? ` | ${e.task_id}` : "";
|
|
195
|
+
lines.push(` ${ts} [${e.event_type}] ${e.agent_id || "-"}${ref} — ${(e.message || "").substring(0, 80)}`);
|
|
196
|
+
}
|
|
197
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
198
|
+
channel,
|
|
199
|
+
text: lines.join("\n"),
|
|
200
|
+
thread_ts: threadTs,
|
|
201
|
+
}));
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
if (cmd === "dir") {
|
|
206
|
+
const dir = ATPDatabase.employeeDirectory();
|
|
207
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
208
|
+
channel,
|
|
209
|
+
text: `\`\`\`\n${dir}\n\`\`\``,
|
|
210
|
+
thread_ts: threadTs,
|
|
211
|
+
}));
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (cmd === "agents") {
|
|
215
|
+
const lines = Object.entries(AGENT_DISPLAY_NAMES)
|
|
216
|
+
.filter(([id]) => id !== "user")
|
|
217
|
+
.map(([id, name]) => ` ${id.padEnd(12)} ${name}`);
|
|
218
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
219
|
+
channel,
|
|
220
|
+
text: ["Agents:", ...lines].join("\n"),
|
|
221
|
+
thread_ts: threadTs,
|
|
222
|
+
}));
|
|
223
|
+
return;
|
|
224
|
+
}
|
|
225
|
+
if (cmd === "help") {
|
|
226
|
+
const help = [
|
|
227
|
+
"*TOWER — VEC Commands*",
|
|
228
|
+
"`!board` — Task board",
|
|
229
|
+
"`!queue` — PM message queue",
|
|
230
|
+
"`!events` — Recent events (last 20)",
|
|
231
|
+
"`!dir` — Employee directory",
|
|
232
|
+
"`!agents` — Agent list",
|
|
233
|
+
"`!help` — This help",
|
|
234
|
+
"",
|
|
235
|
+
"Send any other text to talk to Arjun (PM).",
|
|
236
|
+
].join("\n");
|
|
237
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
238
|
+
channel,
|
|
239
|
+
text: help,
|
|
240
|
+
thread_ts: threadTs,
|
|
241
|
+
}));
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
// Unknown command — treat as regular message
|
|
245
|
+
await this.handleMessage(cmd, channel, threadTs);
|
|
246
|
+
}
|
|
247
|
+
async flushReply() {
|
|
248
|
+
const channel = this.pendingChannel;
|
|
249
|
+
const threadTs = this.pendingThreadTs;
|
|
250
|
+
const text = this.buffer.trim();
|
|
251
|
+
this.clearPending();
|
|
252
|
+
if (!channel || !text)
|
|
253
|
+
return;
|
|
254
|
+
const chunks = splitMessage(text);
|
|
255
|
+
for (const chunk of chunks) {
|
|
256
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
257
|
+
channel,
|
|
258
|
+
text: chunk,
|
|
259
|
+
thread_ts: threadTs,
|
|
260
|
+
}));
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
clearPending() {
|
|
264
|
+
this.pendingChannel = null;
|
|
265
|
+
this.pendingThreadTs = undefined;
|
|
266
|
+
this.buffer = "";
|
|
267
|
+
}
|
|
268
|
+
/** Send a proactive message to the authorized channel (e.g. PM -> user forwarding). */
|
|
269
|
+
async sendToUser(text) {
|
|
270
|
+
const chunks = splitMessage(text);
|
|
271
|
+
for (const chunk of chunks) {
|
|
272
|
+
await slackSend(() => this.app.client.chat.postMessage({
|
|
273
|
+
channel: this.channelId,
|
|
274
|
+
text: chunk,
|
|
275
|
+
}));
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
async start() {
|
|
279
|
+
try {
|
|
280
|
+
await this.app.start();
|
|
281
|
+
console.log(` [Slack] Bot started — channel: ${this.channelId} + DMs`);
|
|
282
|
+
}
|
|
283
|
+
catch (err) {
|
|
284
|
+
console.error("[Slack] Failed to start:", err?.message ?? err);
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
async stop() {
|
|
288
|
+
this.clearPending();
|
|
289
|
+
await this.app.stop();
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
/**
|
|
293
|
+
* Create and return a SlackChannel if SLACK_BOT_TOKEN, SLACK_APP_TOKEN, and
|
|
294
|
+
* SLACK_CHANNEL_ID are set, otherwise returns null (Slack silently disabled).
|
|
295
|
+
*/
|
|
296
|
+
export function createSlackChannel(pmAgent) {
|
|
297
|
+
const botToken = process.env.SLACK_BOT_TOKEN?.trim() ?? "";
|
|
298
|
+
const appToken = process.env.SLACK_APP_TOKEN?.trim() ?? "";
|
|
299
|
+
const channelId = process.env.SLACK_CHANNEL_ID?.trim() ?? "";
|
|
300
|
+
if (!botToken || !appToken || !channelId) {
|
|
301
|
+
return null;
|
|
302
|
+
}
|
|
303
|
+
if (!botToken.startsWith("xoxb-")) {
|
|
304
|
+
console.warn("[Slack] SLACK_BOT_TOKEN should start with 'xoxb-' — Slack disabled.");
|
|
305
|
+
return null;
|
|
306
|
+
}
|
|
307
|
+
if (!appToken.startsWith("xapp-")) {
|
|
308
|
+
console.warn("[Slack] SLACK_APP_TOKEN should start with 'xapp-' — Slack disabled.");
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
return new SlackChannel(botToken, appToken, channelId, pmAgent);
|
|
312
|
+
}
|
|
313
|
+
//# sourceMappingURL=slack.js.map
|