crewly 1.13.1 → 1.14.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/config/constants.ts +8 -0
- package/config/roles/orchestrator/prompt.md +23 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +6 -0
- package/config/skills/agent/core/reply-channel/execute.sh +14 -2
- package/config/skills/agent/core/slack-post/SKILL.md +126 -0
- package/config/skills/agent/core/slack-post/execute.sh +127 -0
- package/config/skills/agent/remote-browser/SKILL.md +178 -0
- package/config/skills/agent/xiaoyuzhoufm-transcript/SKILL.md +46 -22
- package/config/skills/agent/xiaoyuzhoufm-transcript/execute.sh +82 -214
- package/config/skills/agent/xiaoyuzhoufm-transcript/skill.json +3 -3
- package/config/skills/orchestrator/get-team-status/SKILL.md +5 -1
- package/config/skills/orchestrator/get-team-status/execute.sh +28 -2
- package/config/slack-app-manifest.json +7 -1
- package/dist/backend/backend/src/config/oauth.config.d.ts +93 -19
- package/dist/backend/backend/src/config/oauth.config.d.ts.map +1 -1
- package/dist/backend/backend/src/config/oauth.config.js +121 -19
- package/dist/backend/backend/src/config/oauth.config.js.map +1 -1
- package/dist/backend/backend/src/constants.d.ts +60 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +74 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/chat.controller.js +33 -4
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js +27 -9
- package/dist/backend/backend/src/controllers/credentials/google-oauth.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +342 -0
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +23 -7
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts +10 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/idle-detection.service.js +37 -0
- package/dist/backend/backend/src/services/agent/idle-detection.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js +13 -11
- package/dist/backend/backend/src/services/browser/browser-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts +14 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js +56 -0
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
- package/dist/backend/backend/src/services/browser/browser-services.registry.d.ts +63 -0
- package/dist/backend/backend/src/services/browser/browser-services.registry.d.ts.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-services.registry.js +74 -0
- package/dist/backend/backend/src/services/browser/browser-services.registry.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +25 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +19 -7
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +57 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +110 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +21 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +43 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.d.ts +6 -0
- package/dist/backend/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js +1 -1
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -1
- package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +6 -2
- package/dist/backend/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +14 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +8 -0
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js +31 -3
- package/dist/backend/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +34 -0
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +99 -10
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts +209 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js +416 -0
- package/dist/backend/backend/src/services/slack/slack-agent-identity.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts +124 -0
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js +243 -0
- package/dist/backend/backend/src/services/slack/slack-agent-post.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts +11 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-initializer.js +56 -0
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-mention-resolver.d.ts +103 -0
- package/dist/backend/backend/src/services/slack/slack-mention-resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-mention-resolver.js +197 -0
- package/dist/backend/backend/src/services/slack/slack-mention-resolver.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js +18 -0
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts +329 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +821 -0
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -0
- package/dist/backend/backend/src/services/slack/slack.service.d.ts +116 -1
- package/dist/backend/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack.service.js +297 -3
- package/dist/backend/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +22 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js +71 -0
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts +20 -2
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js +27 -2
- package/dist/backend/backend/src/services/wiki/wiki-bookkeep-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts +15 -2
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js +33 -7
- package/dist/backend/backend/src/services/wiki/wiki-reflect-trigger.service.js.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts +37 -0
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js +74 -8
- package/dist/backend/backend/src/services/wiki/wiki-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/types/slack.types.d.ts +87 -0
- package/dist/backend/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/slack.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/backend/config/constants.d.ts +8 -0
- package/dist/backend/config/constants.d.ts.map +1 -1
- package/dist/backend/config/constants.js +8 -0
- package/dist/backend/config/constants.js.map +1 -1
- package/dist/cli/backend/src/config/oauth.config.d.ts +93 -19
- package/dist/cli/backend/src/config/oauth.config.d.ts.map +1 -1
- package/dist/cli/backend/src/config/oauth.config.js +121 -19
- package/dist/cli/backend/src/config/oauth.config.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +60 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +74 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +292 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +420 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts +134 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.mention-resolver.js +232 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.mention-resolver.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +57 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +110 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts +21 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js +43 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.d.ts +6 -0
- package/dist/cli/backend/src/services/core/storage.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js +1 -1
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.d.ts.map +1 -1
- package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js +6 -2
- package/dist/cli/backend/src/services/credential/helpers/gemini-cli-workspace.helper.js.map +1 -1
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +8 -0
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js +31 -3
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts +209 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js +416 -0
- package/dist/cli/backend/src/services/slack/slack-agent-identity.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-mention-resolver.d.ts +103 -0
- package/dist/cli/backend/src/services/slack/slack-mention-resolver.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-mention-resolver.js +197 -0
- package/dist/cli/backend/src/services/slack/slack-mention-resolver.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +18 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts +329 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +821 -0
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +116 -1
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack.service.js +297 -3
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -1
- package/dist/cli/backend/src/types/slack.types.d.ts +87 -0
- package/dist/cli/backend/src/types/slack.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/slack.types.js.map +1 -1
- package/dist/cli/backend/src/utils/team.utils.d.ts +40 -0
- package/dist/cli/backend/src/utils/team.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/team.utils.js +66 -0
- package/dist/cli/backend/src/utils/team.utils.js.map +1 -0
- package/dist/cli/cli/src/index.js +0 -0
- package/dist/cli/config/constants.d.ts +8 -0
- package/dist/cli/config/constants.d.ts.map +1 -1
- package/dist/cli/config/constants.js +8 -0
- package/dist/cli/config/constants.js.map +1 -1
- package/frontend/dist/assets/{index-890d3f9d.js → index-190a957f.js} +294 -294
- package/frontend/dist/assets/{index-8205ea5e.css → index-88feb412.css} +1 -1
- package/frontend/dist/index.html +2 -2
- package/package.json +1 -1
- package/dist/backend/backend/src/controllers/knowledge/index.d.ts +0 -7
- package/dist/backend/backend/src/controllers/knowledge/index.d.ts.map +0 -1
- package/dist/backend/backend/src/controllers/knowledge/index.js +0 -7
- package/dist/backend/backend/src/controllers/knowledge/index.js.map +0 -1
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.d.ts +0 -71
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.d.ts.map +0 -1
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.js +0 -237
- package/dist/backend/backend/src/controllers/knowledge/knowledge.controller.js.map +0 -1
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.d.ts +0 -24
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.d.ts.map +0 -1
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.js +0 -34
- package/dist/backend/backend/src/controllers/knowledge/knowledge.routes.js.map +0 -1
- package/dist/backend/backend/src/controllers/scheduler/unified-scheduler.controller.d.ts +0 -91
- package/dist/backend/backend/src/controllers/scheduler/unified-scheduler.controller.d.ts.map +0 -1
- package/dist/backend/backend/src/controllers/scheduler/unified-scheduler.controller.js +0 -290
- package/dist/backend/backend/src/controllers/scheduler/unified-scheduler.controller.js.map +0 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts +0 -169
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.d.ts.map +0 -1
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js +0 -1779
- package/dist/backend/backend/src/controllers/task-management/task-management.controller.js.map +0 -1
- package/dist/backend/backend/src/index.js.orc-bak-20260529 +0 -3130
- package/dist/backend/backend/src/routes/modules/unified-scheduler.routes.d.ts +0 -27
- package/dist/backend/backend/src/routes/modules/unified-scheduler.routes.d.ts.map +0 -1
- package/dist/backend/backend/src/routes/modules/unified-scheduler.routes.js +0 -40
- package/dist/backend/backend/src/routes/modules/unified-scheduler.routes.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts +0 -513
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js +0 -1568
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-runner.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts +0 -86
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js +0 -147
- package/dist/backend/backend/src/services/agent/crewly-agent/agent-worker.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts +0 -68
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js +0 -131
- package/dist/backend/backend/src/services/agent/crewly-agent/api-client.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts +0 -130
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js +0 -263
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-log.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts +0 -74
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js +0 -140
- package/dist/backend/backend/src/services/agent/crewly-agent/audit-trail.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts +0 -29
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js +0 -279
- package/dist/backend/backend/src/services/agent/crewly-agent/auditor-tools.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts +0 -340
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js +0 -1176
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-runtime.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts +0 -79
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js +0 -145
- package/dist/backend/backend/src/services/agent/crewly-agent/deepseek-sse-transform.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts +0 -79
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js +0 -218
- package/dist/backend/backend/src/services/agent/crewly-agent/env-isolation.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts +0 -16
- package/dist/backend/backend/src/services/agent/crewly-agent/index.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js +0 -16
- package/dist/backend/backend/src/services/agent/crewly-agent/index.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts +0 -135
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js +0 -185
- package/dist/backend/backend/src/services/agent/crewly-agent/mcp-tool-bridge.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +0 -141
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +0 -310
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts +0 -91
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js +0 -143
- package/dist/backend/backend/src/services/agent/crewly-agent/output-filter.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts +0 -103
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js +0 -256
- package/dist/backend/backend/src/services/agent/crewly-agent/prompt-guard.service.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts +0 -143
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js +0 -264
- package/dist/backend/backend/src/services/agent/crewly-agent/rate-limiter.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts +0 -13
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js +0 -91
- package/dist/backend/backend/src/services/agent/crewly-agent/smoke-test.js.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts +0 -135
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.d.ts.map +0 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js +0 -1937
- package/dist/backend/backend/src/services/agent/crewly-agent/tool-registry.js.map +0 -1
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts +0 -429
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js +0 -852
- package/dist/backend/backend/src/services/autonomous/auto-assign.service.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/embedding-provider.d.ts +0 -78
- package/dist/backend/backend/src/services/knowledge/embedding-provider.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/embedding-provider.js +0 -179
- package/dist/backend/backend/src/services/knowledge/embedding-provider.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts +0 -118
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js +0 -241
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/fts5-query-sanitizer.d.ts +0 -102
- package/dist/backend/backend/src/services/knowledge/fts5-query-sanitizer.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/fts5-query-sanitizer.js +0 -118
- package/dist/backend/backend/src/services/knowledge/fts5-query-sanitizer.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/index.d.ts +0 -8
- package/dist/backend/backend/src/services/knowledge/index.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/index.js +0 -8
- package/dist/backend/backend/src/services/knowledge/index.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +0 -241
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +0 -590
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/knowledge.service.d.ts +0 -153
- package/dist/backend/backend/src/services/knowledge/knowledge.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/knowledge.service.js +0 -409
- package/dist/backend/backend/src/services/knowledge/knowledge.service.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts +0 -331
- package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js +0 -830
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js.map +0 -1
- package/dist/backend/backend/src/services/knowledge/wiki-compiler.service.d.ts +0 -115
- package/dist/backend/backend/src/services/knowledge/wiki-compiler.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/knowledge/wiki-compiler.service.js +0 -215
- package/dist/backend/backend/src/services/knowledge/wiki-compiler.service.js.map +0 -1
- package/dist/backend/backend/src/services/memory/embedding-provider.d.ts +0 -78
- package/dist/backend/backend/src/services/memory/embedding-provider.d.ts.map +0 -1
- package/dist/backend/backend/src/services/memory/embedding-provider.js +0 -185
- package/dist/backend/backend/src/services/memory/embedding-provider.js.map +0 -1
- package/dist/backend/backend/src/services/memory/vector-store.service.d.ts +0 -331
- package/dist/backend/backend/src/services/memory/vector-store.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/memory/vector-store.service.js +0 -825
- package/dist/backend/backend/src/services/memory/vector-store.service.js.map +0 -1
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts +0 -171
- package/dist/backend/backend/src/services/project/task-tracking.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/project/task-tracking.service.js +0 -725
- package/dist/backend/backend/src/services/project/task-tracking.service.js.map +0 -1
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts +0 -118
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js +0 -326
- package/dist/backend/backend/src/services/v3/project-task-watcher.service.js.map +0 -1
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts +0 -74
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js +0 -154
- package/dist/backend/backend/src/services/wiki/wiki-chat-subscriber.service.js.map +0 -1
- package/dist/backend/backend/src/services/workflow/unified-scheduler.service.d.ts +0 -234
- package/dist/backend/backend/src/services/workflow/unified-scheduler.service.d.ts.map +0 -1
- package/dist/backend/backend/src/services/workflow/unified-scheduler.service.js +0 -636
- package/dist/backend/backend/src/services/workflow/unified-scheduler.service.js.map +0 -1
- package/dist/backend/backend/src/services/workflow/unified-scheduler.types.d.ts +0 -156
- package/dist/backend/backend/src/services/workflow/unified-scheduler.types.d.ts.map +0 -1
- package/dist/backend/backend/src/services/workflow/unified-scheduler.types.js +0 -81
- package/dist/backend/backend/src/services/workflow/unified-scheduler.types.js.map +0 -1
- package/dist/backend/backend/src/types/auto-assign.types.d.ts +0 -271
- package/dist/backend/backend/src/types/auto-assign.types.d.ts.map +0 -1
- package/dist/backend/backend/src/types/auto-assign.types.js +0 -136
- package/dist/backend/backend/src/types/auto-assign.types.js.map +0 -1
- package/dist/backend/backend/src/types/knowledge.types.d.ts +0 -195
- package/dist/backend/backend/src/types/knowledge.types.d.ts.map +0 -1
- package/dist/backend/backend/src/types/knowledge.types.js +0 -38
- package/dist/backend/backend/src/types/knowledge.types.js.map +0 -1
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts +0 -111
- package/dist/backend/backend/src/utils/esm-require.utils.d.ts.map +0 -1
- package/dist/backend/backend/src/utils/esm-require.utils.js +0 -124
- package/dist/backend/backend/src/utils/esm-require.utils.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/embedding-provider.d.ts +0 -78
- package/dist/cli/backend/src/services/knowledge/embedding-provider.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/embedding-provider.js +0 -179
- package/dist/cli/backend/src/services/knowledge/embedding-provider.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts +0 -118
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js +0 -241
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-query-sanitizer.d.ts +0 -102
- package/dist/cli/backend/src/services/knowledge/fts5-query-sanitizer.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-query-sanitizer.js +0 -118
- package/dist/cli/backend/src/services/knowledge/fts5-query-sanitizer.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts +0 -56
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js +0 -91
- package/dist/cli/backend/src/services/knowledge/fts5-search-strategy.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts +0 -241
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +0 -590
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/knowledge.service.d.ts +0 -153
- package/dist/cli/backend/src/services/knowledge/knowledge.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/knowledge.service.js +0 -409
- package/dist/cli/backend/src/services/knowledge/knowledge.service.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts +0 -159
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js +0 -304
- package/dist/cli/backend/src/services/knowledge/learnings-index.service.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts +0 -331
- package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js +0 -830
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js.map +0 -1
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts +0 -115
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js +0 -215
- package/dist/cli/backend/src/services/knowledge/wiki-compiler.service.js.map +0 -1
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts +0 -78
- package/dist/cli/backend/src/services/memory/embedding-provider.d.ts.map +0 -1
- package/dist/cli/backend/src/services/memory/embedding-provider.js +0 -179
- package/dist/cli/backend/src/services/memory/embedding-provider.js.map +0 -1
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts +0 -331
- package/dist/cli/backend/src/services/memory/vector-store.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/memory/vector-store.service.js +0 -814
- package/dist/cli/backend/src/services/memory/vector-store.service.js.map +0 -1
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts +0 -171
- package/dist/cli/backend/src/services/project/task-tracking.service.d.ts.map +0 -1
- package/dist/cli/backend/src/services/project/task-tracking.service.js +0 -725
- package/dist/cli/backend/src/services/project/task-tracking.service.js.map +0 -1
- package/dist/cli/backend/src/types/auto-assign.types.d.ts +0 -271
- package/dist/cli/backend/src/types/auto-assign.types.d.ts.map +0 -1
- package/dist/cli/backend/src/types/auto-assign.types.js +0 -136
- package/dist/cli/backend/src/types/auto-assign.types.js.map +0 -1
- package/dist/cli/backend/src/types/knowledge.types.d.ts +0 -195
- package/dist/cli/backend/src/types/knowledge.types.d.ts.map +0 -1
- package/dist/cli/backend/src/types/knowledge.types.js +0 -38
- package/dist/cli/backend/src/types/knowledge.types.js.map +0 -1
|
@@ -0,0 +1,821 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Slack Team Channel Service
|
|
3
|
+
*
|
|
4
|
+
* Makes a Crewly team a first-class Slack participant: one Slack channel per
|
|
5
|
+
* team, backed by one chat-v2 huddle whose roster is the team's members.
|
|
6
|
+
*
|
|
7
|
+
* Slack #team-alpha ──inbound──▶ chat-v2 huddle ──dispatcher──▶ every member
|
|
8
|
+
* ▲ (@'d must reply)
|
|
9
|
+
* └──outbound, posted as the agent (username/icon)── reply-channel
|
|
10
|
+
*
|
|
11
|
+
* Responsibilities:
|
|
12
|
+
* - Mapping store (`~/.crewly/slack-team-channels.json`): create/link/unlink.
|
|
13
|
+
* - Lifecycle sync via `StorageService.onStorageEvent`: newly created team →
|
|
14
|
+
* channel + huddle + welcome (existing teams are opted in from Settings);
|
|
15
|
+
* member change → roster; archive/delete → archive both.
|
|
16
|
+
* - Inbound routing for mapped channels (called by the orchestrator bridge):
|
|
17
|
+
* persist into the huddle with Slack thread correlation, resolve `@name`
|
|
18
|
+
* mentions, dispatch, answer typos with "did you mean".
|
|
19
|
+
* - Outbound mirror: agent messages recorded in a mapped huddle are posted to
|
|
20
|
+
* the Slack thread under the agent's own name and icon.
|
|
21
|
+
*
|
|
22
|
+
* Nothing here is orchestrator-specific — the orchestrator keeps handling DMs
|
|
23
|
+
* and unmapped channels exactly as before.
|
|
24
|
+
*
|
|
25
|
+
* @module services/slack/slack-team-channel.service
|
|
26
|
+
*/
|
|
27
|
+
import * as path from 'path';
|
|
28
|
+
import { promises as fs } from 'fs';
|
|
29
|
+
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
30
|
+
import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
|
|
31
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
32
|
+
import { SLACK_TEAM_CHANNEL_CONSTANTS } from '../../constants.js';
|
|
33
|
+
import { resolveSlackMentions } from './slack-mention-resolver.js';
|
|
34
|
+
const EMPTY_STORE = {
|
|
35
|
+
version: 1,
|
|
36
|
+
autoCreate: true,
|
|
37
|
+
channelPrefix: '',
|
|
38
|
+
mappings: [],
|
|
39
|
+
};
|
|
40
|
+
/**
|
|
41
|
+
* Turn a team name into a Slack-legal channel name: lower-case, letters of
|
|
42
|
+
* any script, digits, `-` and `_`; whitespace and punctuation collapse to
|
|
43
|
+
* `-`; at most 80 characters.
|
|
44
|
+
*
|
|
45
|
+
* @param teamName - The team display name
|
|
46
|
+
* @param prefix - Optional prefix (e.g. `crew-`), sanitised the same way
|
|
47
|
+
* @returns The channel name without `#`, never empty
|
|
48
|
+
*
|
|
49
|
+
* @example
|
|
50
|
+
* slackChannelNameFor('Growth Team!', 'crew-') // 'crew-growth-team'
|
|
51
|
+
*/
|
|
52
|
+
export function slackChannelNameFor(teamName, prefix = '') {
|
|
53
|
+
const clean = (s) => s
|
|
54
|
+
.toLowerCase()
|
|
55
|
+
.replace(/[^\p{L}\p{N}_-]+/gu, '-')
|
|
56
|
+
.replace(/-{2,}/g, '-')
|
|
57
|
+
.replace(/^-+|-+$/g, '');
|
|
58
|
+
const body = clean(teamName) || 'team';
|
|
59
|
+
const p = clean(prefix);
|
|
60
|
+
const joined = p ? `${p}-${body}` : body;
|
|
61
|
+
return joined.slice(0, SLACK_TEAM_CHANNEL_CONSTANTS.MAX_CHANNEL_NAME_LENGTH).replace(/-+$/g, '') || 'team';
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* The agents that take part in a team channel: every member except the
|
|
65
|
+
* orchestrator role (the orc is not a huddle participant — the point of
|
|
66
|
+
* team channels is talking to the team without it).
|
|
67
|
+
*
|
|
68
|
+
* @param team - The team
|
|
69
|
+
* @returns Members with a session name, orchestrator excluded
|
|
70
|
+
*/
|
|
71
|
+
export function teamChannelMembers(team) {
|
|
72
|
+
return (team.members ?? []).filter((m) => !!m.sessionName && m.role !== 'orchestrator');
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Pick the Slack identity an agent posts under.
|
|
76
|
+
*
|
|
77
|
+
* `username` is the member's display name. The icon is the member avatar
|
|
78
|
+
* when it is a Slack emoji name (`:rocket:`) or a URL; otherwise a per-role
|
|
79
|
+
* emoji, then the default robot.
|
|
80
|
+
*
|
|
81
|
+
* @param member - The team member, or undefined when the session is unknown
|
|
82
|
+
* @param sessionName - Fallback display name
|
|
83
|
+
* @returns The identity fields for SlackOutgoingMessage
|
|
84
|
+
*/
|
|
85
|
+
export function slackIdentityFor(member, sessionName) {
|
|
86
|
+
const username = member?.name?.trim() || sessionName;
|
|
87
|
+
const avatar = member?.avatar?.trim() ?? '';
|
|
88
|
+
if (/^:[a-z0-9_+-]+:$/i.test(avatar)) {
|
|
89
|
+
return { username, iconEmoji: avatar };
|
|
90
|
+
}
|
|
91
|
+
if (/^https?:\/\//i.test(avatar)) {
|
|
92
|
+
return { username, iconUrl: avatar };
|
|
93
|
+
}
|
|
94
|
+
const roleEmoji = member?.role
|
|
95
|
+
? SLACK_TEAM_CHANNEL_CONSTANTS.ROLE_ICON_EMOJI[member.role]
|
|
96
|
+
: undefined;
|
|
97
|
+
return { username, iconEmoji: roleEmoji ?? SLACK_TEAM_CHANNEL_CONSTANTS.DEFAULT_ICON_EMOJI };
|
|
98
|
+
}
|
|
99
|
+
/**
|
|
100
|
+
* Service — see module docs.
|
|
101
|
+
*/
|
|
102
|
+
export class SlackTeamChannelService {
|
|
103
|
+
logger;
|
|
104
|
+
deps;
|
|
105
|
+
storePath;
|
|
106
|
+
store = null;
|
|
107
|
+
loading = null;
|
|
108
|
+
unsubscribeStorage = null;
|
|
109
|
+
unsubscribeIdentity = null;
|
|
110
|
+
onChatMessage = (dto) => {
|
|
111
|
+
void this.mirrorOutbound(dto);
|
|
112
|
+
};
|
|
113
|
+
started = false;
|
|
114
|
+
/** Per-team serialisation so two team-saved events cannot create two channels. */
|
|
115
|
+
inflight = new Map();
|
|
116
|
+
constructor(deps) {
|
|
117
|
+
this.deps = deps;
|
|
118
|
+
this.storePath =
|
|
119
|
+
deps.storePath ?? path.join(getCrewlyHomePath(), SLACK_TEAM_CHANNEL_CONSTANTS.STORE_FILENAME);
|
|
120
|
+
this.logger = LoggerService.getInstance().createComponentLogger('SlackTeamChannels');
|
|
121
|
+
}
|
|
122
|
+
// -------------------------------------------------------------------------
|
|
123
|
+
// Lifecycle
|
|
124
|
+
// -------------------------------------------------------------------------
|
|
125
|
+
/**
|
|
126
|
+
* Subscribe to team lifecycle events and chat-v2 agent messages. Idempotent.
|
|
127
|
+
* Safe to call before Slack is connected — every Slack call is guarded by
|
|
128
|
+
* `isConnected()` at the time it runs.
|
|
129
|
+
*/
|
|
130
|
+
async start() {
|
|
131
|
+
if (this.started)
|
|
132
|
+
return;
|
|
133
|
+
await this.load();
|
|
134
|
+
this.unsubscribeStorage = this.deps.storage.onStorageEvent((event) => this.handleStorageEvent(event));
|
|
135
|
+
this.deps.chat.on('chat_message', this.onChatMessage);
|
|
136
|
+
// A freshly installed agent bot gets invited into every channel of a
|
|
137
|
+
// team it belongs to, so it can post there under its own identity.
|
|
138
|
+
this.unsubscribeIdentity =
|
|
139
|
+
this.deps.identities?.onInstalled((record) => {
|
|
140
|
+
void this.inviteInstalledEverywhere(record.agentSession);
|
|
141
|
+
}) ?? null;
|
|
142
|
+
this.started = true;
|
|
143
|
+
this.logger.info('Slack team channels started', {
|
|
144
|
+
mappings: this.store?.mappings.length ?? 0,
|
|
145
|
+
autoCreate: this.store?.autoCreate ?? true,
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
/** Undo {@link start}. */
|
|
149
|
+
stop() {
|
|
150
|
+
if (!this.started)
|
|
151
|
+
return;
|
|
152
|
+
this.unsubscribeStorage?.();
|
|
153
|
+
this.unsubscribeStorage = null;
|
|
154
|
+
this.unsubscribeIdentity?.();
|
|
155
|
+
this.unsubscribeIdentity = null;
|
|
156
|
+
this.deps.chat.off('chat_message', this.onChatMessage);
|
|
157
|
+
this.started = false;
|
|
158
|
+
}
|
|
159
|
+
// -------------------------------------------------------------------------
|
|
160
|
+
// Store
|
|
161
|
+
// -------------------------------------------------------------------------
|
|
162
|
+
/** Read the store from disk once; later calls return the cached copy. */
|
|
163
|
+
async load() {
|
|
164
|
+
if (this.store)
|
|
165
|
+
return this.store;
|
|
166
|
+
if (!this.loading) {
|
|
167
|
+
this.loading = safeReadJson(this.storePath, EMPTY_STORE).then((raw) => {
|
|
168
|
+
const store = {
|
|
169
|
+
version: 1,
|
|
170
|
+
autoCreate: raw?.autoCreate !== false,
|
|
171
|
+
channelPrefix: typeof raw?.channelPrefix === 'string' ? raw.channelPrefix : '',
|
|
172
|
+
mappings: Array.isArray(raw?.mappings) ? raw.mappings.filter(isMapping) : [],
|
|
173
|
+
};
|
|
174
|
+
this.store = store;
|
|
175
|
+
return store;
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
return this.loading;
|
|
179
|
+
}
|
|
180
|
+
async save() {
|
|
181
|
+
const store = await this.load();
|
|
182
|
+
await fs.mkdir(path.dirname(this.storePath), { recursive: true });
|
|
183
|
+
await atomicWriteJson(this.storePath, store);
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Current settings (auto-create + prefix).
|
|
187
|
+
*
|
|
188
|
+
* @returns The settings half of the store
|
|
189
|
+
*/
|
|
190
|
+
async getSettings() {
|
|
191
|
+
const s = await this.load();
|
|
192
|
+
return { autoCreate: s.autoCreate, channelPrefix: s.channelPrefix };
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Update settings. Only the keys present in `patch` change.
|
|
196
|
+
*
|
|
197
|
+
* @param patch - Partial settings
|
|
198
|
+
* @returns The settings after the update
|
|
199
|
+
*/
|
|
200
|
+
async updateSettings(patch) {
|
|
201
|
+
const s = await this.load();
|
|
202
|
+
if (typeof patch.autoCreate === 'boolean')
|
|
203
|
+
s.autoCreate = patch.autoCreate;
|
|
204
|
+
if (typeof patch.channelPrefix === 'string')
|
|
205
|
+
s.channelPrefix = patch.channelPrefix.trim();
|
|
206
|
+
await this.save();
|
|
207
|
+
return this.getSettings();
|
|
208
|
+
}
|
|
209
|
+
/**
|
|
210
|
+
* All mappings, in creation order.
|
|
211
|
+
*
|
|
212
|
+
* @returns Copies of every mapping
|
|
213
|
+
*/
|
|
214
|
+
async listMappings() {
|
|
215
|
+
const s = await this.load();
|
|
216
|
+
return s.mappings.map((m) => ({ ...m }));
|
|
217
|
+
}
|
|
218
|
+
/**
|
|
219
|
+
* Mapping for a team, if any. Synchronous read of the cached store — the
|
|
220
|
+
* bridge calls this on every inbound Slack message.
|
|
221
|
+
*
|
|
222
|
+
* @param teamId - Crewly team id
|
|
223
|
+
* @returns The mapping, or null
|
|
224
|
+
*/
|
|
225
|
+
findByTeamId(teamId) {
|
|
226
|
+
return this.store?.mappings.find((m) => m.teamId === teamId) ?? null;
|
|
227
|
+
}
|
|
228
|
+
/**
|
|
229
|
+
* Mapping for a Slack channel, if any (sync, cached store).
|
|
230
|
+
*
|
|
231
|
+
* @param slackChannelId - Slack channel id
|
|
232
|
+
* @returns The mapping, or null
|
|
233
|
+
*/
|
|
234
|
+
findBySlackChannelId(slackChannelId) {
|
|
235
|
+
return this.store?.mappings.find((m) => m.slackChannelId === slackChannelId) ?? null;
|
|
236
|
+
}
|
|
237
|
+
/**
|
|
238
|
+
* Mapping for a chat-v2 huddle, if any (sync, cached store).
|
|
239
|
+
*
|
|
240
|
+
* @param chatChannelId - chat-v2 channel id
|
|
241
|
+
* @returns The mapping, or null
|
|
242
|
+
*/
|
|
243
|
+
findByChatChannelId(chatChannelId) {
|
|
244
|
+
return this.store?.mappings.find((m) => m.chatChannelId === chatChannelId) ?? null;
|
|
245
|
+
}
|
|
246
|
+
/**
|
|
247
|
+
* Every non-archived team with its mapping (or null). One call for the
|
|
248
|
+
* Settings UI.
|
|
249
|
+
*
|
|
250
|
+
* @returns Teams in storage order
|
|
251
|
+
*/
|
|
252
|
+
async listTeamsWithMappings() {
|
|
253
|
+
await this.load();
|
|
254
|
+
const teams = await this.deps.storage.getTeams();
|
|
255
|
+
return teams
|
|
256
|
+
.filter((t) => !t.archived)
|
|
257
|
+
.map((t) => ({
|
|
258
|
+
teamId: t.id,
|
|
259
|
+
teamName: t.name,
|
|
260
|
+
memberCount: teamChannelMembers(t).length,
|
|
261
|
+
mapping: this.findByTeamId(t.id),
|
|
262
|
+
}));
|
|
263
|
+
}
|
|
264
|
+
/**
|
|
265
|
+
* Look a team up by id.
|
|
266
|
+
*
|
|
267
|
+
* @param teamId - Crewly team id
|
|
268
|
+
* @returns The team, or null
|
|
269
|
+
*/
|
|
270
|
+
async getTeam(teamId) {
|
|
271
|
+
const teams = await this.deps.storage.getTeams();
|
|
272
|
+
return teams.find((t) => t.id === teamId) ?? null;
|
|
273
|
+
}
|
|
274
|
+
// -------------------------------------------------------------------------
|
|
275
|
+
// Create / link / unlink
|
|
276
|
+
// -------------------------------------------------------------------------
|
|
277
|
+
/**
|
|
278
|
+
* Make sure `team` has a Slack channel and a huddle. Creates the Slack
|
|
279
|
+
* channel (or reuses one with the same name) unless `slackChannelId`
|
|
280
|
+
* names an existing channel to link. Idempotent: an existing mapping is
|
|
281
|
+
* returned after a roster sync.
|
|
282
|
+
*
|
|
283
|
+
* @param team - The team
|
|
284
|
+
* @param options - `slackChannelId` to link an existing channel instead of creating
|
|
285
|
+
* @returns The mapping
|
|
286
|
+
* @throws Error when Slack is not connected or the channel cannot be created/found
|
|
287
|
+
*/
|
|
288
|
+
async ensureTeamChannel(team, options = {}) {
|
|
289
|
+
return this.serialised(team.id, async () => {
|
|
290
|
+
await this.load();
|
|
291
|
+
const existing = this.findByTeamId(team.id);
|
|
292
|
+
if (existing) {
|
|
293
|
+
await this.syncTeamMembers(team, existing);
|
|
294
|
+
return existing;
|
|
295
|
+
}
|
|
296
|
+
if (!this.deps.slack.isConnected()) {
|
|
297
|
+
throw new Error('Slack is not connected');
|
|
298
|
+
}
|
|
299
|
+
let channel;
|
|
300
|
+
let autoCreated;
|
|
301
|
+
if (options.slackChannelId) {
|
|
302
|
+
const info = await this.deps.slack.getChannelInfo(options.slackChannelId);
|
|
303
|
+
if (!info)
|
|
304
|
+
throw new Error(`Slack channel not found: ${options.slackChannelId}`);
|
|
305
|
+
if (info.isArchived)
|
|
306
|
+
throw new Error(`Slack channel is archived: ${info.name}`);
|
|
307
|
+
await this.deps.slack.joinChannel(info.id).catch((err) => {
|
|
308
|
+
// Private channels cannot be joined; the bot must be invited by a human.
|
|
309
|
+
this.logger.warn('Could not join linked channel — invite the bot manually', {
|
|
310
|
+
channel: info.name,
|
|
311
|
+
error: err instanceof Error ? err.message : String(err),
|
|
312
|
+
});
|
|
313
|
+
});
|
|
314
|
+
channel = info;
|
|
315
|
+
autoCreated = false;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
const store = await this.load();
|
|
319
|
+
channel = await this.deps.slack.createChannel(slackChannelNameFor(team.name, store.channelPrefix));
|
|
320
|
+
autoCreated = true;
|
|
321
|
+
const purpose = team.description?.trim() || `Crewly team "${team.name}"`;
|
|
322
|
+
await this.deps.slack.setChannelPurpose(channel.id, purpose).catch((err) => {
|
|
323
|
+
this.logger.debug('setPurpose failed (non-critical)', {
|
|
324
|
+
error: err instanceof Error ? err.message : String(err),
|
|
325
|
+
});
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
const members = teamChannelMembers(team);
|
|
329
|
+
const huddle = this.deps.chat.createHuddle({
|
|
330
|
+
name: `#${channel.name}`,
|
|
331
|
+
purpose: `Slack team channel for ${team.name}`,
|
|
332
|
+
// A brand-new team may have no members yet; keep the huddle valid
|
|
333
|
+
// with a placeholder that the first roster sync replaces.
|
|
334
|
+
memberSessions: members.length > 0 ? members.map((m) => m.sessionName) : [`team:${team.id}`],
|
|
335
|
+
principal: { userId: 'system', source: 'oss' },
|
|
336
|
+
});
|
|
337
|
+
const mapping = {
|
|
338
|
+
teamId: team.id,
|
|
339
|
+
slackChannelId: channel.id,
|
|
340
|
+
slackChannelName: channel.name,
|
|
341
|
+
chatChannelId: huddle.id,
|
|
342
|
+
createdAt: (this.deps.now?.() ?? new Date()).toISOString(),
|
|
343
|
+
autoCreated,
|
|
344
|
+
};
|
|
345
|
+
const store = await this.load();
|
|
346
|
+
store.mappings.push(mapping);
|
|
347
|
+
await this.save();
|
|
348
|
+
this.logger.info('Team channel ready', {
|
|
349
|
+
teamId: team.id,
|
|
350
|
+
teamName: team.name,
|
|
351
|
+
slackChannel: `#${channel.name}`,
|
|
352
|
+
huddle: huddle.id,
|
|
353
|
+
members: members.length,
|
|
354
|
+
autoCreated,
|
|
355
|
+
});
|
|
356
|
+
await this.postWelcome(mapping, team, members);
|
|
357
|
+
await this.ensureIdentities(team, mapping);
|
|
358
|
+
return mapping;
|
|
359
|
+
});
|
|
360
|
+
}
|
|
361
|
+
/**
|
|
362
|
+
* Remove a team's mapping. Optionally archives the Slack channel; the
|
|
363
|
+
* huddle is always archived (it is Crewly's own object).
|
|
364
|
+
*
|
|
365
|
+
* @param teamId - Crewly team id
|
|
366
|
+
* @param options - `archiveSlackChannel` (default false — leave the humans' channel alone)
|
|
367
|
+
* @returns True when a mapping existed
|
|
368
|
+
*/
|
|
369
|
+
async unlinkTeam(teamId, options = {}) {
|
|
370
|
+
return this.serialised(teamId, async () => {
|
|
371
|
+
const store = await this.load();
|
|
372
|
+
const idx = store.mappings.findIndex((m) => m.teamId === teamId);
|
|
373
|
+
if (idx < 0)
|
|
374
|
+
return false;
|
|
375
|
+
const [mapping] = store.mappings.splice(idx, 1);
|
|
376
|
+
await this.save();
|
|
377
|
+
this.deps.chat.archiveChannelForBridge(mapping.chatChannelId);
|
|
378
|
+
if (options.archiveSlackChannel && this.deps.slack.isConnected()) {
|
|
379
|
+
await this.deps.slack.archiveChannel(mapping.slackChannelId).catch((err) => {
|
|
380
|
+
this.logger.warn('Could not archive Slack channel', {
|
|
381
|
+
channel: mapping.slackChannelName,
|
|
382
|
+
error: err instanceof Error ? err.message : String(err),
|
|
383
|
+
});
|
|
384
|
+
});
|
|
385
|
+
}
|
|
386
|
+
this.logger.info('Team channel unlinked', {
|
|
387
|
+
teamId,
|
|
388
|
+
slackChannel: `#${mapping.slackChannelName}`,
|
|
389
|
+
archivedSlack: !!options.archiveSlackChannel,
|
|
390
|
+
});
|
|
391
|
+
return true;
|
|
392
|
+
});
|
|
393
|
+
}
|
|
394
|
+
/**
|
|
395
|
+
* Reconcile the huddle roster with the team's current members.
|
|
396
|
+
*
|
|
397
|
+
* @param team - The team
|
|
398
|
+
* @param mapping - Its mapping (looked up when omitted)
|
|
399
|
+
* @returns The roster diff, or null when the team is unmapped
|
|
400
|
+
*/
|
|
401
|
+
async syncTeamMembers(team, mapping) {
|
|
402
|
+
await this.load();
|
|
403
|
+
const m = mapping ?? this.findByTeamId(team.id);
|
|
404
|
+
if (!m)
|
|
405
|
+
return null;
|
|
406
|
+
const sessions = teamChannelMembers(team).map((x) => x.sessionName);
|
|
407
|
+
if (sessions.length === 0)
|
|
408
|
+
return { added: [], removed: [] };
|
|
409
|
+
const diff = this.deps.chat.setHuddleMembers(m.chatChannelId, sessions);
|
|
410
|
+
if (diff.added.length || diff.removed.length) {
|
|
411
|
+
this.logger.info('Team channel roster synced', { teamId: team.id, ...diff });
|
|
412
|
+
}
|
|
413
|
+
await this.ensureIdentities(team, m);
|
|
414
|
+
return diff;
|
|
415
|
+
}
|
|
416
|
+
// -------------------------------------------------------------------------
|
|
417
|
+
// Team lifecycle
|
|
418
|
+
// -------------------------------------------------------------------------
|
|
419
|
+
/**
|
|
420
|
+
* React to a storage event. Public so the bridge/tests can drive it
|
|
421
|
+
* directly; `start()` subscribes it.
|
|
422
|
+
*
|
|
423
|
+
* @param event - The storage event
|
|
424
|
+
*/
|
|
425
|
+
async handleStorageEvent(event) {
|
|
426
|
+
try {
|
|
427
|
+
if (event.kind === 'team-deleted') {
|
|
428
|
+
await this.unlinkTeam(event.teamId, { archiveSlackChannel: true });
|
|
429
|
+
return;
|
|
430
|
+
}
|
|
431
|
+
const team = event.team;
|
|
432
|
+
await this.load();
|
|
433
|
+
const mapping = this.findByTeamId(team.id);
|
|
434
|
+
if (team.archived) {
|
|
435
|
+
if (mapping)
|
|
436
|
+
await this.unlinkTeam(team.id, { archiveSlackChannel: true });
|
|
437
|
+
return;
|
|
438
|
+
}
|
|
439
|
+
if (mapping) {
|
|
440
|
+
await this.syncTeamMembers(team, mapping);
|
|
441
|
+
return;
|
|
442
|
+
}
|
|
443
|
+
// Auto-create only for teams that were just created. Every status
|
|
444
|
+
// write (activity monitor, registration) also lands here as a
|
|
445
|
+
// team-saved event; turning each of those into a Slack channel would
|
|
446
|
+
// carpet the workspace with channels for teams that pre-date this
|
|
447
|
+
// feature. Existing teams get a channel from Settings → Team Channels.
|
|
448
|
+
if (!event.created)
|
|
449
|
+
return;
|
|
450
|
+
const settings = await this.getSettings();
|
|
451
|
+
if (!settings.autoCreate || !this.deps.slack.isConnected())
|
|
452
|
+
return;
|
|
453
|
+
await this.ensureTeamChannel(team);
|
|
454
|
+
}
|
|
455
|
+
catch (err) {
|
|
456
|
+
this.logger.warn('Team lifecycle sync failed', {
|
|
457
|
+
kind: event.kind,
|
|
458
|
+
error: err instanceof Error ? err.message : String(err),
|
|
459
|
+
});
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
// -------------------------------------------------------------------------
|
|
463
|
+
// Inbound (Slack → huddle)
|
|
464
|
+
// -------------------------------------------------------------------------
|
|
465
|
+
/**
|
|
466
|
+
* Route an inbound Slack message that arrived in a mapped channel.
|
|
467
|
+
*
|
|
468
|
+
* 1. Persist into the huddle: a top-level Slack message becomes a chat-v2
|
|
469
|
+
* root carrying `slackThreadTs = ts`; a Slack thread reply is filed
|
|
470
|
+
* under the root that carries `slackThreadTs = thread_ts` (or becomes
|
|
471
|
+
* a root itself when that root predates the mapping).
|
|
472
|
+
* 2. Resolve `@name` tokens against the team's members → `mentions`.
|
|
473
|
+
* 3. Dispatch through the chat-v2 dispatcher (`huddle-broadcast`) with
|
|
474
|
+
* the thread id and the `reply-channel` hint.
|
|
475
|
+
* 4. Answer unknown `@names` in-thread with suggestions.
|
|
476
|
+
*
|
|
477
|
+
* @param message - The inbound Slack message
|
|
478
|
+
* @returns The routing result, or null when the channel is not mapped
|
|
479
|
+
*/
|
|
480
|
+
async routeInbound(message) {
|
|
481
|
+
await this.load();
|
|
482
|
+
const mapping = this.findBySlackChannelId(message.channelId);
|
|
483
|
+
if (!mapping)
|
|
484
|
+
return null;
|
|
485
|
+
const channel = this.deps.chat.getChannelForBridge(mapping.chatChannelId);
|
|
486
|
+
if (!channel || channel.archivedAt) {
|
|
487
|
+
this.logger.warn('Mapped huddle missing or archived — dropping mapping', {
|
|
488
|
+
teamId: mapping.teamId,
|
|
489
|
+
huddle: mapping.chatChannelId,
|
|
490
|
+
});
|
|
491
|
+
await this.unlinkTeam(mapping.teamId);
|
|
492
|
+
return null;
|
|
493
|
+
}
|
|
494
|
+
const team = (await this.deps.storage.getTeams()).find((t) => t.id === mapping.teamId);
|
|
495
|
+
const members = team ? teamChannelMembers(team) : [];
|
|
496
|
+
if (this.deps.identities)
|
|
497
|
+
await this.deps.identities.load();
|
|
498
|
+
const candidates = members.map((m) => ({
|
|
499
|
+
name: m.name,
|
|
500
|
+
sessionName: m.sessionName,
|
|
501
|
+
botUserId: this.deps.identities?.get(m.sessionName)?.botUserId,
|
|
502
|
+
}));
|
|
503
|
+
const resolved = resolveSlackMentions(message.text ?? '', candidates);
|
|
504
|
+
// Thread correlation.
|
|
505
|
+
const slackThreadTs = message.threadTs || message.ts;
|
|
506
|
+
let threadId;
|
|
507
|
+
if (message.threadTs) {
|
|
508
|
+
const root = this.deps.chat.findSlackThreadRoot(mapping.chatChannelId, message.threadTs);
|
|
509
|
+
threadId = root?.id;
|
|
510
|
+
}
|
|
511
|
+
const senderId = message.user?.name || message.userId || 'slack-user';
|
|
512
|
+
const { message: persisted } = this.deps.chat.recordTurn({
|
|
513
|
+
channelId: mapping.chatChannelId,
|
|
514
|
+
senderType: 'user',
|
|
515
|
+
senderId,
|
|
516
|
+
content: message.text ?? '',
|
|
517
|
+
threadId,
|
|
518
|
+
mentions: resolved.mentions,
|
|
519
|
+
metadata: {
|
|
520
|
+
source: 'slack',
|
|
521
|
+
slackChannelId: message.channelId,
|
|
522
|
+
slackThreadTs,
|
|
523
|
+
slackTs: message.ts,
|
|
524
|
+
slackUserId: message.userId,
|
|
525
|
+
},
|
|
526
|
+
});
|
|
527
|
+
await this.deps.slack
|
|
528
|
+
.addReaction(message.channelId, message.ts, SLACK_TEAM_CHANNEL_CONSTANTS.INBOUND_REACTION)
|
|
529
|
+
.catch(() => undefined);
|
|
530
|
+
const dispatcher = this.deps.getDispatcher();
|
|
531
|
+
let dispatch = null;
|
|
532
|
+
if (dispatcher) {
|
|
533
|
+
dispatch = await dispatcher.dispatchMessage(channel, persisted, {
|
|
534
|
+
threadId: threadId ?? persisted.id,
|
|
535
|
+
replyVia: 'reply-channel',
|
|
536
|
+
});
|
|
537
|
+
}
|
|
538
|
+
else {
|
|
539
|
+
this.logger.warn('No chat dispatcher wired — message persisted but not delivered', {
|
|
540
|
+
huddle: mapping.chatChannelId,
|
|
541
|
+
});
|
|
542
|
+
}
|
|
543
|
+
if (resolved.unknown.length > 0) {
|
|
544
|
+
await this.postUnknownMentionHint(message, resolved.unknown, candidates);
|
|
545
|
+
}
|
|
546
|
+
this.logger.info('Slack team message routed', {
|
|
547
|
+
teamId: mapping.teamId,
|
|
548
|
+
slackChannel: `#${mapping.slackChannelName}`,
|
|
549
|
+
mentions: resolved.mentions,
|
|
550
|
+
unknown: resolved.unknown.map((u) => u.token),
|
|
551
|
+
strategy: dispatch?.strategy ?? 'none',
|
|
552
|
+
threaded: !!threadId,
|
|
553
|
+
});
|
|
554
|
+
return { mapping, message: persisted, mentions: resolved.mentions, dispatch };
|
|
555
|
+
}
|
|
556
|
+
// -------------------------------------------------------------------------
|
|
557
|
+
// Outbound (huddle → Slack)
|
|
558
|
+
// -------------------------------------------------------------------------
|
|
559
|
+
/**
|
|
560
|
+
* Post an agent's huddle message to the mapped Slack channel under the
|
|
561
|
+
* agent's identity. Called for every chat-v2 `chat_message`; ignores
|
|
562
|
+
* anything that is not an agent message in a mapped huddle.
|
|
563
|
+
*
|
|
564
|
+
* @param dto - The chat-v2 message
|
|
565
|
+
* @returns True when a Slack post was attempted
|
|
566
|
+
*/
|
|
567
|
+
async mirrorOutbound(dto) {
|
|
568
|
+
try {
|
|
569
|
+
if (dto.senderType !== 'agent')
|
|
570
|
+
return false;
|
|
571
|
+
if (dto.metadata?.source === 'slack')
|
|
572
|
+
return false;
|
|
573
|
+
await this.load();
|
|
574
|
+
const mapping = this.findByChatChannelId(dto.channelId);
|
|
575
|
+
if (!mapping)
|
|
576
|
+
return false;
|
|
577
|
+
if (!this.deps.slack.isConnected())
|
|
578
|
+
return false;
|
|
579
|
+
const threadTs = this.resolveOutboundThreadTs(mapping, dto);
|
|
580
|
+
const team = (await this.deps.storage.getTeams()).find((t) => t.id === mapping.teamId);
|
|
581
|
+
const member = team?.members.find((m) => m.sessionName === dto.senderId);
|
|
582
|
+
// A real bot user (Cloud-provisioned identity) beats the cosmetic
|
|
583
|
+
// username/icon override.
|
|
584
|
+
const installed = this.deps.identities?.getInstalled(dto.senderId) ?? null;
|
|
585
|
+
const identity = installed ? { botToken: installed.botToken } : slackIdentityFor(member, dto.senderId);
|
|
586
|
+
await this.deps.slack.sendMessage({
|
|
587
|
+
channelId: mapping.slackChannelId,
|
|
588
|
+
text: dto.content,
|
|
589
|
+
threadTs,
|
|
590
|
+
skipChatV2Mirror: true,
|
|
591
|
+
...identity,
|
|
592
|
+
});
|
|
593
|
+
return true;
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
this.logger.warn('Outbound mirror to Slack failed', {
|
|
597
|
+
channelId: dto.channelId,
|
|
598
|
+
sender: dto.senderId,
|
|
599
|
+
error: err instanceof Error ? err.message : String(err),
|
|
600
|
+
});
|
|
601
|
+
return false;
|
|
602
|
+
}
|
|
603
|
+
}
|
|
604
|
+
/**
|
|
605
|
+
* Which Slack thread an agent reply belongs to: its chat-v2 thread root's
|
|
606
|
+
* `slackThreadTs`; failing that, the latest Slack-origin root in the
|
|
607
|
+
* huddle; failing that, the channel top level.
|
|
608
|
+
*/
|
|
609
|
+
resolveOutboundThreadTs(mapping, dto) {
|
|
610
|
+
if (dto.threadId) {
|
|
611
|
+
const root = this.deps.chat.getMessageForBridge(dto.threadId);
|
|
612
|
+
const ts = root?.metadata?.slackThreadTs;
|
|
613
|
+
if (typeof ts === 'string' && ts)
|
|
614
|
+
return ts;
|
|
615
|
+
}
|
|
616
|
+
const latest = this.deps.chat.findLatestSlackRoot(mapping.chatChannelId);
|
|
617
|
+
const ts = latest?.metadata?.slackThreadTs;
|
|
618
|
+
return typeof ts === 'string' && ts ? ts : undefined;
|
|
619
|
+
}
|
|
620
|
+
// -------------------------------------------------------------------------
|
|
621
|
+
// Agent identities (real bot users)
|
|
622
|
+
// -------------------------------------------------------------------------
|
|
623
|
+
/**
|
|
624
|
+
* Give every member of a mapped team a real Slack identity: ask Cloud for
|
|
625
|
+
* one where missing, announce the owner's install links in the team's
|
|
626
|
+
* channel (once per agent per channel), and invite installed bots into the
|
|
627
|
+
* channel (once). Silent no-op when identities are unavailable (no Cloud
|
|
628
|
+
* login) or the owner has not stored a config token yet.
|
|
629
|
+
*
|
|
630
|
+
* @param team - The team
|
|
631
|
+
* @param mapping - Its channel mapping
|
|
632
|
+
* @returns Counts, for logging and the REST surface
|
|
633
|
+
*/
|
|
634
|
+
async ensureIdentities(team, mapping) {
|
|
635
|
+
const identities = this.deps.identities;
|
|
636
|
+
const result = { provisioned: 0, announced: 0, invited: 0, skipped: null };
|
|
637
|
+
if (!identities || !identities.isAvailable()) {
|
|
638
|
+
result.skipped = 'identities unavailable';
|
|
639
|
+
return result;
|
|
640
|
+
}
|
|
641
|
+
await identities.load();
|
|
642
|
+
const pendingLinks = [];
|
|
643
|
+
for (const member of teamChannelMembers(team)) {
|
|
644
|
+
let record = identities.get(member.sessionName);
|
|
645
|
+
if (!record || record.status !== 'installed') {
|
|
646
|
+
try {
|
|
647
|
+
record = await identities.provision(member.sessionName, member.name, `${member.name} — ${member.role} on ${team.name} (Crewly)`);
|
|
648
|
+
result.provisioned += 1;
|
|
649
|
+
}
|
|
650
|
+
catch (err) {
|
|
651
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
652
|
+
// A missing/invalid config token stops the whole pass — every
|
|
653
|
+
// member would fail the same way. Anything else is per-member.
|
|
654
|
+
if (/config_token|not_configured|not_logged_in/i.test(message) || /config(uration)? token/i.test(message)) {
|
|
655
|
+
this.logger.info('Agent identities skipped', { reason: message });
|
|
656
|
+
result.skipped = message;
|
|
657
|
+
break;
|
|
658
|
+
}
|
|
659
|
+
this.logger.warn('Could not provision Slack identity', { agent: member.sessionName, error: message });
|
|
660
|
+
continue;
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
if (record.status === 'pending_install' && record.installUrl && !record.announcedIn.includes(mapping.slackChannelId)) {
|
|
664
|
+
pendingLinks.push({ name: member.name, url: record.installUrl, session: member.sessionName });
|
|
665
|
+
}
|
|
666
|
+
if (record.status === 'installed' && record.botUserId && !record.invitedTo.includes(mapping.slackChannelId)) {
|
|
667
|
+
if (await this.inviteBot(mapping, member.sessionName, record.botUserId))
|
|
668
|
+
result.invited += 1;
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
if (pendingLinks.length > 0) {
|
|
672
|
+
const lines = pendingLinks.map((p) => `• *${p.name}* → <${p.url}|安装 ${p.name}>`);
|
|
673
|
+
await this.deps.slack
|
|
674
|
+
.sendMessage({
|
|
675
|
+
channelId: mapping.slackChannelId,
|
|
676
|
+
text: [
|
|
677
|
+
`:id: 给 *${team.name}* 的 ${pendingLinks.length} 位成员创建了 Slack 身份,点一下安装(每个各一次):`,
|
|
678
|
+
...lines,
|
|
679
|
+
'_安装后它们会以自己的名字出现在成员列表里,可以直接 @。_',
|
|
680
|
+
].join('\n'),
|
|
681
|
+
skipChatV2Mirror: true,
|
|
682
|
+
})
|
|
683
|
+
.then(async () => {
|
|
684
|
+
for (const p of pendingLinks)
|
|
685
|
+
await identities.markChannel(p.session, { announcedIn: mapping.slackChannelId });
|
|
686
|
+
result.announced = pendingLinks.length;
|
|
687
|
+
})
|
|
688
|
+
.catch((err) => {
|
|
689
|
+
this.logger.warn('Could not announce install links', { error: err instanceof Error ? err.message : String(err) });
|
|
690
|
+
});
|
|
691
|
+
}
|
|
692
|
+
return result;
|
|
693
|
+
}
|
|
694
|
+
/**
|
|
695
|
+
* Invite a newly installed agent bot into every channel of every team it
|
|
696
|
+
* belongs to. Runs from the identity service's `onInstalled` hook.
|
|
697
|
+
*
|
|
698
|
+
* @param agentSession - The agent that just got its bot user
|
|
699
|
+
*/
|
|
700
|
+
async inviteInstalledEverywhere(agentSession) {
|
|
701
|
+
const identities = this.deps.identities;
|
|
702
|
+
if (!identities)
|
|
703
|
+
return;
|
|
704
|
+
const record = identities.get(agentSession);
|
|
705
|
+
if (!record?.botUserId)
|
|
706
|
+
return;
|
|
707
|
+
await this.load();
|
|
708
|
+
const teams = await this.deps.storage.getTeams();
|
|
709
|
+
for (const team of teams) {
|
|
710
|
+
if (!teamChannelMembers(team).some((m) => m.sessionName === agentSession))
|
|
711
|
+
continue;
|
|
712
|
+
const mapping = this.findByTeamId(team.id);
|
|
713
|
+
if (!mapping || record.invitedTo.includes(mapping.slackChannelId))
|
|
714
|
+
continue;
|
|
715
|
+
await this.inviteBot(mapping, agentSession, record.botUserId);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
/** Invite one bot user into a mapped channel and remember it. */
|
|
719
|
+
async inviteBot(mapping, agentSession, botUserId) {
|
|
720
|
+
try {
|
|
721
|
+
await this.deps.slack.inviteToChannel(mapping.slackChannelId, [botUserId]);
|
|
722
|
+
}
|
|
723
|
+
catch (err) {
|
|
724
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
725
|
+
if (!/already_in_channel/.test(message)) {
|
|
726
|
+
this.logger.warn('Could not invite agent bot into channel', { agentSession, channel: mapping.slackChannelName, error: message });
|
|
727
|
+
return false;
|
|
728
|
+
}
|
|
729
|
+
}
|
|
730
|
+
await this.deps.identities?.markChannel(agentSession, { invitedTo: mapping.slackChannelId });
|
|
731
|
+
this.logger.info('Agent bot invited into team channel', { agentSession, channel: `#${mapping.slackChannelName}` });
|
|
732
|
+
return true;
|
|
733
|
+
}
|
|
734
|
+
// -------------------------------------------------------------------------
|
|
735
|
+
// Slack-side messages
|
|
736
|
+
// -------------------------------------------------------------------------
|
|
737
|
+
async postWelcome(mapping, team, members) {
|
|
738
|
+
const roster = members.length > 0
|
|
739
|
+
? members.map((m) => `• *${m.name}* (${m.role}) — \`@${m.name}\``).join('\n')
|
|
740
|
+
: '_(还没有成员,成员加入后会自动同步)_';
|
|
741
|
+
const text = [
|
|
742
|
+
`:tada: 团队 *${team.name}* 的频道已就绪。`,
|
|
743
|
+
'',
|
|
744
|
+
'成员:',
|
|
745
|
+
roster,
|
|
746
|
+
'',
|
|
747
|
+
'直接发言,全队都能看到;`@名字` 可以点名某个 agent 必须回复。回复会以各自的名字出现在 thread 里。',
|
|
748
|
+
].join('\n');
|
|
749
|
+
await this.deps.slack
|
|
750
|
+
.sendMessage({ channelId: mapping.slackChannelId, text, skipChatV2Mirror: true })
|
|
751
|
+
.catch((err) => {
|
|
752
|
+
this.logger.debug('Welcome message failed (non-critical)', {
|
|
753
|
+
error: err instanceof Error ? err.message : String(err),
|
|
754
|
+
});
|
|
755
|
+
});
|
|
756
|
+
}
|
|
757
|
+
async postUnknownMentionHint(message, unknown, candidates) {
|
|
758
|
+
const lines = unknown.map((u) => u.suggestions.length > 0
|
|
759
|
+
? `没有叫 \`@${u.token}\` 的成员,你是想找 ${u.suggestions.map((s) => `\`@${s}\``).join(' / ')} 吗?`
|
|
760
|
+
: `没有叫 \`@${u.token}\` 的成员。`);
|
|
761
|
+
const rosterHint = candidates.length > 0
|
|
762
|
+
? `本频道的成员:${candidates.map((c) => `\`@${c.name}\``).join(' ')}`
|
|
763
|
+
: '本频道目前没有成员。';
|
|
764
|
+
await this.deps.slack
|
|
765
|
+
.sendMessage({
|
|
766
|
+
channelId: message.channelId,
|
|
767
|
+
threadTs: message.threadTs || message.ts,
|
|
768
|
+
text: `${lines.join('\n')}\n${rosterHint}\n_(消息已经发给全队;只有被正确 @ 的成员会被要求必须回复。)_`,
|
|
769
|
+
skipChatV2Mirror: true,
|
|
770
|
+
})
|
|
771
|
+
.catch(() => undefined);
|
|
772
|
+
}
|
|
773
|
+
// -------------------------------------------------------------------------
|
|
774
|
+
// Helpers
|
|
775
|
+
// -------------------------------------------------------------------------
|
|
776
|
+
/** Run `fn` after any in-flight operation for the same team finishes. */
|
|
777
|
+
serialised(teamId, fn) {
|
|
778
|
+
const prev = this.inflight.get(teamId) ?? Promise.resolve();
|
|
779
|
+
const next = prev.catch(() => undefined).then(fn);
|
|
780
|
+
this.inflight.set(teamId, next);
|
|
781
|
+
const cleanup = () => {
|
|
782
|
+
if (this.inflight.get(teamId) === next)
|
|
783
|
+
this.inflight.delete(teamId);
|
|
784
|
+
};
|
|
785
|
+
// `then(cleanup, cleanup)` rather than `finally`: a rejected `next` is
|
|
786
|
+
// returned to the caller, and a second derived promise must not turn
|
|
787
|
+
// that same rejection into an unhandled one.
|
|
788
|
+
next.then(cleanup, cleanup);
|
|
789
|
+
return next;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
/** Type guard for persisted mapping rows. */
|
|
793
|
+
function isMapping(value) {
|
|
794
|
+
const v = value;
|
|
795
|
+
return (!!v &&
|
|
796
|
+
typeof v.teamId === 'string' &&
|
|
797
|
+
typeof v.slackChannelId === 'string' &&
|
|
798
|
+
typeof v.chatChannelId === 'string');
|
|
799
|
+
}
|
|
800
|
+
// ---------------------------------------------------------------------------
|
|
801
|
+
// Singleton
|
|
802
|
+
// ---------------------------------------------------------------------------
|
|
803
|
+
let instance = null;
|
|
804
|
+
/**
|
|
805
|
+
* Install the process-wide instance (composition root).
|
|
806
|
+
*
|
|
807
|
+
* @param service - The configured service
|
|
808
|
+
*/
|
|
809
|
+
export function setSlackTeamChannelService(service) {
|
|
810
|
+
instance = service;
|
|
811
|
+
}
|
|
812
|
+
/**
|
|
813
|
+
* The process-wide instance, or null before the composition root wires it
|
|
814
|
+
* (e.g. Slack not configured).
|
|
815
|
+
*
|
|
816
|
+
* @returns The service or null
|
|
817
|
+
*/
|
|
818
|
+
export function getSlackTeamChannelService() {
|
|
819
|
+
return instance;
|
|
820
|
+
}
|
|
821
|
+
//# sourceMappingURL=slack-team-channel.service.js.map
|