crewly 1.20.203 → 1.20.205
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/skills/_common/lib.sh +4 -3
- package/config/skills/agent/core/project-tickets/SKILL.md +13 -1
- package/config/skills/agent/core/project-tickets/execute.sh +20 -6
- package/config/skills/agent/core/project-tickets/execute.test.sh +8 -2
- package/config/skills/agent/core/reply/SKILL.md +1 -0
- package/config/skills/agent/core/reply/execute.sh +7 -1
- package/config/skills/agent/core/reply-channel/SKILL.md +4 -0
- package/config/skills/agent/core/reply-channel/execute.sh +12 -1
- package/dist/backend/backend/src/constants.d.ts +124 -0
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +130 -0
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.js +1 -0
- package/dist/backend/backend/src/controllers/chat/agent-reply.controller.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 +1 -0
- package/dist/backend/backend/src/controllers/chat/chat.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +50 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts +6 -0
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js +2 -1
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js +9 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.d.ts +1 -1
- package/dist/backend/backend/src/controllers/project-tickets/project-tickets.routes.js +1 -1
- package/dist/backend/backend/src/controllers/settings/role.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/settings/role.controller.js +21 -7
- package/dist/backend/backend/src/controllers/settings/role.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/settings/settings.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/settings/settings.controller.js +26 -20
- package/dist/backend/backend/src/controllers/settings/settings.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts +13 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/skill/skill.controller.js +21 -0
- package/dist/backend/backend/src/controllers/skill/skill.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts +11 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/system/system-control.controller.js +36 -2
- package/dist/backend/backend/src/controllers/system/system-control.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts +9 -0
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +37 -1
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts +3 -2
- package/dist/backend/backend/src/middleware/agent-origin.middleware.d.ts.map +1 -1
- package/dist/backend/backend/src/middleware/agent-origin.middleware.js +5 -3
- package/dist/backend/backend/src/middleware/agent-origin.middleware.js.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts +36 -0
- package/dist/backend/backend/src/middleware/caller-identity.middleware.d.ts.map +1 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js +97 -1
- package/dist/backend/backend/src/middleware/caller-identity.middleware.js.map +1 -1
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts +4 -0
- package/dist/backend/backend/src/routes/modules/terminal.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/modules/terminal.routes.js +19 -5
- package/dist/backend/backend/src/routes/modules/terminal.routes.js.map +1 -1
- package/dist/backend/backend/src/scripts/input-guard-classify.d.ts +44 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.js +116 -0
- package/dist/backend/backend/src/scripts/input-guard-classify.js.map +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
- package/dist/backend/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +117 -0
- 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 +165 -9
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/room-context-backlog.d.ts +93 -0
- package/dist/backend/backend/src/services/chat-v2/room-context-backlog.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/room-context-backlog.js +138 -0
- package/dist/backend/backend/src/services/chat-v2/room-context-backlog.js.map +1 -0
- package/dist/backend/backend/src/services/decisions/decision-card.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision-card.js +5 -1
- package/dist/backend/backend/src/services/decisions/decision-card.js.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts +26 -1
- package/dist/backend/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/decisions/decision.service.js +83 -5
- package/dist/backend/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts +75 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.js +87 -0
- package/dist/backend/backend/src/services/messaging/forced-delivery.js.map +1 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +21 -0
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js +48 -3
- package/dist/backend/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/backend/backend/src/services/monitoring/liveness-monitor.service.d.ts +27 -1
- package/dist/backend/backend/src/services/monitoring/liveness-monitor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/monitoring/liveness-monitor.service.js +51 -2
- package/dist/backend/backend/src/services/monitoring/liveness-monitor.service.js.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-chat-owner-mirror.d.ts +51 -4
- package/dist/backend/backend/src/services/orc/orc-chat-owner-mirror.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/orc-chat-owner-mirror.js +83 -6
- package/dist/backend/backend/src/services/orc/orc-chat-owner-mirror.js.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts +16 -0
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js +22 -2
- package/dist/backend/backend/src/services/orc/reply-destination.wiring.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts +130 -4
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js +115 -3
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-decision.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts +122 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.d.ts.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js +274 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-goal.js.map +1 -0
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts +34 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js +59 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-messages.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-retro.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts +6 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js +9 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-stats.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts +13 -3
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js +28 -2
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot-trace.js.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts +92 -7
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js +316 -41
- package/dist/backend/backend/src/services/project-tickets/ticket-autopilot.service.js.map +1 -1
- package/dist/backend/backend/src/services/restart/in-flight-turn-tracker.service.d.ts +9 -0
- package/dist/backend/backend/src/services/restart/in-flight-turn-tracker.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/restart/in-flight-turn-tracker.service.js +11 -0
- package/dist/backend/backend/src/services/restart/in-flight-turn-tracker.service.js.map +1 -1
- package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts +126 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.js +223 -0
- package/dist/backend/backend/src/services/settings/api-key-access.service.js.map +1 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/skill/skill-executor.service.js +17 -0
- package/dist/backend/backend/src/services/skill/skill-executor.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/room-responder.d.ts +144 -0
- package/dist/backend/backend/src/services/slack/room-responder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/slack/room-responder.js +236 -0
- package/dist/backend/backend/src/services/slack/room-responder.js.map +1 -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 +36 -2
- package/dist/backend/backend/src/services/slack/slack-initializer.js.map +1 -1
- 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 +29 -8
- 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 +107 -2
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js +403 -13
- package/dist/backend/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts +27 -0
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.js +52 -0
- package/dist/backend/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts +9 -0
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js +20 -0
- package/dist/backend/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts +24 -0
- package/dist/backend/backend/src/services/system/auto-update.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.service.js +78 -8
- package/dist/backend/backend/src/services/system/auto-update.service.js.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/auto-update.utils.js.map +1 -1
- package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts +162 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.js +264 -0
- package/dist/backend/backend/src/services/system/input-guard-release-check.js.map +1 -0
- package/dist/backend/backend/src/services/system/system-control.service.d.ts +36 -0
- package/dist/backend/backend/src/services/system/system-control.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/system/system-control.service.js +109 -10
- package/dist/backend/backend/src/services/system/system-control.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/ticket-channel-hooks.d.ts +13 -1
- package/dist/backend/backend/src/services/v3/ticket-channel-hooks.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/ticket-channel-hooks.js +36 -3
- package/dist/backend/backend/src/services/v3/ticket-channel-hooks.js.map +1 -1
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.d.ts +54 -3
- 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 +117 -9
- package/dist/backend/backend/src/services/v3/workitem-dispatch.subscriber.js.map +1 -1
- package/dist/backend/backend/src/types/decision.types.d.ts +7 -0
- package/dist/backend/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/skill.types.d.ts +16 -0
- package/dist/backend/backend/src/types/skill.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/skill.types.js.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts +21 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/ticket-autopilot.types.js +48 -0
- package/dist/backend/backend/src/types/ticket-autopilot.types.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js +1 -0
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/build-info.json +2 -2
- package/dist/cli/backend/src/constants.d.ts +124 -0
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +130 -0
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/scripts/input-guard-classify.d.ts +44 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.d.ts.map +1 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.js +116 -0
- package/dist/cli/backend/src/scripts/input-guard-classify.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +8 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +10 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +8 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +21 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +13 -27
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +117 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +165 -9
- package/dist/cli/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -1
- package/dist/cli/backend/src/services/chat-v2/room-context-backlog.d.ts +93 -0
- package/dist/cli/backend/src/services/chat-v2/room-context-backlog.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/room-context-backlog.js +138 -0
- package/dist/cli/backend/src/services/chat-v2/room-context-backlog.js.map +1 -0
- package/dist/cli/backend/src/services/decisions/decision-card.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision-card.js +5 -1
- package/dist/cli/backend/src/services/decisions/decision-card.js.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts +26 -1
- package/dist/cli/backend/src/services/decisions/decision.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/decisions/decision.service.js +83 -5
- package/dist/cli/backend/src/services/decisions/decision.service.js.map +1 -1
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts +59 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js +70 -0
- package/dist/cli/backend/src/services/messaging/drain-queued-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts +75 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.js +87 -0
- package/dist/cli/backend/src/services/messaging/forced-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts +54 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js +65 -0
- package/dist/cli/backend/src/services/messaging/spend-capped-delivery.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +21 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +48 -3
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -1
- package/dist/cli/backend/src/services/restart/in-flight-turn-tracker.service.d.ts +9 -0
- package/dist/cli/backend/src/services/restart/in-flight-turn-tracker.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/restart/in-flight-turn-tracker.service.js +11 -0
- package/dist/cli/backend/src/services/restart/in-flight-turn-tracker.service.js.map +1 -1
- package/dist/cli/backend/src/services/skill/skill-executor.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/skill/skill-executor.service.js +17 -0
- package/dist/cli/backend/src/services/skill/skill-executor.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/room-responder.d.ts +144 -0
- package/dist/cli/backend/src/services/slack/room-responder.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/room-responder.js +236 -0
- package/dist/cli/backend/src/services/slack/room-responder.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 +29 -8
- 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 +107 -2
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js +403 -13
- package/dist/cli/backend/src/services/slack/slack-team-channel.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts +27 -0
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.js +52 -0
- package/dist/cli/backend/src/services/slack/slack-thread-context.service.js.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts +9 -0
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js +20 -0
- package/dist/cli/backend/src/services/slack/slack-typing-placeholder.service.js.map +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.d.ts.map +1 -1
- package/dist/cli/backend/src/services/system/auto-update.utils.js.map +1 -1
- package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts +162 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.d.ts.map +1 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.js +264 -0
- package/dist/cli/backend/src/services/system/input-guard-release-check.js.map +1 -0
- package/dist/cli/backend/src/services/v3/ticket-channel-hooks.d.ts +13 -1
- package/dist/cli/backend/src/services/v3/ticket-channel-hooks.d.ts.map +1 -1
- package/dist/cli/backend/src/services/v3/ticket-channel-hooks.js +36 -3
- package/dist/cli/backend/src/services/v3/ticket-channel-hooks.js.map +1 -1
- package/dist/cli/backend/src/types/decision.types.d.ts +7 -0
- package/dist/cli/backend/src/types/decision.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/skill.types.d.ts +16 -0
- package/dist/cli/backend/src/types/skill.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/skill.types.js.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts +21 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/ticket-autopilot.types.js +48 -0
- package/dist/cli/backend/src/types/ticket-autopilot.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts.map +1 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js +1 -0
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/cli/cli/src/commands/doctor.d.ts +20 -0
- package/dist/cli/cli/src/commands/doctor.d.ts.map +1 -1
- package/dist/cli/cli/src/commands/doctor.js +49 -0
- package/dist/cli/cli/src/commands/doctor.js.map +1 -1
- package/dist/cli/cli/src/index.js +9 -2
- package/dist/cli/cli/src/index.js.map +1 -1
- package/frontend/dist/assets/{index-63a33dbf.js → index-f64b333a.js} +258 -258
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -1
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
import { notePerson } from '../people/people-directory.service.js';
|
|
28
28
|
import { dedicatedDecisionFor } from '../people/dedicated-agent.js';
|
|
29
29
|
import { getTicketIntakeService } from '../v3/ticket-intake.service.js';
|
|
30
|
-
import { intakeWithin, slackIntakeMessage, ticketOfOutcome, markAndLinkTicket } from '../v3/ticket-channel-hooks.js';
|
|
30
|
+
import { intakeWithin, slackIntakeMessage, ticketOfOutcome, markAndLinkTicket, linkLateTicket } from '../v3/ticket-channel-hooks.js';
|
|
31
31
|
import { CREWLY_CONSTANTS, RUNTIME_FALLBACK_CONSTANTS } from '../../constants.js';
|
|
32
32
|
import { isInterim } from './slack-typing-placeholder.service.js';
|
|
33
33
|
import { isOwnerAuthored, deliveredSessions } from './slack-auto-working.service.js';
|
|
@@ -38,6 +38,8 @@ import * as path from 'path';
|
|
|
38
38
|
import { promises as fs } from 'fs';
|
|
39
39
|
import { describeSlackError } from './slack.service.js';
|
|
40
40
|
import { isSilentByDefault, } from '../chat-v2/chat-v2.dispatcher.service.js';
|
|
41
|
+
import { ROOM_RESPONDER_CONSTANTS } from '../../constants.js';
|
|
42
|
+
import { buildRoomAgentDirectory, findPriorRoomAnswer, threadOwnerFromSlack, } from './room-responder.js';
|
|
41
43
|
import { getCrewlyHomePath } from '../core/crewly-home.utils.js';
|
|
42
44
|
import { atomicWriteJson, safeReadJson } from '../../utils/file-io.utils.js';
|
|
43
45
|
import { LoggerService } from '../core/logger.service.js';
|
|
@@ -96,6 +98,27 @@ export function roomOwnerInstance(room) {
|
|
|
96
98
|
return room.home.instanceId;
|
|
97
99
|
return awake.sort()[0];
|
|
98
100
|
}
|
|
101
|
+
/**
|
|
102
|
+
* The one machine that watches a shared-room message nobody on it answers:
|
|
103
|
+
* the room owner (#1019), else Cloud's fallback machine, else — nobody awake
|
|
104
|
+
* and no fallback named — the lowest instance id among the machines in the
|
|
105
|
+
* room that Cloud reports live (`live`, when Cloud sends it; a member it
|
|
106
|
+
* marks `live: false` never counts). Every machine computes the same answer
|
|
107
|
+
* from the same snapshot. Because the last resort may still name a machine
|
|
108
|
+
* that is gone, every other machine that received the message keeps a
|
|
109
|
+
* note-only watch (see `NOTE_ONLY_WATCH_MS`).
|
|
110
|
+
*
|
|
111
|
+
* @param room - Cloud's room presence
|
|
112
|
+
* @returns Instance id, or null for an empty roster
|
|
113
|
+
*/
|
|
114
|
+
export function roomWatcherInstance(room) {
|
|
115
|
+
const owner = roomOwnerInstance(room) ?? room.fallback?.instanceId;
|
|
116
|
+
if (owner)
|
|
117
|
+
return owner;
|
|
118
|
+
const live = (m) => m.live;
|
|
119
|
+
const pool = room.members.some((m) => live(m) === true) ? room.members.filter((m) => live(m) === true) : room.members.filter((m) => live(m) !== false);
|
|
120
|
+
return [...new Set(pool.map((m) => m.instanceId))].sort()[0] ?? null;
|
|
121
|
+
}
|
|
99
122
|
/**
|
|
100
123
|
* Whether a mapping is an ad-hoc channel (no Crewly team behind it).
|
|
101
124
|
*
|
|
@@ -1357,8 +1380,16 @@ export class SlackTeamChannelService {
|
|
|
1357
1380
|
}
|
|
1358
1381
|
return { mapping, message: persisted, mentions: [], dispatch: null };
|
|
1359
1382
|
}
|
|
1383
|
+
// One responder per message a person wrote; everyone else listens
|
|
1384
|
+
// (specs/2026-10-03-one-responder-per-message.md). Agent-authored posts
|
|
1385
|
+
// keep the older fan-out. A step that does not finish still yields a
|
|
1386
|
+
// responder: without a pin the dispatcher's rules pick one.
|
|
1387
|
+
const oneResponder = remoteAgent
|
|
1388
|
+
? null
|
|
1389
|
+
: await this.bounded(this.oneResponderFor(message, mapping, members, team ?? null, resolved.mentions, handoffTo, threadId, persisted.id), {}, 'responder', message);
|
|
1390
|
+
const routedOptions = oneResponder ? { ...dispatchOptions, oneResponder } : dispatchOptions;
|
|
1360
1391
|
const planned = dispatcherForPlan?.planHuddleTargets
|
|
1361
|
-
? await this.bounded(dispatcherForPlan.planHuddleTargets(channel, persisted,
|
|
1392
|
+
? await this.bounded(dispatcherForPlan.planHuddleTargets(channel, persisted, routedOptions).catch(() => null), null, 'dispatch plan', message)
|
|
1362
1393
|
: null;
|
|
1363
1394
|
// Prompt backstop: who the message (or the exchange it may continue)
|
|
1364
1395
|
// was meant for, when people are in the picture. A direct request inside
|
|
@@ -1380,7 +1411,7 @@ export class SlackTeamChannelService {
|
|
|
1380
1411
|
// the single intake. Started now, awaited just before dispatch, so the
|
|
1381
1412
|
// receipt and the agent's `[TICKET:…]` marker do not hold up the eyes /
|
|
1382
1413
|
// placeholders below.
|
|
1383
|
-
const ticketPromise = this.intakeTicket(message, mapping, resolved.mentions, planned, handoffTo, remoteAgent);
|
|
1414
|
+
const ticketPromise = this.intakeTicket(message, mapping, resolved.mentions, planned, handoffTo, remoteAgent, persisted);
|
|
1384
1415
|
await this.bounded(this.acknowledgeSeen(message, mapping, resolved.mentions, planned), undefined, 'seen reaction', message);
|
|
1385
1416
|
// Agents that must reply get a placeholder straight away — "waking up…"
|
|
1386
1417
|
// for an idle agent (a cold start is 1–2 minutes), "is working on it…"
|
|
@@ -1416,6 +1447,9 @@ export class SlackTeamChannelService {
|
|
|
1416
1447
|
const typingTargets = [];
|
|
1417
1448
|
if (this.deps.typing) {
|
|
1418
1449
|
for (const session of owing) {
|
|
1450
|
+
// Rescued meanwhile: the hand-off posts its own placeholder.
|
|
1451
|
+
if (routeGuard?.rescued())
|
|
1452
|
+
break;
|
|
1419
1453
|
const member = members.find((m) => m.sessionName === session);
|
|
1420
1454
|
const installed = this.deps.identities?.getInstalled(session);
|
|
1421
1455
|
// Own bot when installed; otherwise the master bot wearing the agent's
|
|
@@ -1425,6 +1459,7 @@ export class SlackTeamChannelService {
|
|
|
1425
1459
|
: { displayName: member?.name ?? session, ...slackIdentityFor(member, session) };
|
|
1426
1460
|
const key = { agentSession: session, slackChannelId: message.channelId, threadTs: slackThreadTs };
|
|
1427
1461
|
const awake = this.deps.isAgentAwake ? this.deps.isAgentAwake(session) : true;
|
|
1462
|
+
routeGuard?.notePlaceholder(key);
|
|
1428
1463
|
await this.bounded(this.deps.typing.begin(key, identity, awake ? 'typing' : 'waking', message.ts), null, 'placeholder', message);
|
|
1429
1464
|
typingTargets.push({ session, key });
|
|
1430
1465
|
}
|
|
@@ -1479,7 +1514,7 @@ export class SlackTeamChannelService {
|
|
|
1479
1514
|
return { mapping, message: persisted, mentions: resolved.mentions, dispatch: null };
|
|
1480
1515
|
}
|
|
1481
1516
|
dispatch = await dispatcher.dispatchMessage(channel, markAndLinkTicket(persisted, ticket), {
|
|
1482
|
-
...
|
|
1517
|
+
...routedOptions,
|
|
1483
1518
|
...(roster ? { channelRoster: roster } : {}),
|
|
1484
1519
|
...(peopleAddressing ? { peopleAddressing } : {}),
|
|
1485
1520
|
...(slackContext
|
|
@@ -1521,8 +1556,19 @@ export class SlackTeamChannelService {
|
|
|
1521
1556
|
unknown: resolved.unknown.map((u) => u.token),
|
|
1522
1557
|
strategy: dispatch?.strategy ?? 'none',
|
|
1523
1558
|
threaded: !!threadId,
|
|
1559
|
+
...(oneResponder
|
|
1560
|
+
? {
|
|
1561
|
+
responder: oneResponder.pinned
|
|
1562
|
+
? { session: oneResponder.pinned.session, name: oneResponder.pinned.name ?? null, reason: oneResponder.pinned.reason }
|
|
1563
|
+
: (dispatch?.huddleOutcomes ?? []).map((o) => o.sessionName),
|
|
1564
|
+
contextOnly: dispatch?.contextOnly ?? [],
|
|
1565
|
+
}
|
|
1566
|
+
: {}),
|
|
1524
1567
|
});
|
|
1525
1568
|
const recipients = deliveredSessions(dispatch);
|
|
1569
|
+
const heldBy = (dispatch?.huddleOutcomes ?? []).filter((o) => o.dispatched && o.queued).map((o) => o.sessionName);
|
|
1570
|
+
if (oneResponder)
|
|
1571
|
+
this.recordChosenResponders(persisted, dispatch, oneResponder);
|
|
1526
1572
|
if (dispatch?.dispatched) {
|
|
1527
1573
|
// Someone here holds it — including a follow-up in a thread we were
|
|
1528
1574
|
// still waiting on.
|
|
@@ -1530,8 +1576,11 @@ export class SlackTeamChannelService {
|
|
|
1530
1576
|
// The owner machine of a shared room (others deferred to it) runs the
|
|
1531
1577
|
// one 90 s fallback: if nobody here answers, its lead is woken.
|
|
1532
1578
|
const allOptional = (dispatch.huddleOutcomes ?? []).every((o) => o.responseMode !== 'required');
|
|
1533
|
-
|
|
1534
|
-
|
|
1579
|
+
// With one responder the other awake agents no longer hear it, so an
|
|
1580
|
+
// optional responder that stays silent needs the fallback in any room.
|
|
1581
|
+
const optionalResponder = !!oneResponder && (dispatch.huddleOutcomes ?? []).length > 0 && allOptional;
|
|
1582
|
+
if (((presence?.ownsSharedMessage && allOptional) || optionalResponder) && !handoffTo && isOwnerAuthored(message, this.deps.getOwnerUserId?.())) {
|
|
1583
|
+
this.watchUnanswered(message, mapping, persisted, dispatchOptions.threadId, recipients, heldBy);
|
|
1535
1584
|
}
|
|
1536
1585
|
}
|
|
1537
1586
|
else if (!remoteAgent) {
|
|
@@ -1550,8 +1599,17 @@ export class SlackTeamChannelService {
|
|
|
1550
1599
|
});
|
|
1551
1600
|
// A message deferred to another machine is watched there, by its
|
|
1552
1601
|
// owner, only — two machines running the fallback would answer twice.
|
|
1553
|
-
|
|
1554
|
-
|
|
1602
|
+
// One the decision path consumed, or whose responder runs on another
|
|
1603
|
+
// machine, is that path's / machine's to answer.
|
|
1604
|
+
// A responder on another machine is watched from the room's owner
|
|
1605
|
+
// machine (watchHere): never an owner message without a watcher.
|
|
1606
|
+
const pinned = oneResponder?.pinned;
|
|
1607
|
+
const watchHere = pinned?.watchHere === true;
|
|
1608
|
+
const noteOnly = watchHere && pinned?.watchNoteOnly === true;
|
|
1609
|
+
const answeredElsewhere = !!pinned && !pinned.session && !watchHere;
|
|
1610
|
+
const watchable = watchHere || (!answeredElsewhere && !presence?.deferredElsewhere && !presence?.fallbackElsewhere);
|
|
1611
|
+
if (!handoffTo && watchable && isOwnerAuthored(message, this.deps.getOwnerUserId?.())) {
|
|
1612
|
+
this.watchUnanswered(message, mapping, persisted, dispatchOptions.threadId, recipients, heldBy, noteOnly);
|
|
1555
1613
|
}
|
|
1556
1614
|
}
|
|
1557
1615
|
return { mapping, message: persisted, mentions: resolved.mentions, dispatch };
|
|
@@ -1564,6 +1622,253 @@ export class SlackTeamChannelService {
|
|
|
1564
1622
|
routeGuard?.disarm();
|
|
1565
1623
|
}
|
|
1566
1624
|
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Who answers a person's room message, from data only this layer has
|
|
1627
|
+
* (specs/2026-10-03-one-responder-per-message.md §1): the decision path's
|
|
1628
|
+
* outcome (a), else — for an un-@'d thread reply — the Slack thread's
|
|
1629
|
+
* owner (c). @-mentions (b) and the top-level room rule (d) are applied by
|
|
1630
|
+
* the dispatcher. Never throws.
|
|
1631
|
+
*
|
|
1632
|
+
* @param message - Inbound message
|
|
1633
|
+
* @param mapping - Its room
|
|
1634
|
+
* @param members - Agents the room resolves names against (local)
|
|
1635
|
+
* @param team - The room's team, or null for an ad-hoc room
|
|
1636
|
+
* @param mentions - Local agents @'d
|
|
1637
|
+
* @param handoffTo - Local agent the message was handed to
|
|
1638
|
+
* @returns One-responder options for the dispatcher
|
|
1639
|
+
*/
|
|
1640
|
+
async oneResponderFor(message, mapping, members, team, mentions, handoffTo, threadId, messageId) {
|
|
1641
|
+
const nameFor = (session) => members.find((m) => m.sessionName === session)?.name ??
|
|
1642
|
+
message.room?.members.find((m) => localAgentSession(m.agentSession) === session)?.displayName;
|
|
1643
|
+
const base = { nameFor };
|
|
1644
|
+
const isThreadReply = !!message.threadTs && message.threadTs !== message.ts;
|
|
1645
|
+
if (handoffTo || !isThreadReply)
|
|
1646
|
+
return base;
|
|
1647
|
+
try {
|
|
1648
|
+
const roomSessions = team ? teamChannelMembers(team).map((m) => m.sessionName) : [...(mapping.members ?? [])];
|
|
1649
|
+
const isLocal = (s) => this.deps.isLocalAgent?.(s) ?? members.some((m) => m.sessionName === s);
|
|
1650
|
+
// Which machine decides when the shared picture is not enough: the
|
|
1651
|
+
// #1019 room owner (or Cloud's fallback machine). Without presence this
|
|
1652
|
+
// machine is the only one that can.
|
|
1653
|
+
const room = message.room ?? null;
|
|
1654
|
+
const me = room && this.deps.resolveInstanceId ? await this.deps.resolveInstanceId().catch(() => null) : null;
|
|
1655
|
+
// The machine that decides and watches (owner, else fallback, else the
|
|
1656
|
+
// lowest instance id). A machine that cannot tell who it is watches
|
|
1657
|
+
// anyway: two watchers beat none (the fallback reads Slack first).
|
|
1658
|
+
const watcher = room ? roomWatcherInstance(room) : null;
|
|
1659
|
+
const ownsRoom = !room || !me || !watcher || watcher === me;
|
|
1660
|
+
const isWatcher = !!room && !!me && (!watcher || watcher === me);
|
|
1661
|
+
// Cloud named no owner and no fallback machine: the last-resort watcher
|
|
1662
|
+
// may be gone, so every machine that received this keeps a note-only
|
|
1663
|
+
// watch. So does a machine that cannot tell who it is.
|
|
1664
|
+
const lastResort = !!room && !roomOwnerInstance(room) && !room.fallback;
|
|
1665
|
+
const noteOnlyHere = !!room && !isWatcher && (!me || lastResort);
|
|
1666
|
+
const watchFlags = () => isWatcher ? { watchHere: true } : noteOnlyHere ? { watchHere: true, watchNoteOnly: true } : {};
|
|
1667
|
+
// Not ours to decide: the owner machine applies the room's rules and watches.
|
|
1668
|
+
const deferToRoomOwner = { ...base, pinned: { session: null, reason: 'deferred', ...watchFlags() } };
|
|
1669
|
+
// Ours to watch, but not to guess: nobody here answers; the 90 s watch does.
|
|
1670
|
+
const watchOnly = { ...base, pinned: { session: null, reason: 'deferred', ...watchFlags() } };
|
|
1671
|
+
// (a) The decision path: one shared run per Slack message.
|
|
1672
|
+
const decision = this.deps.decisionReplyFor
|
|
1673
|
+
? await withinMs(this.deps.decisionReplyFor(message).catch(() => null), ROOM_RESPONDER_CONSTANTS.DECISION_WAIT_MS, null)
|
|
1674
|
+
: null;
|
|
1675
|
+
if (decision) {
|
|
1676
|
+
const askerHere = isLocal(decision.asker) && roomSessions.includes(decision.asker);
|
|
1677
|
+
const name = nameFor(decision.asker) ?? decision.asker;
|
|
1678
|
+
if (decision.consumed) {
|
|
1679
|
+
return {
|
|
1680
|
+
...base,
|
|
1681
|
+
pinned: { session: null, answeredBy: decision.asker, name, reason: 'decision-consumed', ...(askerHere ? { alreadyHas: decision.asker } : {}) },
|
|
1682
|
+
};
|
|
1683
|
+
}
|
|
1684
|
+
if (askerHere)
|
|
1685
|
+
return { ...base, pinned: { session: decision.asker, answeredBy: decision.asker, name, reason: 'decision' } };
|
|
1686
|
+
// The asker is not in this room and the card is still open: the room's own rules.
|
|
1687
|
+
}
|
|
1688
|
+
if (mentions.length > 0)
|
|
1689
|
+
return base;
|
|
1690
|
+
// (c) The Slack thread's owner — the same answer on every machine.
|
|
1691
|
+
const read = await this.slackThreadOwner(message, members, roomSessions, isLocal);
|
|
1692
|
+
if (!read.readable) {
|
|
1693
|
+
// Machines that cannot read the thread would each guess from their
|
|
1694
|
+
// own log: only the room's owner machine does — and only when its log
|
|
1695
|
+
// can be trusted to name the last speaker. Otherwise it answers
|
|
1696
|
+
// nothing and keeps the 90 s watch.
|
|
1697
|
+
if (!ownsRoom)
|
|
1698
|
+
return deferToRoomOwner;
|
|
1699
|
+
if (!room)
|
|
1700
|
+
return base;
|
|
1701
|
+
return this.localThreadLogTrusted(mapping.chatChannelId, threadId, messageId, room, me) ? base : watchOnly;
|
|
1702
|
+
}
|
|
1703
|
+
if (!read.owner)
|
|
1704
|
+
return base;
|
|
1705
|
+
const { agent, via } = read.owner;
|
|
1706
|
+
// A remote owner counts only while Cloud shows it awake — an offline,
|
|
1707
|
+
// stale or retired machine never answers. Every machine judges by the
|
|
1708
|
+
// same snapshot (its own agents included). A card's asker is the
|
|
1709
|
+
// exception: its machine's decision path takes the reply.
|
|
1710
|
+
const live = !room || room.members.some((m) => localAgentSession(m.agentSession) === localAgentSession(agent.session) && m.awake);
|
|
1711
|
+
if (live || via === 'card') {
|
|
1712
|
+
if (agent.local)
|
|
1713
|
+
return { ...base, pinned: { session: agent.session, answeredBy: agent.session, name: agent.name, reason: 'thread-owner' } };
|
|
1714
|
+
// An awake asker's machine answers a card through the decision path —
|
|
1715
|
+
// no placeholder, no thread post — so a watch here would hand the
|
|
1716
|
+
// message to our lead at 90 s and answer it twice. Watch only an
|
|
1717
|
+
// asker that is not awake (its machine may be offline).
|
|
1718
|
+
const watch = !(via === 'card' && live);
|
|
1719
|
+
return { ...base, pinned: { session: null, answeredBy: agent.session, name: agent.name, reason: 'thread-owner', ...(watch ? watchFlags() : {}) } };
|
|
1720
|
+
}
|
|
1721
|
+
return ownsRoom ? base : deferToRoomOwner;
|
|
1722
|
+
}
|
|
1723
|
+
catch (err) {
|
|
1724
|
+
this.logger.warn('Could not choose the responder — using the room rules', {
|
|
1725
|
+
slackChannel: mapping.slackChannelName ?? message.channelId,
|
|
1726
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1727
|
+
});
|
|
1728
|
+
}
|
|
1729
|
+
return base;
|
|
1730
|
+
}
|
|
1731
|
+
/**
|
|
1732
|
+
* Whether this machine's chat log can name a thread's last speaker when
|
|
1733
|
+
* Slack cannot be read. Cloud never forwards other machines' bot posts, so
|
|
1734
|
+
* with colleagues on other machines in the room the log is trusted only
|
|
1735
|
+
* when its latest agent turn in the thread is local and recent.
|
|
1736
|
+
*
|
|
1737
|
+
* @param chatChannelId - The huddle
|
|
1738
|
+
* @param threadId - The thread root here, when known
|
|
1739
|
+
* @param messageId - The message being routed (not counted)
|
|
1740
|
+
* @param room - Cloud's room presence
|
|
1741
|
+
* @param me - This machine's instance id, when known
|
|
1742
|
+
* @returns True when local rules may answer from the log
|
|
1743
|
+
*/
|
|
1744
|
+
localThreadLogTrusted(chatChannelId, threadId, messageId, room, me) {
|
|
1745
|
+
const othersInRoom = room.members.some((m) => !me || m.instanceId !== me);
|
|
1746
|
+
if (!othersInRoom)
|
|
1747
|
+
return true;
|
|
1748
|
+
if (!threadId || !this.deps.chat.listThreadForBridge)
|
|
1749
|
+
return false;
|
|
1750
|
+
const rows = this.deps.chat.listThreadForBridge(chatChannelId, threadId).filter((r) => r.id !== messageId);
|
|
1751
|
+
const isLocal = (s) => this.deps.isLocalAgent?.(localAgentSession(s)) ?? false;
|
|
1752
|
+
// A person @'d an agent on another machine: that agent's answer (no @
|
|
1753
|
+
// of anyone here) never reaches this log, so it cannot name the last speaker.
|
|
1754
|
+
const askedElsewhere = (r) => {
|
|
1755
|
+
if (r.senderType !== 'user' || typeof r.metadata?.[OWNER_EVIDENCE_METADATA.REMOTE_AGENT_SESSION] === 'string')
|
|
1756
|
+
return false;
|
|
1757
|
+
const named = r.metadata?.[SLACK_TEAM_CHANNEL_CONSTANTS.AGENT_MENTIONS_METADATA_KEY];
|
|
1758
|
+
return Array.isArray(named) && named.some((s) => typeof s === 'string' && !isLocal(s));
|
|
1759
|
+
};
|
|
1760
|
+
for (let i = rows.length - 1; i >= 0; i--) {
|
|
1761
|
+
const r = rows[i];
|
|
1762
|
+
if (askedElsewhere(r))
|
|
1763
|
+
return false;
|
|
1764
|
+
const remote = r.senderType === 'user' && typeof r.metadata?.[OWNER_EVIDENCE_METADATA.REMOTE_AGENT_SESSION] === 'string';
|
|
1765
|
+
if (r.senderType !== 'agent' && !remote)
|
|
1766
|
+
continue;
|
|
1767
|
+
if (remote)
|
|
1768
|
+
return false;
|
|
1769
|
+
const now = this.deps.now ? this.deps.now().getTime() : Date.now();
|
|
1770
|
+
return now - r.createdAt <= ROOM_RESPONDER_CONSTANTS.LOCAL_LOG_FRESH_MS;
|
|
1771
|
+
}
|
|
1772
|
+
return false;
|
|
1773
|
+
}
|
|
1774
|
+
/**
|
|
1775
|
+
* Record on the owner's row who was chosen to answer it — the reply gate
|
|
1776
|
+
* holds only a post that repeats what one of them already said.
|
|
1777
|
+
*
|
|
1778
|
+
* @param persisted - The owner's huddle row
|
|
1779
|
+
* @param dispatch - What was delivered here
|
|
1780
|
+
* @param one - The one-responder options used
|
|
1781
|
+
*/
|
|
1782
|
+
recordChosenResponders(persisted, dispatch, one) {
|
|
1783
|
+
const chosen = [
|
|
1784
|
+
...new Set([
|
|
1785
|
+
...(dispatch?.huddleOutcomes ?? []).filter((o) => o.dispatched).map((o) => o.sessionName),
|
|
1786
|
+
...(one.pinned?.answeredBy ? [one.pinned.answeredBy] : []),
|
|
1787
|
+
]),
|
|
1788
|
+
];
|
|
1789
|
+
if (chosen.length === 0 || !this.deps.chat.updateMessageMetadata)
|
|
1790
|
+
return;
|
|
1791
|
+
try {
|
|
1792
|
+
this.deps.chat.updateMessageMetadata(persisted.id, { [ROOM_RESPONDER_CONSTANTS.CHOSEN_RESPONDERS_METADATA_KEY]: chosen });
|
|
1793
|
+
}
|
|
1794
|
+
catch {
|
|
1795
|
+
// The gate then holds nothing for this message: never worse than before.
|
|
1796
|
+
}
|
|
1797
|
+
}
|
|
1798
|
+
/**
|
|
1799
|
+
* The owner of the Slack thread a message replies in, read from Slack
|
|
1800
|
+
* (`message.threadContext`), so every machine names the same agent. Null
|
|
1801
|
+
* owner when no agent has spoken in it; `readable: false` when the thread
|
|
1802
|
+
* could not be read in time.
|
|
1803
|
+
*
|
|
1804
|
+
* @param message - Inbound thread reply
|
|
1805
|
+
* @param members - Local agents the room resolves names against
|
|
1806
|
+
* @param roomSessions - This machine's room members
|
|
1807
|
+
* @param isLocal - Whether a session runs here
|
|
1808
|
+
* @returns The thread owner, or null
|
|
1809
|
+
*/
|
|
1810
|
+
async slackThreadOwner(message, members, roomSessions, isLocal) {
|
|
1811
|
+
let ctx = message.threadContext
|
|
1812
|
+
? await withinMs(message.threadContext, ROOM_RESPONDER_CONSTANTS.THREAD_CONTEXT_WAIT_MS, null)
|
|
1813
|
+
: null;
|
|
1814
|
+
if (!ctx && this.deps.readThreadContext) {
|
|
1815
|
+
// One retry after a short pause; a rate limit's retry-after is waited
|
|
1816
|
+
// out too, up to THREAD_READ_RETRY_MAX_WAIT_MS in all.
|
|
1817
|
+
const backoff = ROOM_RESPONDER_CONSTANTS.THREAD_READ_RETRY_BACKOFF_MS;
|
|
1818
|
+
await new Promise((resolve) => {
|
|
1819
|
+
const t = setTimeout(resolve, backoff);
|
|
1820
|
+
t.unref?.();
|
|
1821
|
+
});
|
|
1822
|
+
const maxWaitMs = ROOM_RESPONDER_CONSTANTS.THREAD_READ_RETRY_MAX_WAIT_MS - backoff;
|
|
1823
|
+
ctx = await withinMs(this.deps.readThreadContext(message, { maxWaitMs }).catch(() => null), ROOM_RESPONDER_CONSTANTS.THREAD_CONTEXT_WAIT_MS + maxWaitMs, null);
|
|
1824
|
+
}
|
|
1825
|
+
if (!ctx)
|
|
1826
|
+
return { readable: false, owner: null };
|
|
1827
|
+
const local = members
|
|
1828
|
+
.filter((m) => roomSessions.includes(m.sessionName))
|
|
1829
|
+
.map((m) => ({ session: m.sessionName, name: m.name, botUserId: this.deps.identities?.get(m.sessionName)?.botUserId ?? null }));
|
|
1830
|
+
const remote = (message.room?.members ?? [])
|
|
1831
|
+
.filter((m) => !isLocal(localAgentSession(m.agentSession)))
|
|
1832
|
+
.map((m) => ({ session: m.agentSession, name: m.displayName }));
|
|
1833
|
+
return { readable: true, owner: threadOwnerFromSlack(ctx, buildRoomAgentDirectory({ local, remote })) };
|
|
1834
|
+
}
|
|
1835
|
+
/**
|
|
1836
|
+
* The reply gate (specs/2026-10-03-one-responder-per-message.md §2): an
|
|
1837
|
+
* agent is about to post in a room thread where a different agent already
|
|
1838
|
+
* answered the owner's latest message. Reads this machine's huddle thread
|
|
1839
|
+
* only — no Slack call.
|
|
1840
|
+
*
|
|
1841
|
+
* @param input - Conversation (huddle id), the thread named (huddle message id or Slack thread key), and the agent
|
|
1842
|
+
* @returns The earlier answer when the post should be held, else null
|
|
1843
|
+
*/
|
|
1844
|
+
async heldReplyFor(input) {
|
|
1845
|
+
try {
|
|
1846
|
+
await this.load();
|
|
1847
|
+
const mapping = (this.store?.mappings ?? []).find((m) => m.chatChannelId === input.conversationId);
|
|
1848
|
+
const raw = input.thread?.trim();
|
|
1849
|
+
if (!mapping || !raw || !this.deps.chat.listThreadForBridge)
|
|
1850
|
+
return null;
|
|
1851
|
+
let rootId;
|
|
1852
|
+
const named = this.deps.chat.getMessageForBridge(raw);
|
|
1853
|
+
if (named && named.channelId === input.conversationId)
|
|
1854
|
+
rootId = named.threadId ?? named.id;
|
|
1855
|
+
const key = parseSlackThreadKey(raw);
|
|
1856
|
+
if (!rootId && key && key.slackChannelId === mapping.slackChannelId) {
|
|
1857
|
+
rootId = this.deps.chat.findSlackThreadRoot(input.conversationId, key.threadTs)?.id;
|
|
1858
|
+
}
|
|
1859
|
+
if (!rootId)
|
|
1860
|
+
return null;
|
|
1861
|
+
const thread = this.deps.chat.listThreadForBridge(input.conversationId, rootId).slice(-ROOM_RESPONDER_CONSTANTS.GATE_THREAD_SCAN);
|
|
1862
|
+
const local = (await this.deps.storage.getTeams()).flatMap((t) => teamChannelMembers(t));
|
|
1863
|
+
const nameFor = (s) => local.find((m) => m.sessionName === s)?.name ??
|
|
1864
|
+
this.lastRooms.get(mapping.slackChannelId)?.members.find((m) => localAgentSession(m.agentSession) === s)?.displayName;
|
|
1865
|
+
return findPriorRoomAnswer(thread, input.agentSession, nameFor);
|
|
1866
|
+
}
|
|
1867
|
+
catch {
|
|
1868
|
+
// The gate is a backstop: never block a reply because it failed.
|
|
1869
|
+
return null;
|
|
1870
|
+
}
|
|
1871
|
+
}
|
|
1567
1872
|
/**
|
|
1568
1873
|
* Hold an owner's room message that reached nobody here until an agent
|
|
1569
1874
|
* takes it — on this machine or another — or the fallback runs.
|
|
@@ -1573,13 +1878,13 @@ export class SlackTeamChannelService {
|
|
|
1573
1878
|
* @param persisted - The huddle row it was recorded as
|
|
1574
1879
|
* @param threadId - Huddle thread the replies land in
|
|
1575
1880
|
*/
|
|
1576
|
-
watchUnanswered(message, mapping, persisted, threadId, recipients = []) {
|
|
1881
|
+
watchUnanswered(message, mapping, persisted, threadId, recipients = [], heldBy = [], noteOnly = false) {
|
|
1577
1882
|
const threadTs = message.threadTs || message.ts;
|
|
1578
1883
|
const key = `${message.channelId}:${threadTs}`;
|
|
1579
1884
|
this.settleUnanswered(key);
|
|
1580
1885
|
const timer = setTimeout(() => {
|
|
1581
1886
|
void this.runUnansweredFallback(key);
|
|
1582
|
-
}, SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_FALLBACK_MS);
|
|
1887
|
+
}, noteOnly ? ROOM_RESPONDER_CONSTANTS.NOTE_ONLY_WATCH_MS : SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_FALLBACK_MS);
|
|
1583
1888
|
timer.unref?.();
|
|
1584
1889
|
this.unanswered.set(key, {
|
|
1585
1890
|
message,
|
|
@@ -1589,6 +1894,8 @@ export class SlackTeamChannelService {
|
|
|
1589
1894
|
threadId,
|
|
1590
1895
|
messageId: persisted.id,
|
|
1591
1896
|
recipients,
|
|
1897
|
+
...(heldBy.length > 0 ? { heldBy } : {}),
|
|
1898
|
+
...(noteOnly ? { noteOnly: true } : {}),
|
|
1592
1899
|
timer,
|
|
1593
1900
|
});
|
|
1594
1901
|
}
|
|
@@ -1618,6 +1925,7 @@ export class SlackTeamChannelService {
|
|
|
1618
1925
|
armRouteGuard(message, mapping, persisted, threadId) {
|
|
1619
1926
|
let done = false;
|
|
1620
1927
|
let rescued = false;
|
|
1928
|
+
const placeholders = [];
|
|
1621
1929
|
const rescue = (why, err) => {
|
|
1622
1930
|
if (done)
|
|
1623
1931
|
return;
|
|
@@ -1633,7 +1941,17 @@ export class SlackTeamChannelService {
|
|
|
1633
1941
|
handoff: message.handoffTo ?? null,
|
|
1634
1942
|
...(err !== undefined ? { error: err instanceof Error ? err.message : String(err) } : {}),
|
|
1635
1943
|
});
|
|
1636
|
-
|
|
1944
|
+
// The original routing's placeholders come down first (quietly): the
|
|
1945
|
+
// hand-off posts its own, and a placeholder of an agent that never
|
|
1946
|
+
// got the message would promise an answer nobody owes.
|
|
1947
|
+
const withdraw = async () => {
|
|
1948
|
+
for (const key of placeholders.splice(0)) {
|
|
1949
|
+
await this.deps.typing?.withdraw?.(key).catch(() => 0);
|
|
1950
|
+
}
|
|
1951
|
+
};
|
|
1952
|
+
void withdraw()
|
|
1953
|
+
.then(() => this.rescueUnroutedMessage(message, mapping, persisted, threadId))
|
|
1954
|
+
.catch((rescueErr) => this.logger.error('Could not rescue an unrouted owner room message', {
|
|
1637
1955
|
ts: message.ts,
|
|
1638
1956
|
error: rescueErr instanceof Error ? rescueErr.message : String(rescueErr),
|
|
1639
1957
|
}));
|
|
@@ -1650,6 +1968,11 @@ export class SlackTeamChannelService {
|
|
|
1650
1968
|
// After dispatch started, a throw is not an undelivered message:
|
|
1651
1969
|
// rescuing then could deliver it twice.
|
|
1652
1970
|
fail: (err) => rescue('failed', err),
|
|
1971
|
+
rescued: () => rescued,
|
|
1972
|
+
notePlaceholder: (key) => {
|
|
1973
|
+
if (!rescued)
|
|
1974
|
+
placeholders.push(key);
|
|
1975
|
+
},
|
|
1653
1976
|
dispatchStarting: () => {
|
|
1654
1977
|
if (rescued)
|
|
1655
1978
|
return false;
|
|
@@ -1762,7 +2085,43 @@ export class SlackTeamChannelService {
|
|
|
1762
2085
|
return;
|
|
1763
2086
|
this.unanswered.delete(key);
|
|
1764
2087
|
try {
|
|
2088
|
+
// The chosen responder holds it on its queue (it was busy): it will
|
|
2089
|
+
// answer when idle; handing it to the lead too means two answers.
|
|
2090
|
+
if ((pending.heldBy ?? []).length > 0) {
|
|
2091
|
+
this.logger.info('Unanswered room message: the responder holds it on its queue — no hand-off', {
|
|
2092
|
+
slackChannel: pending.slackChannelId,
|
|
2093
|
+
ts: pending.message.ts,
|
|
2094
|
+
heldBy: pending.heldBy,
|
|
2095
|
+
});
|
|
2096
|
+
return;
|
|
2097
|
+
}
|
|
2098
|
+
// An agent — here or on another machine — already replied in the
|
|
2099
|
+
// thread, or shows "working on it": nothing to hand over.
|
|
2100
|
+
const after = this.deps.slackRepliesAfter
|
|
2101
|
+
? await withinMs(this.deps.slackRepliesAfter(pending.slackChannelId, pending.threadTs, pending.message.ts).catch(() => null), ROOM_RESPONDER_CONSTANTS.THREAD_CONTEXT_WAIT_MS, null)
|
|
2102
|
+
: null;
|
|
2103
|
+
const harnessNotes = [SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_UNANSWERED_NOTE, SLACK_TEAM_CHANNEL_CONSTANTS.ROOM_ROUTE_STALLED_NOTE];
|
|
2104
|
+
if (after?.some((m) => m.isBot && !harnessNotes.includes(m.text.trim()))) {
|
|
2105
|
+
this.logger.info('Unanswered room message: an agent has replied or is working on it in Slack — no hand-off', {
|
|
2106
|
+
slackChannel: pending.slackChannelId,
|
|
2107
|
+
ts: pending.message.ts,
|
|
2108
|
+
});
|
|
2109
|
+
return;
|
|
2110
|
+
}
|
|
1765
2111
|
const mapping = this.findBySlackChannelId(pending.slackChannelId);
|
|
2112
|
+
// Not this machine's to hand over (another may be the watcher), or the
|
|
2113
|
+
// thread could not be read just now: tell the owner rather than hand
|
|
2114
|
+
// it over blind and risk a second answer.
|
|
2115
|
+
const unreadable = !!this.deps.slackRepliesAfter && after === null && !!pending.timer;
|
|
2116
|
+
if (pending.noteOnly || unreadable) {
|
|
2117
|
+
this.logger.warn('Unanswered room message: telling the owner instead of handing it over', {
|
|
2118
|
+
slackChannel: pending.slackChannelId,
|
|
2119
|
+
ts: pending.message.ts,
|
|
2120
|
+
reason: pending.noteOnly ? 'not this machine\'s hand-off' : 'Slack thread unreadable',
|
|
2121
|
+
});
|
|
2122
|
+
await this.postUnansweredNote(pending, mapping);
|
|
2123
|
+
return;
|
|
2124
|
+
}
|
|
1766
2125
|
const lead = mapping ? await this.localRoomLead(mapping) : null;
|
|
1767
2126
|
if (mapping && lead && pending.recipients.includes(lead)) {
|
|
1768
2127
|
// The lead already holds it (delivered as optional): handing it over
|
|
@@ -1877,7 +2236,7 @@ export class SlackTeamChannelService {
|
|
|
1877
2236
|
* @param remoteAgent - Authoring agent, when an agent wrote it
|
|
1878
2237
|
* @returns The ticket the message belongs to, or null
|
|
1879
2238
|
*/
|
|
1880
|
-
async intakeTicket(message, mapping, mentions, planned, handoffTo, remoteAgent) {
|
|
2239
|
+
async intakeTicket(message, mapping, mentions, planned, handoffTo, remoteAgent, delivered) {
|
|
1881
2240
|
try {
|
|
1882
2241
|
const intake = getTicketIntakeService();
|
|
1883
2242
|
if (!intake || remoteAgent)
|
|
@@ -1901,7 +2260,16 @@ export class SlackTeamChannelService {
|
|
|
1901
2260
|
userName: message.user?.realName || message.user?.name,
|
|
1902
2261
|
hasFiles: message.hasFiles,
|
|
1903
2262
|
ownerUserId: this.deps.getOwnerUserId?.() ?? null,
|
|
1904
|
-
}, 'team-channel', { ...(targetAgent ? { targetAgent } : {}), ...(postAs ? { receiptPostAs: postAs } : {}) })
|
|
2263
|
+
}, 'team-channel', { ...(targetAgent ? { targetAgent } : {}), ...(postAs ? { receiptPostAs: postAs } : {}) }), undefined,
|
|
2264
|
+
// Filed after the message went out without its marker: link the
|
|
2265
|
+
// ticket to the copy that was dispatched (crewly#1015 follow-up L2).
|
|
2266
|
+
delivered
|
|
2267
|
+
? (late) => {
|
|
2268
|
+
const linked = linkLateTicket(late, delivered);
|
|
2269
|
+
if (linked)
|
|
2270
|
+
this.logger.info('Late ticket linked to the delivered message', { ticketId: linked.id, messageId: delivered.id });
|
|
2271
|
+
}
|
|
2272
|
+
: undefined);
|
|
1905
2273
|
return ticketOfOutcome(outcome);
|
|
1906
2274
|
}
|
|
1907
2275
|
catch (err) {
|
|
@@ -3169,6 +3537,28 @@ export class SlackTeamChannelService {
|
|
|
3169
3537
|
}
|
|
3170
3538
|
}
|
|
3171
3539
|
/** Type guard for persisted mapping rows. */
|
|
3540
|
+
/**
|
|
3541
|
+
* A promise's value, or `fallback` when it takes longer than `ms`.
|
|
3542
|
+
*
|
|
3543
|
+
* @param promise - What to wait for
|
|
3544
|
+
* @param ms - Longest wait
|
|
3545
|
+
* @param fallback - Value on timeout
|
|
3546
|
+
* @returns The value, or the fallback
|
|
3547
|
+
*/
|
|
3548
|
+
async function withinMs(promise, ms, fallback) {
|
|
3549
|
+
let timer;
|
|
3550
|
+
const timeout = new Promise((resolve) => {
|
|
3551
|
+
timer = setTimeout(() => resolve(fallback), ms);
|
|
3552
|
+
timer.unref?.();
|
|
3553
|
+
});
|
|
3554
|
+
try {
|
|
3555
|
+
return await Promise.race([promise, timeout]);
|
|
3556
|
+
}
|
|
3557
|
+
finally {
|
|
3558
|
+
if (timer)
|
|
3559
|
+
clearTimeout(timer);
|
|
3560
|
+
}
|
|
3561
|
+
}
|
|
3172
3562
|
function isMapping(value) {
|
|
3173
3563
|
const v = value;
|
|
3174
3564
|
return (!!v &&
|