crewly 1.6.1 → 1.6.2
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/roles/orchestrator/prompt.md +16 -0
- package/config/skills/agent/core/get-my-active-work/SKILL.md +101 -0
- package/config/skills/agent/core/get-my-active-work/execute.sh +122 -0
- package/config/skills/agent/core/record-learning/SKILL.md +29 -0
- package/config/skills/agent/core/reply-channel/SKILL.md +41 -0
- package/config/skills/agent/core/reply-channel/execute.sh +165 -0
- package/config/skills/agent/core/reply-channel/execute.test.sh +148 -0
- package/config/skills/agent/remote-browser/execute.sh +296 -14
- package/config/skills/agent/remote-browser/execute.test.sh +482 -0
- package/config/skills/orchestrator/send-message/SKILL.md +30 -7
- package/config/skills/orchestrator/team-health-scan/SKILL.md +98 -0
- package/config/skills/orchestrator/team-health-scan/execute.sh +44 -0
- package/config/skills/registry.json +62 -1
- package/config/sops/developer/git-workflow.md +38 -3
- package/dist/backend/backend/src/constants.d.ts +69 -1
- package/dist/backend/backend/src/constants.d.ts.map +1 -1
- package/dist/backend/backend/src/constants.js +69 -2
- package/dist/backend/backend/src/constants.js.map +1 -1
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts +53 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.js +92 -0
- package/dist/backend/backend/src/controllers/active-work/active-work.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js +18 -1
- package/dist/backend/backend/src/controllers/agent-stream/agent-stream.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts +68 -0
- package/dist/backend/backend/src/controllers/browser/browser.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.controller.js +233 -5
- package/dist/backend/backend/src/controllers/browser/browser.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.js +10 -1
- package/dist/backend/backend/src/controllers/browser/browser.routes.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 +8 -3
- 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 +132 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js +401 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts +29 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js +39 -0
- package/dist/backend/backend/src/controllers/chat-v2/chat-v2.routes.js.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.d.ts +8 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.js +8 -0
- package/dist/backend/backend/src/controllers/chat-v2/index.js.map +1 -0
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts +13 -13
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js +74 -234
- package/dist/backend/backend/src/controllers/onboarding/onboarding.routes.js.map +1 -1
- package/dist/backend/backend/src/controllers/request/request.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/request/request.controller.js +4 -6
- package/dist/backend/backend/src/controllers/request/request.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts +43 -0
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js +200 -72
- package/dist/backend/backend/src/controllers/task-management/tasks.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/team/team.controller.js +46 -0
- package/dist/backend/backend/src/controllers/team/team.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts +59 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.js +127 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.controller.js.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts +13 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.d.ts.map +1 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.js +20 -0
- package/dist/backend/backend/src/controllers/team-health/team-health.routes.js.map +1 -0
- 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 +233 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.d.ts.map +1 -1
- package/dist/backend/backend/src/routes/api.routes.js +40 -6
- package/dist/backend/backend/src/routes/api.routes.js.map +1 -1
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts +498 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.js +759 -0
- package/dist/backend/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts +25 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/agent-registration.service.js +193 -57
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts +9 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js +35 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/model-manager.js.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts +8 -2
- package/dist/backend/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/types.js +1 -0
- package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.js +2 -1
- package/dist/backend/backend/src/services/agent/tmux-command.service.js.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/agent/tmux.service.js +2 -1
- package/dist/backend/backend/src/services/agent/tmux.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts +148 -3
- package/dist/backend/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js +241 -2
- package/dist/backend/backend/src/services/ai/prompt-builder.service.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js +13 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js +26 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -1
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
- package/dist/backend/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
- package/dist/backend/backend/src/services/browser/browser-bridge.service.d.ts +161 -0
- 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 +382 -2
- 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 +105 -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 +232 -13
- package/dist/backend/backend/src/services/browser/browser-proxy.service.js.map +1 -1
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts +178 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js +254 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.dispatcher.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts +134 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js +232 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.mention-resolver.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts +25 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js +23 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.realtime-holder.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts +254 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js +467 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts +43 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js +54 -0
- package/dist/backend/backend/src/services/chat-v2/chat-v2.team-membership.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/config.d.ts +100 -0
- package/dist/backend/backend/src/services/chat-v2/config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/config.js +174 -0
- package/dist/backend/backend/src/services/chat-v2/config.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/index.d.ts +11 -0
- package/dist/backend/backend/src/services/chat-v2/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/index.js +12 -0
- package/dist/backend/backend/src/services/chat-v2/index.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts +114 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js +194 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts +100 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js +351 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts +132 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js +281 -0
- package/dist/backend/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts +295 -0
- package/dist/backend/backend/src/services/chat-v2/types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/chat-v2/types.js +61 -0
- package/dist/backend/backend/src/services/chat-v2/types.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts +113 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js +179 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts +131 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js +227 -0
- package/dist/backend/backend/src/services/cloud/cloud-event-forwarder.service.js.map +1 -0
- package/dist/backend/backend/src/services/core/config.service.js +3 -3
- package/dist/backend/backend/src/services/core/config.service.js.map +1 -1
- package/dist/backend/backend/src/services/core/storage.service.d.ts +7 -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 +15 -0
- package/dist/backend/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts +69 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js +118 -0
- package/dist/backend/backend/src/services/event-bus/event-bus.service.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +275 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +736 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js +18 -2
- package/dist/backend/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js +123 -29
- package/dist/backend/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts +159 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.js +304 -0
- package/dist/backend/backend/src/services/knowledge/learnings-index.service.js.map +1 -0
- package/dist/backend/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js +24 -4
- package/dist/backend/backend/src/services/knowledge/vector-store.service.js.map +1 -1
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts +174 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js +375 -0
- package/dist/backend/backend/src/services/memory/auto-learning.subscriber.js.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts +97 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.d.ts.map +1 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.js +209 -0
- package/dist/backend/backend/src/services/memory/learning-format.validator.js.map +1 -0
- package/dist/backend/backend/src/services/memory/vector-store.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/memory/vector-store.service.js +19 -4
- package/dist/backend/backend/src/services/memory/vector-store.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts +16 -5
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js +32 -5
- package/dist/backend/backend/src/services/onboarding/onboarding-provision.service.js.map +1 -1
- package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts +157 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.js +229 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.service.js.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts +141 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.js +18 -0
- package/dist/backend/backend/src/services/onboarding/onboarding.types.js.map +1 -0
- package/dist/backend/backend/src/services/pr-review/pr-review.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js +1 -1
- package/dist/backend/backend/src/services/pr-review/pr-review.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js +17 -1
- package/dist/backend/backend/src/services/slack/cross-machine-message.service.js.map +1 -1
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.d.ts +39 -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 +158 -26
- package/dist/backend/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +248 -6
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js +531 -51
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/team-health/index.d.ts +16 -0
- package/dist/backend/backend/src/services/team-health/index.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/index.js +16 -0
- package/dist/backend/backend/src/services/team-health/index.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts +52 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js +161 -0
- package/dist/backend/backend/src/services/team-health/live-team-health-data-provider.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts +53 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js +88 -0
- package/dist/backend/backend/src/services/team-health/lost-dispatch-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts +44 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js +83 -0
- package/dist/backend/backend/src/services/team-health/stale-trigger-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts +92 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.js +328 -0
- package/dist/backend/backend/src/services/team-health/team-health-alert-router.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.d.ts +41 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.js +213 -0
- package/dist/backend/backend/src/services/team-health/team-health-config.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts +46 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.js +347 -0
- package/dist/backend/backend/src/services/team-health/team-health-detector.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.d.ts +154 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.js +94 -0
- package/dist/backend/backend/src/services/team-health/team-health-types.js.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts +111 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js +226 -0
- package/dist/backend/backend/src/services/team-health/team-health-watchdog.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts +148 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js +545 -0
- package/dist/backend/backend/src/services/v3/mission-reminder.service.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts +499 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +1105 -0
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts +22 -0
- package/dist/backend/backend/src/services/v3/request.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request.service.js +71 -0
- package/dist/backend/backend/src/services/v3/request.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts +1 -0
- package/dist/backend/backend/src/services/v3/v3-data.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/v3-data.service.js +22 -6
- package/dist/backend/backend/src/services/v3/v3-data.service.js.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts +19 -1
- package/dist/backend/backend/src/types/event-bus.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/event-bus.types.js +43 -0
- package/dist/backend/backend/src/types/event-bus.types.js.map +1 -1
- package/dist/backend/backend/src/types/index.d.ts +22 -1
- package/dist/backend/backend/src/types/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/index.js.map +1 -1
- package/dist/backend/backend/src/types/review-reason.types.d.ts +63 -0
- package/dist/backend/backend/src/types/review-reason.types.d.ts.map +1 -0
- package/dist/backend/backend/src/types/review-reason.types.js +50 -0
- package/dist/backend/backend/src/types/review-reason.types.js.map +1 -0
- package/dist/backend/backend/src/types/slack.types.d.ts +4 -1
- 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/backend/src/types/v2/mission.types.d.ts +18 -0
- package/dist/backend/backend/src/types/v2/mission.types.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/mission.types.js +1 -0
- package/dist/backend/backend/src/types/v2/mission.types.js.map +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 +25 -1
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/backend/src/utils/team.utils.d.ts +38 -0
- package/dist/backend/backend/src/utils/team.utils.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/team.utils.js +45 -0
- package/dist/backend/backend/src/utils/team.utils.js.map +1 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts +195 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.d.ts.map +1 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.js +401 -0
- package/dist/backend/backend/src/websocket/chat-v2.gateway.js.map +1 -0
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts +37 -2
- package/dist/backend/backend/src/websocket/terminal.gateway.d.ts.map +1 -1
- package/dist/backend/backend/src/websocket/terminal.gateway.js +106 -5
- package/dist/backend/backend/src/websocket/terminal.gateway.js.map +1 -1
- package/dist/cli/backend/src/constants.d.ts +69 -1
- package/dist/cli/backend/src/constants.d.ts.map +1 -1
- package/dist/cli/backend/src/constants.js +69 -2
- package/dist/cli/backend/src/constants.js.map +1 -1
- package/dist/cli/backend/src/services/core/config.service.js +3 -3
- package/dist/cli/backend/src/services/core/config.service.js.map +1 -1
- package/dist/cli/backend/src/services/core/storage.service.d.ts +7 -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 +15 -0
- package/dist/cli/backend/src/services/core/storage.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js +18 -2
- package/dist/cli/backend/src/services/knowledge/fts5-index.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts +49 -13
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js +123 -29
- package/dist/cli/backend/src/services/knowledge/knowledge-search.service.js.map +1 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js +24 -4
- package/dist/cli/backend/src/services/knowledge/vector-store.service.js.map +1 -1
- package/dist/cli/backend/src/types/index.d.ts +22 -1
- package/dist/cli/backend/src/types/index.d.ts.map +1 -1
- package/dist/cli/backend/src/types/index.js.map +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 +25 -1
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/frontend/dist/assets/{index-70356616.js → index-7a4e7df5.js} +328 -326
- package/frontend/dist/assets/index-b7e59b2b.css +33 -0
- package/frontend/dist/index.html +2 -2
- package/package.json +2 -1
- package/config/skills/orchestrator/recall/SKILL.md +0 -47
- package/config/skills/orchestrator/recall/execute.sh +0 -13
- package/config/skills/orchestrator/record-learning/SKILL.md +0 -47
- package/config/skills/orchestrator/record-learning/execute.sh +0 -13
- package/config/skills/orchestrator/remember/SKILL.md +0 -55
- package/config/skills/orchestrator/remember/execute.sh +0 -15
- package/frontend/dist/assets/index-6aaa0630.css +0 -33
|
@@ -0,0 +1,736 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EventToWorkItemBridge
|
|
3
|
+
*
|
|
4
|
+
* Subscribes to autonomy-relevant events on the EventBus and creates the
|
|
5
|
+
* appropriate WorkItem(s) so the autonomy chain advances without an explicit
|
|
6
|
+
* Trigger Engine (Arch Veto V5 — `.crewly/tmp/autonomy-doc-review-arch-2026-04-26.md`).
|
|
7
|
+
*
|
|
8
|
+
* Subscribed events (all `CRITICAL_EVENT_TYPES`):
|
|
9
|
+
* - `task:done_by_worker` → verification WI for TL
|
|
10
|
+
* - `task:rejected` → retry WI (or escalation review WI at retry cap)
|
|
11
|
+
* - `task:blocked` → review WI (`reviewReason: 'task_blocked'`)
|
|
12
|
+
* - `team:all_tasks_done` → emit `mission:review_due` (if mission active)
|
|
13
|
+
* - `mission:review_due` → review WI mirroring REVIEW-1's id pattern
|
|
14
|
+
* - `mission:stale` → review WI (`reviewReason: 'no_active_work'`)
|
|
15
|
+
* - `mission:replanned` → review WI (`reviewReason: 'scheduled_review'`)
|
|
16
|
+
*
|
|
17
|
+
* Idempotency contract (Arch Veto V1):
|
|
18
|
+
* Every auto-created WorkItem carries a deterministic `id` and a documentary
|
|
19
|
+
* `metadata.idempotencyKey === id`. The real dedup gate is
|
|
20
|
+
* `TaskPoolService.addToPool` which short-circuits on duplicate id. So a
|
|
21
|
+
* replayed event fires the bridge handler again, the bridge re-builds the
|
|
22
|
+
* same id, and addToPool's existing dedup is the safety net — no separate
|
|
23
|
+
* idempotency store needed.
|
|
24
|
+
*
|
|
25
|
+
* Retry cap (Arch Veto V2):
|
|
26
|
+
* `task:rejected` increments `retryCount` on the new retry WI. At
|
|
27
|
+
* `sourceWI.retryCount >= DEFAULT_MAX_RETRIES` (3) the bridge escalates to
|
|
28
|
+
* a review WI for the TL with `reviewReason: 'max_retries_exceeded'` and
|
|
29
|
+
* does NOT enqueue another retry. Retry WIs target the original worker;
|
|
30
|
+
* escalation WIs target the team lead resolved via `pickTeamLead()`.
|
|
31
|
+
*
|
|
32
|
+
* Status mutations (Arch Veto V3):
|
|
33
|
+
* The bridge does NOT mutate status directly — every state change goes
|
|
34
|
+
* through `TaskPoolService.transitionStatus()` (the canonical guarded
|
|
35
|
+
* entrypoint TRANS-1 shipped). Search guard (see PR body for full grep
|
|
36
|
+
* command — kept out of the doc comment so the grep doesn't false-positive
|
|
37
|
+
* on its own description).
|
|
38
|
+
*
|
|
39
|
+
* Team-lead resolution (Arch Veto V4):
|
|
40
|
+
* `pickTeamLead()` may return `null`. The bridge throws
|
|
41
|
+
* {@link BridgeTeamLeadResolutionError} on null — no silent fallback to
|
|
42
|
+
* the executor session.
|
|
43
|
+
*
|
|
44
|
+
* No parallel scheduler entity (Arch Veto V5):
|
|
45
|
+
* The bridge is a pure event listener. It does NOT subclass anything called
|
|
46
|
+
* "Trigger", does NOT create one, does NOT register with a TriggerEngine.
|
|
47
|
+
* See PR body for the grep guard command.
|
|
48
|
+
*
|
|
49
|
+
* Cron-recursion block (Arch Vetos V6 / V9):
|
|
50
|
+
* When the bridge derives a new WI from a source WI whose
|
|
51
|
+
* `metadata.triggerSource === 'cron'`, the new WI's triggerSource is
|
|
52
|
+
* demoted to `'event'` (cron sessions cannot beget more cron jobs). Any
|
|
53
|
+
* call to {@link assertNoCronFromCron} from a cron-source WI throws
|
|
54
|
+
* {@link CronRecursionError}. The retry path tags `triggerSource: 'event'`
|
|
55
|
+
* directly so demotion is the default rather than the exceptional path.
|
|
56
|
+
*
|
|
57
|
+
* @module services/event-bus/event-to-workitem-bridge
|
|
58
|
+
*/
|
|
59
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
60
|
+
import { StorageService } from '../core/storage.service.js';
|
|
61
|
+
import { TaskPoolService } from '../task-pool/task-pool.service.js';
|
|
62
|
+
import { pickTeamLead } from '../../utils/team.utils.js';
|
|
63
|
+
import { ORCHESTRATOR_SESSION_NAME } from '../../constants.js';
|
|
64
|
+
import { formatError } from '../../utils/format-error.js';
|
|
65
|
+
import { DEFAULT_MAX_RETRIES, } from '../../types/v2/work-item.types.js';
|
|
66
|
+
import { isReviewReason, } from '../../types/review-reason.types.js';
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Constants
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
70
|
+
/**
|
|
71
|
+
* Event types the bridge subscribes to. Listed in the same order as the
|
|
72
|
+
* dispatch table to make the start() registration easy to audit.
|
|
73
|
+
*/
|
|
74
|
+
export const BRIDGE_SUBSCRIBED_EVENTS = [
|
|
75
|
+
'task:done_by_worker',
|
|
76
|
+
'task:rejected',
|
|
77
|
+
'task:blocked',
|
|
78
|
+
'team:all_tasks_done',
|
|
79
|
+
'mission:review_due',
|
|
80
|
+
'mission:stale',
|
|
81
|
+
'mission:replanned',
|
|
82
|
+
];
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Errors
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Thrown when a cron-sourced WorkItem attempts to create another cron-recurring
|
|
88
|
+
* WorkItem. Encoded as a named error so tests + observability can distinguish
|
|
89
|
+
* the recursion guard from generic bridge errors.
|
|
90
|
+
*/
|
|
91
|
+
export class CronRecursionError extends Error {
|
|
92
|
+
constructor(message) {
|
|
93
|
+
super(message);
|
|
94
|
+
this.name = 'CronRecursionError';
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Thrown when the bridge cannot resolve a Team Lead session for a team that
|
|
99
|
+
* needs an escalation/verification WorkItem. Per Arch Veto V4 the bridge
|
|
100
|
+
* MUST NOT silently fall back to the executor session — losing visibility
|
|
101
|
+
* on a stalled escalation is worse than failing loudly.
|
|
102
|
+
*/
|
|
103
|
+
export class BridgeTeamLeadResolutionError extends Error {
|
|
104
|
+
constructor(message) {
|
|
105
|
+
super(message);
|
|
106
|
+
this.name = 'BridgeTeamLeadResolutionError';
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
// ---------------------------------------------------------------------------
|
|
110
|
+
// Helpers
|
|
111
|
+
// ---------------------------------------------------------------------------
|
|
112
|
+
/** Day-bucket cycle key used by mission cadence handlers. */
|
|
113
|
+
function todayCycleKey(now = new Date()) {
|
|
114
|
+
return now.toISOString().slice(0, 10);
|
|
115
|
+
}
|
|
116
|
+
/**
|
|
117
|
+
* V6 / V9 guard: throw if a cron-sourced WI tries to create another cron job.
|
|
118
|
+
*
|
|
119
|
+
* @param sourceWI - WI being reacted to
|
|
120
|
+
* @param attemptedAction - Short label for the action that would create cron
|
|
121
|
+
* @throws CronRecursionError when sourceWI.metadata.triggerSource === 'cron'
|
|
122
|
+
*/
|
|
123
|
+
export function assertNoCronFromCron(sourceWI, attemptedAction) {
|
|
124
|
+
const triggerSource = sourceWI.metadata?.['triggerSource'];
|
|
125
|
+
if (triggerSource === 'cron') {
|
|
126
|
+
throw new CronRecursionError(`Refusing ${attemptedAction}: source WorkItem ${sourceWI.id} has triggerSource='cron'. ` +
|
|
127
|
+
`Cron-fired sessions cannot create new cron / recurring schedules.`);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
/**
|
|
131
|
+
* Derive the trigger source for a downstream WI. Defaults to `'event'`. If the
|
|
132
|
+
* source WI was cron-fired, the downstream WI is demoted to `'event'` (V6 / V9).
|
|
133
|
+
*
|
|
134
|
+
* @param sourceWI - Optional source WorkItem
|
|
135
|
+
* @returns The triggerSource value to tag on the new WorkItem
|
|
136
|
+
*/
|
|
137
|
+
function inheritedTriggerSource(sourceWI) {
|
|
138
|
+
const tag = sourceWI?.metadata?.['triggerSource'];
|
|
139
|
+
if (tag === 'cron')
|
|
140
|
+
return 'event'; // demote
|
|
141
|
+
if (tag === 'manual')
|
|
142
|
+
return 'manual';
|
|
143
|
+
return 'event';
|
|
144
|
+
}
|
|
145
|
+
/**
|
|
146
|
+
* EventToWorkItemBridge — the BRIDGE-1 deliverable.
|
|
147
|
+
*
|
|
148
|
+
* Wired in `backend/src/index.ts` via {@link EventToWorkItemBridge.boot}; tests
|
|
149
|
+
* construct via the {@link EventToWorkItemBridge} constructor directly with
|
|
150
|
+
* fake dependencies.
|
|
151
|
+
*
|
|
152
|
+
* @example
|
|
153
|
+
* ```typescript
|
|
154
|
+
* const bridge = EventToWorkItemBridge.boot(eventBus);
|
|
155
|
+
* bridge.start();
|
|
156
|
+
* // … later, on shutdown:
|
|
157
|
+
* bridge.stop();
|
|
158
|
+
* ```
|
|
159
|
+
*/
|
|
160
|
+
export class EventToWorkItemBridge {
|
|
161
|
+
eventBus;
|
|
162
|
+
taskPool;
|
|
163
|
+
loadMission;
|
|
164
|
+
loadTeam;
|
|
165
|
+
logger;
|
|
166
|
+
unsubscribers = [];
|
|
167
|
+
started = false;
|
|
168
|
+
/**
|
|
169
|
+
* In-flight async handler invocations. Test code calls
|
|
170
|
+
* {@link flushPending} to await every in-flight dispatch deterministically.
|
|
171
|
+
* Production code never reads this — handlers continue running off the
|
|
172
|
+
* event loop with their own try/catch.
|
|
173
|
+
*/
|
|
174
|
+
pendingDispatches = new Set();
|
|
175
|
+
constructor(deps) {
|
|
176
|
+
this.eventBus = deps.eventBus;
|
|
177
|
+
this.taskPool = deps.taskPool;
|
|
178
|
+
this.loadMission = deps.loadMission;
|
|
179
|
+
this.loadTeam = deps.loadTeam;
|
|
180
|
+
this.logger =
|
|
181
|
+
deps.logger ?? LoggerService.getInstance().createComponentLogger('EventToWorkItemBridge');
|
|
182
|
+
}
|
|
183
|
+
/**
|
|
184
|
+
* Production wiring helper — constructs a bridge from singletons.
|
|
185
|
+
*
|
|
186
|
+
* Tests should use the constructor directly with fake `loadMission` /
|
|
187
|
+
* `loadTeam` rather than reaching for `boot`.
|
|
188
|
+
*
|
|
189
|
+
* @param eventBus - The live event bus
|
|
190
|
+
* @returns A bridge instance ready to `start()`
|
|
191
|
+
*/
|
|
192
|
+
static boot(eventBus) {
|
|
193
|
+
const taskPool = TaskPoolService.getInstance();
|
|
194
|
+
const storage = StorageService.getInstance();
|
|
195
|
+
return new EventToWorkItemBridge({
|
|
196
|
+
eventBus,
|
|
197
|
+
taskPool,
|
|
198
|
+
loadMission: async (missionId) => {
|
|
199
|
+
// Storage exposes mission read by id; fall back to null on miss/error
|
|
200
|
+
try {
|
|
201
|
+
const mission = await storage.getMissionById?.(missionId);
|
|
202
|
+
return mission ?? null;
|
|
203
|
+
}
|
|
204
|
+
catch {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
},
|
|
208
|
+
loadTeam: async (teamId) => {
|
|
209
|
+
const teams = await storage.getTeams();
|
|
210
|
+
return teams.find((t) => t.id === teamId) ?? null;
|
|
211
|
+
},
|
|
212
|
+
});
|
|
213
|
+
}
|
|
214
|
+
/**
|
|
215
|
+
* Subscribe to all bridge events. Idempotent — calling twice is a no-op.
|
|
216
|
+
*
|
|
217
|
+
* Each handler is wrapped so that a thrown error inside a single event
|
|
218
|
+
* dispatch is logged but does NOT propagate to `EventBus.publish()`. The
|
|
219
|
+
* EventBus itself also isolates handler errors (see
|
|
220
|
+
* `dispatchInProcess`); the inner try/catch belt-and-braces against the
|
|
221
|
+
* particular case where a handler returns a non-Promise that subsequently
|
|
222
|
+
* throws on access (rare but observed in jest fake-timer setups).
|
|
223
|
+
*/
|
|
224
|
+
start() {
|
|
225
|
+
if (this.started)
|
|
226
|
+
return;
|
|
227
|
+
this.started = true;
|
|
228
|
+
this.unsubscribers.push(this.eventBus.onInProcess('task:done_by_worker', (e) => this.safeDispatch('task:done_by_worker', e, this.handleTaskDoneByWorker)), this.eventBus.onInProcess('task:rejected', (e) => this.safeDispatch('task:rejected', e, this.handleTaskRejected)), this.eventBus.onInProcess('task:blocked', (e) => this.safeDispatch('task:blocked', e, this.handleTaskBlocked)), this.eventBus.onInProcess('team:all_tasks_done', (e) => this.safeDispatch('team:all_tasks_done', e, this.handleTeamAllTasksDone)), this.eventBus.onInProcess('mission:review_due', (e) => this.safeDispatch('mission:review_due', e, this.handleMissionReviewDue)), this.eventBus.onInProcess('mission:stale', (e) => this.safeDispatch('mission:stale', e, this.handleMissionStale)), this.eventBus.onInProcess('mission:replanned', (e) => this.safeDispatch('mission:replanned', e, this.handleMissionReplanned)));
|
|
229
|
+
this.logger.info('EventToWorkItemBridge subscribed', {
|
|
230
|
+
eventTypes: BRIDGE_SUBSCRIBED_EVENTS,
|
|
231
|
+
});
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* Wait for every in-flight handler dispatch to settle. Test affordance —
|
|
235
|
+
* production code does not need this; the EventBus + safeDispatch already
|
|
236
|
+
* isolate handler errors. Tests use it to assert deterministically that
|
|
237
|
+
* `addToPool` has been called by the time the expectation runs, even
|
|
238
|
+
* though `EventBus.publish` is synchronous and bridge handlers are async.
|
|
239
|
+
*
|
|
240
|
+
* Calling more than once is safe — pending dispatches are removed as they
|
|
241
|
+
* settle, so a second call is effectively a no-op when nothing is in flight.
|
|
242
|
+
*/
|
|
243
|
+
async flushPending() {
|
|
244
|
+
while (this.pendingDispatches.size > 0) {
|
|
245
|
+
// Snapshot the current pending set; new entries added by handlers (e.g.
|
|
246
|
+
// bridge re-emitting `mission:review_due` from `team:all_tasks_done`)
|
|
247
|
+
// will appear in the next loop iteration.
|
|
248
|
+
const inFlight = Array.from(this.pendingDispatches);
|
|
249
|
+
await Promise.allSettled(inFlight);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
/**
|
|
253
|
+
* Detach every subscription. Safe to call multiple times.
|
|
254
|
+
*/
|
|
255
|
+
stop() {
|
|
256
|
+
for (const unsubscribe of this.unsubscribers) {
|
|
257
|
+
try {
|
|
258
|
+
unsubscribe();
|
|
259
|
+
}
|
|
260
|
+
catch (err) {
|
|
261
|
+
this.logger.warn('Bridge unsubscribe threw', { error: formatError(err) });
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
this.unsubscribers = [];
|
|
265
|
+
this.started = false;
|
|
266
|
+
this.logger.info('EventToWorkItemBridge stopped');
|
|
267
|
+
}
|
|
268
|
+
// -------------------------------------------------------------------------
|
|
269
|
+
// Event handlers — each returns a Promise so onInProcess can attach .catch()
|
|
270
|
+
// -------------------------------------------------------------------------
|
|
271
|
+
/**
|
|
272
|
+
* `task:done_by_worker` → create a verification WI for the team lead.
|
|
273
|
+
*
|
|
274
|
+
* Idempotency: `${sourceWI.id}:verify:${sourceWI.id}` (one verify WI per task done).
|
|
275
|
+
* V4: throws if no TL resolvable.
|
|
276
|
+
*/
|
|
277
|
+
handleTaskDoneByWorker = async (event) => {
|
|
278
|
+
const sourceWI = await this.resolveSourceWorkItem(event);
|
|
279
|
+
if (!sourceWI) {
|
|
280
|
+
this.logger.warn('task:done_by_worker missing source WorkItem', {
|
|
281
|
+
eventId: event.id,
|
|
282
|
+
workItemId: event.workItemId,
|
|
283
|
+
taskId: event.taskId,
|
|
284
|
+
});
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
const target = await this.resolveTeamLeadSession(sourceWI);
|
|
288
|
+
const verifyId = `${sourceWI.id}:verify:${sourceWI.id}`;
|
|
289
|
+
const verifyWI = this.buildAutoWorkItem({
|
|
290
|
+
id: verifyId,
|
|
291
|
+
type: 'review',
|
|
292
|
+
owner: 'team_lead',
|
|
293
|
+
target,
|
|
294
|
+
title: `Verify: ${sourceWI.title}`,
|
|
295
|
+
description: `Worker reported done on ${sourceWI.id}. Verify the deliverable.`,
|
|
296
|
+
sourceWI,
|
|
297
|
+
missionId: sourceWI.missionId,
|
|
298
|
+
requestId: sourceWI.requestId,
|
|
299
|
+
retryCount: 0,
|
|
300
|
+
extraMetadata: {
|
|
301
|
+
idempotencyKey: verifyId, // V1 (per-handler)
|
|
302
|
+
sourceWorkItemId: sourceWI.id,
|
|
303
|
+
verifyOf: sourceWI.id,
|
|
304
|
+
},
|
|
305
|
+
});
|
|
306
|
+
await this.taskPool.addToPool(verifyWI);
|
|
307
|
+
this.logger.info('Verification WorkItem created', {
|
|
308
|
+
sourceWorkItemId: sourceWI.id,
|
|
309
|
+
verifyWorkItemId: verifyId,
|
|
310
|
+
target,
|
|
311
|
+
});
|
|
312
|
+
};
|
|
313
|
+
/**
|
|
314
|
+
* `task:rejected` → retry WI (cap at DEFAULT_MAX_RETRIES) or escalation review WI.
|
|
315
|
+
*
|
|
316
|
+
* - retryCount < cap → retry WI assigned to original worker, retryCount + 1
|
|
317
|
+
* - retryCount >= cap → review WI for TL with reviewReason='max_retries_exceeded'
|
|
318
|
+
*
|
|
319
|
+
* Idempotency:
|
|
320
|
+
* - retry path: `${sourceWI.id}:retry:${retryAttempt}`
|
|
321
|
+
* - escalation: `${sourceWI.id}:review:max_retries`
|
|
322
|
+
*/
|
|
323
|
+
handleTaskRejected = async (event) => {
|
|
324
|
+
const sourceWI = await this.resolveSourceWorkItem(event);
|
|
325
|
+
if (!sourceWI) {
|
|
326
|
+
this.logger.warn('task:rejected missing source WorkItem', { eventId: event.id });
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
const cap = sourceWI.maxRetries > 0 ? sourceWI.maxRetries : DEFAULT_MAX_RETRIES;
|
|
330
|
+
if (sourceWI.retryCount >= cap) {
|
|
331
|
+
// V2 escalation path
|
|
332
|
+
const escalationId = `${sourceWI.id}:review:max_retries`;
|
|
333
|
+
const target = await this.resolveTeamLeadSession(sourceWI);
|
|
334
|
+
const reason = 'max_retries_exceeded';
|
|
335
|
+
// 1-line cheap guard against silent typo drift (Sam's reminder)
|
|
336
|
+
if (!isReviewReason(reason)) {
|
|
337
|
+
throw new Error(`BRIDGE-1: invalid reviewReason ${String(reason)}`);
|
|
338
|
+
}
|
|
339
|
+
const escalationWI = this.buildAutoWorkItem({
|
|
340
|
+
id: escalationId,
|
|
341
|
+
type: 'review',
|
|
342
|
+
owner: 'team_lead',
|
|
343
|
+
target,
|
|
344
|
+
title: `Escalation: ${sourceWI.title} rejected ${sourceWI.retryCount}x`,
|
|
345
|
+
description: `Source WorkItem ${sourceWI.id} has been rejected ${sourceWI.retryCount} times ` +
|
|
346
|
+
`(cap = ${cap}). TL must re-scope, reassign, or cancel.`,
|
|
347
|
+
sourceWI,
|
|
348
|
+
missionId: sourceWI.missionId,
|
|
349
|
+
requestId: sourceWI.requestId,
|
|
350
|
+
retryCount: 0,
|
|
351
|
+
extraMetadata: {
|
|
352
|
+
idempotencyKey: escalationId, // V1 (per-handler)
|
|
353
|
+
sourceWorkItemId: sourceWI.id,
|
|
354
|
+
reviewReason: reason,
|
|
355
|
+
escalatedRetryCount: sourceWI.retryCount,
|
|
356
|
+
},
|
|
357
|
+
});
|
|
358
|
+
await this.taskPool.addToPool(escalationWI);
|
|
359
|
+
this.logger.info('Retry cap reached — escalation WorkItem created', {
|
|
360
|
+
sourceWorkItemId: sourceWI.id,
|
|
361
|
+
escalationWorkItemId: escalationId,
|
|
362
|
+
retryCount: sourceWI.retryCount,
|
|
363
|
+
cap,
|
|
364
|
+
});
|
|
365
|
+
return;
|
|
366
|
+
}
|
|
367
|
+
// Retry path
|
|
368
|
+
const retryAttempt = sourceWI.retryCount + 1;
|
|
369
|
+
const retryId = `${sourceWI.id}:retry:${retryAttempt}`;
|
|
370
|
+
const retryWI = this.buildAutoWorkItem({
|
|
371
|
+
id: retryId,
|
|
372
|
+
type: sourceWI.type,
|
|
373
|
+
owner: sourceWI.owner,
|
|
374
|
+
target: sourceWI.target,
|
|
375
|
+
title: `Retry ${retryAttempt}/${cap}: ${sourceWI.title}`,
|
|
376
|
+
description: sourceWI.description,
|
|
377
|
+
sourceWI,
|
|
378
|
+
missionId: sourceWI.missionId,
|
|
379
|
+
requestId: sourceWI.requestId,
|
|
380
|
+
retryCount: retryAttempt,
|
|
381
|
+
extraMetadata: {
|
|
382
|
+
idempotencyKey: retryId, // V1 (per-handler)
|
|
383
|
+
sourceWorkItemId: sourceWI.id,
|
|
384
|
+
retryAttempt,
|
|
385
|
+
},
|
|
386
|
+
});
|
|
387
|
+
await this.taskPool.addToPool(retryWI);
|
|
388
|
+
this.logger.info('Retry WorkItem created', {
|
|
389
|
+
sourceWorkItemId: sourceWI.id,
|
|
390
|
+
retryWorkItemId: retryId,
|
|
391
|
+
retryAttempt,
|
|
392
|
+
cap,
|
|
393
|
+
});
|
|
394
|
+
};
|
|
395
|
+
/**
|
|
396
|
+
* `task:blocked` → review WI for TL with `reviewReason='task_blocked'`.
|
|
397
|
+
*
|
|
398
|
+
* Idempotency: `${sourceWI.id}:review:blocked` (one review per blocked WI).
|
|
399
|
+
*/
|
|
400
|
+
handleTaskBlocked = async (event) => {
|
|
401
|
+
const sourceWI = await this.resolveSourceWorkItem(event);
|
|
402
|
+
if (!sourceWI) {
|
|
403
|
+
this.logger.warn('task:blocked missing source WorkItem', { eventId: event.id });
|
|
404
|
+
return;
|
|
405
|
+
}
|
|
406
|
+
const blockedId = `${sourceWI.id}:review:blocked`;
|
|
407
|
+
const target = await this.resolveTeamLeadSession(sourceWI);
|
|
408
|
+
const reason = 'task_blocked';
|
|
409
|
+
if (!isReviewReason(reason)) {
|
|
410
|
+
throw new Error(`BRIDGE-1: invalid reviewReason ${String(reason)}`);
|
|
411
|
+
}
|
|
412
|
+
const reviewWI = this.buildAutoWorkItem({
|
|
413
|
+
id: blockedId,
|
|
414
|
+
type: 'review',
|
|
415
|
+
owner: 'team_lead',
|
|
416
|
+
target,
|
|
417
|
+
title: `Blocked: ${sourceWI.title}`,
|
|
418
|
+
description: `Source WorkItem ${sourceWI.id} entered 'blocked' status. TL must unblock or re-scope.`,
|
|
419
|
+
sourceWI,
|
|
420
|
+
missionId: sourceWI.missionId,
|
|
421
|
+
requestId: sourceWI.requestId,
|
|
422
|
+
retryCount: 0,
|
|
423
|
+
extraMetadata: {
|
|
424
|
+
idempotencyKey: blockedId, // V1 (per-handler)
|
|
425
|
+
sourceWorkItemId: sourceWI.id,
|
|
426
|
+
reviewReason: reason,
|
|
427
|
+
},
|
|
428
|
+
});
|
|
429
|
+
await this.taskPool.addToPool(reviewWI);
|
|
430
|
+
this.logger.info('Blocked-task review WorkItem created', {
|
|
431
|
+
sourceWorkItemId: sourceWI.id,
|
|
432
|
+
reviewWorkItemId: blockedId,
|
|
433
|
+
target,
|
|
434
|
+
});
|
|
435
|
+
};
|
|
436
|
+
/**
|
|
437
|
+
* `team:all_tasks_done` → emit `mission:review_due` if mission is active.
|
|
438
|
+
*
|
|
439
|
+
* The downstream `mission:review_due` handler then creates the actual
|
|
440
|
+
* review WorkItem. We split the emission from the WI-creation so the
|
|
441
|
+
* existing REVIEW-1 sweep path and BRIDGE-1's event-driven path converge
|
|
442
|
+
* on the same idempotency key (REVIEW-1 uses `${missionId}:review:${YYYY-MM-DD}`;
|
|
443
|
+
* BRIDGE-1's mission:review_due handler uses the same).
|
|
444
|
+
*/
|
|
445
|
+
handleTeamAllTasksDone = async (event) => {
|
|
446
|
+
if (!event.missionId) {
|
|
447
|
+
this.logger.debug('team:all_tasks_done without missionId — ignoring', {
|
|
448
|
+
eventId: event.id,
|
|
449
|
+
});
|
|
450
|
+
return;
|
|
451
|
+
}
|
|
452
|
+
const mission = await this.loadMission(event.missionId);
|
|
453
|
+
if (!mission) {
|
|
454
|
+
this.logger.warn('team:all_tasks_done references unknown mission', {
|
|
455
|
+
missionId: event.missionId,
|
|
456
|
+
});
|
|
457
|
+
return;
|
|
458
|
+
}
|
|
459
|
+
if (mission.status !== 'active') {
|
|
460
|
+
this.logger.debug('team:all_tasks_done for non-active mission — skipping emit', {
|
|
461
|
+
missionId: mission.id,
|
|
462
|
+
status: mission.status,
|
|
463
|
+
});
|
|
464
|
+
return;
|
|
465
|
+
}
|
|
466
|
+
// Re-emit as mission:review_due so the downstream handler creates the WI.
|
|
467
|
+
// This preserves the deterministic-id contract — the downstream handler
|
|
468
|
+
// is the single point that creates review WIs for missions.
|
|
469
|
+
this.eventBus.publish({
|
|
470
|
+
id: `${mission.id}:emit:review_due:${todayCycleKey()}`,
|
|
471
|
+
type: 'mission:review_due',
|
|
472
|
+
timestamp: new Date().toISOString(),
|
|
473
|
+
teamId: mission.ownerTeamId,
|
|
474
|
+
teamName: '',
|
|
475
|
+
memberId: '',
|
|
476
|
+
memberName: '',
|
|
477
|
+
sessionName: '',
|
|
478
|
+
previousValue: '',
|
|
479
|
+
newValue: 'review_due',
|
|
480
|
+
changedField: 'taskStatus',
|
|
481
|
+
missionId: mission.id,
|
|
482
|
+
});
|
|
483
|
+
this.logger.info('Emitted mission:review_due for active mission', {
|
|
484
|
+
missionId: mission.id,
|
|
485
|
+
});
|
|
486
|
+
};
|
|
487
|
+
/**
|
|
488
|
+
* `mission:review_due` → review WI mirroring REVIEW-1's id pattern.
|
|
489
|
+
* Idempotency: `${missionId}:review:${YYYY-MM-DD}` — same as REVIEW-1.
|
|
490
|
+
*/
|
|
491
|
+
handleMissionReviewDue = async (event) => {
|
|
492
|
+
if (!event.missionId)
|
|
493
|
+
return;
|
|
494
|
+
const mission = await this.loadMission(event.missionId);
|
|
495
|
+
if (!mission)
|
|
496
|
+
return;
|
|
497
|
+
const cycleId = todayCycleKey();
|
|
498
|
+
const id = `${mission.id}:review:${cycleId}`;
|
|
499
|
+
const target = await this.resolveTeamLeadSessionForMission(mission);
|
|
500
|
+
const reason = 'scheduled_review';
|
|
501
|
+
const reviewWI = this.buildAutoWorkItem({
|
|
502
|
+
id,
|
|
503
|
+
type: 'review',
|
|
504
|
+
owner: 'team_lead',
|
|
505
|
+
target,
|
|
506
|
+
title: `Mission review — ${mission.objective.slice(0, 60)}`,
|
|
507
|
+
description: `Event-driven mission review (cycle ${cycleId}). Reason: ${reason}.`,
|
|
508
|
+
sourceWI: null,
|
|
509
|
+
missionId: mission.id,
|
|
510
|
+
requestId: undefined,
|
|
511
|
+
retryCount: 0,
|
|
512
|
+
extraMetadata: {
|
|
513
|
+
idempotencyKey: id, // V1 (per-handler) — id matches REVIEW-1's pattern
|
|
514
|
+
reviewReason: reason,
|
|
515
|
+
reviewCycleId: cycleId,
|
|
516
|
+
triggerEventId: event.id,
|
|
517
|
+
},
|
|
518
|
+
});
|
|
519
|
+
await this.taskPool.addToPool(reviewWI);
|
|
520
|
+
this.logger.info('Mission review WorkItem created (event-driven)', {
|
|
521
|
+
missionId: mission.id,
|
|
522
|
+
workItemId: id,
|
|
523
|
+
});
|
|
524
|
+
};
|
|
525
|
+
/**
|
|
526
|
+
* `mission:stale` → review WI with `reviewReason='no_active_work'`.
|
|
527
|
+
* Idempotency: `${missionId}:review:stale:${YYYY-MM-DD}`.
|
|
528
|
+
*/
|
|
529
|
+
handleMissionStale = async (event) => {
|
|
530
|
+
if (!event.missionId)
|
|
531
|
+
return;
|
|
532
|
+
const mission = await this.loadMission(event.missionId);
|
|
533
|
+
if (!mission)
|
|
534
|
+
return;
|
|
535
|
+
const cycleId = todayCycleKey();
|
|
536
|
+
const id = `${mission.id}:review:stale:${cycleId}`;
|
|
537
|
+
const target = await this.resolveTeamLeadSessionForMission(mission);
|
|
538
|
+
const reason = 'no_active_work';
|
|
539
|
+
const reviewWI = this.buildAutoWorkItem({
|
|
540
|
+
id,
|
|
541
|
+
type: 'review',
|
|
542
|
+
owner: 'team_lead',
|
|
543
|
+
target,
|
|
544
|
+
title: `Stale mission — ${mission.objective.slice(0, 60)}`,
|
|
545
|
+
description: `Mission ${mission.id} flagged stale on ${cycleId}.`,
|
|
546
|
+
sourceWI: null,
|
|
547
|
+
missionId: mission.id,
|
|
548
|
+
requestId: undefined,
|
|
549
|
+
retryCount: 0,
|
|
550
|
+
extraMetadata: {
|
|
551
|
+
idempotencyKey: id, // V1 (per-handler)
|
|
552
|
+
reviewReason: reason,
|
|
553
|
+
reviewCycleId: cycleId,
|
|
554
|
+
},
|
|
555
|
+
});
|
|
556
|
+
await this.taskPool.addToPool(reviewWI);
|
|
557
|
+
};
|
|
558
|
+
/**
|
|
559
|
+
* `mission:replanned` → review WI tied to the replan event id.
|
|
560
|
+
* Idempotency: `${missionId}:review:replan:${eventId}`.
|
|
561
|
+
*/
|
|
562
|
+
handleMissionReplanned = async (event) => {
|
|
563
|
+
if (!event.missionId)
|
|
564
|
+
return;
|
|
565
|
+
const mission = await this.loadMission(event.missionId);
|
|
566
|
+
if (!mission)
|
|
567
|
+
return;
|
|
568
|
+
const id = `${mission.id}:review:replan:${event.id}`;
|
|
569
|
+
const target = await this.resolveTeamLeadSessionForMission(mission);
|
|
570
|
+
const reason = 'scheduled_review';
|
|
571
|
+
const reviewWI = this.buildAutoWorkItem({
|
|
572
|
+
id,
|
|
573
|
+
type: 'review',
|
|
574
|
+
owner: 'team_lead',
|
|
575
|
+
target,
|
|
576
|
+
title: `Mission replanned — ${mission.objective.slice(0, 60)}`,
|
|
577
|
+
description: `Mission ${mission.id} replanned (event ${event.id}). TL acknowledge.`,
|
|
578
|
+
sourceWI: null,
|
|
579
|
+
missionId: mission.id,
|
|
580
|
+
requestId: undefined,
|
|
581
|
+
retryCount: 0,
|
|
582
|
+
extraMetadata: {
|
|
583
|
+
idempotencyKey: id, // V1 (per-handler)
|
|
584
|
+
reviewReason: reason,
|
|
585
|
+
triggerEventId: event.id,
|
|
586
|
+
},
|
|
587
|
+
});
|
|
588
|
+
await this.taskPool.addToPool(reviewWI);
|
|
589
|
+
};
|
|
590
|
+
// -------------------------------------------------------------------------
|
|
591
|
+
// Internal helpers
|
|
592
|
+
// -------------------------------------------------------------------------
|
|
593
|
+
/**
|
|
594
|
+
* Build an auto-created WorkItem with all the BRIDGE-1 metadata invariants
|
|
595
|
+
* filled in (deterministic id, idempotencyKey === id, triggerSource derived
|
|
596
|
+
* from source WI, parent linkage). Pulls every shared field through one
|
|
597
|
+
* factory so each handler stays declarative and the V1 grep guard
|
|
598
|
+
* (`grep -c "idempotencyKey:" backend/src/services/event-bus/event-to-workitem-bridge.service.ts`
|
|
599
|
+
* ≥ 6) holds without litter.
|
|
600
|
+
*/
|
|
601
|
+
buildAutoWorkItem(args) {
|
|
602
|
+
const triggerSource = inheritedTriggerSource(args.sourceWI);
|
|
603
|
+
const now = new Date().toISOString();
|
|
604
|
+
return {
|
|
605
|
+
id: args.id,
|
|
606
|
+
type: args.type,
|
|
607
|
+
owner: args.owner,
|
|
608
|
+
target: args.target,
|
|
609
|
+
title: args.title,
|
|
610
|
+
description: args.description,
|
|
611
|
+
status: 'queued',
|
|
612
|
+
createdAt: now,
|
|
613
|
+
retryCount: args.retryCount,
|
|
614
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
615
|
+
requestId: args.requestId,
|
|
616
|
+
missionId: args.missionId,
|
|
617
|
+
parentWorkItemId: args.sourceWI?.id,
|
|
618
|
+
inputTokens: 0,
|
|
619
|
+
outputTokens: 0,
|
|
620
|
+
cost: 0,
|
|
621
|
+
metadata: {
|
|
622
|
+
// V1 idempotency + V6 triggerSource invariants are enforced HERE in the
|
|
623
|
+
// factory and ALSO declared per-handler in `extraMetadata` so the
|
|
624
|
+
// audit grep (`grep -c "idempotencyKey:" event-to-workitem-bridge.service.ts`)
|
|
625
|
+
// shows one line per handler. Spread order keeps the per-handler
|
|
626
|
+
// declaration as the source of truth — factory only fills if the
|
|
627
|
+
// handler omitted (defensive default).
|
|
628
|
+
triggerSource, // V6 — never 'cron' for bridge-created WIs
|
|
629
|
+
...args.extraMetadata,
|
|
630
|
+
},
|
|
631
|
+
};
|
|
632
|
+
}
|
|
633
|
+
/**
|
|
634
|
+
* Resolve the source WorkItem from an event. Prefers `event.workItemId`
|
|
635
|
+
* (BRIDGE-1.1 explicit field), falls back to `event.taskId` for events
|
|
636
|
+
* whose publishers haven't been migrated yet.
|
|
637
|
+
*
|
|
638
|
+
* @param event - The event being handled
|
|
639
|
+
* @returns The source WorkItem, or null when nothing matches
|
|
640
|
+
*/
|
|
641
|
+
async resolveSourceWorkItem(event) {
|
|
642
|
+
const id = event.workItemId ?? event.taskId;
|
|
643
|
+
if (!id)
|
|
644
|
+
return null;
|
|
645
|
+
return this.taskPool.findWorkItem(id);
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Resolve the team-lead session for a source WI's owning team.
|
|
649
|
+
* Throws {@link BridgeTeamLeadResolutionError} if no TL is resolvable —
|
|
650
|
+
* Arch Veto V4 forbids silently falling back to the executor.
|
|
651
|
+
*/
|
|
652
|
+
async resolveTeamLeadSession(sourceWI) {
|
|
653
|
+
const teamId = sourceWI.metadata?.['teamId'] ?? null;
|
|
654
|
+
if (!teamId) {
|
|
655
|
+
// No team metadata on the WI — last-resort orchestrator routing keeps
|
|
656
|
+
// the escalation visible. We log a warn so this surface is auditable
|
|
657
|
+
// when a team-aware producer arrives.
|
|
658
|
+
this.logger.warn('Source WI missing metadata.teamId — routing escalation to orchestrator', {
|
|
659
|
+
sourceWorkItemId: sourceWI.id,
|
|
660
|
+
});
|
|
661
|
+
return ORCHESTRATOR_SESSION_NAME;
|
|
662
|
+
}
|
|
663
|
+
const team = await this.loadTeam(teamId);
|
|
664
|
+
if (!team) {
|
|
665
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: cannot resolve team ${teamId} for source WorkItem ${sourceWI.id}`);
|
|
666
|
+
}
|
|
667
|
+
const lead = pickTeamLead(team);
|
|
668
|
+
if (!lead) {
|
|
669
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: cannot resolve team lead for team ${teamId} on source WorkItem ${sourceWI.id}`);
|
|
670
|
+
}
|
|
671
|
+
if (!lead.sessionName) {
|
|
672
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: team lead for team ${teamId} has no sessionName (member ${lead.id})`);
|
|
673
|
+
}
|
|
674
|
+
return lead.sessionName;
|
|
675
|
+
}
|
|
676
|
+
/**
|
|
677
|
+
* Resolve TL for a mission. Same V4 fail-fast contract as
|
|
678
|
+
* {@link resolveTeamLeadSession} but keyed on `mission.ownerTeamId`.
|
|
679
|
+
*/
|
|
680
|
+
async resolveTeamLeadSessionForMission(mission) {
|
|
681
|
+
if (mission.ownerId) {
|
|
682
|
+
// Owner is a member id; we don't load members directly here. If the
|
|
683
|
+
// ownerId is set we trust it as a session name fallback would lose data
|
|
684
|
+
// — but `pickTeamLead` is the canonical resolver, so we still consult it.
|
|
685
|
+
}
|
|
686
|
+
const team = await this.loadTeam(mission.ownerTeamId);
|
|
687
|
+
if (!team) {
|
|
688
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: cannot resolve owner team ${mission.ownerTeamId} for mission ${mission.id}`);
|
|
689
|
+
}
|
|
690
|
+
const lead = pickTeamLead(team);
|
|
691
|
+
if (!lead) {
|
|
692
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: cannot resolve team lead for owner team ${mission.ownerTeamId} on mission ${mission.id}`);
|
|
693
|
+
}
|
|
694
|
+
if (!lead.sessionName) {
|
|
695
|
+
throw new BridgeTeamLeadResolutionError(`BRIDGE-1: team lead for owner team ${mission.ownerTeamId} has no sessionName (member ${lead.id})`);
|
|
696
|
+
}
|
|
697
|
+
return lead.sessionName;
|
|
698
|
+
}
|
|
699
|
+
/**
|
|
700
|
+
* Wrap a handler so a thrown error is caught at the bridge boundary instead
|
|
701
|
+
* of bubbling into EventBus.dispatchInProcess. Errors from handlers
|
|
702
|
+
* shouldn't crash the publisher — they should be observable via logs and
|
|
703
|
+
* — for tests — rethrowable from a deterministic spy point.
|
|
704
|
+
*/
|
|
705
|
+
safeDispatch(eventType, event, handler) {
|
|
706
|
+
const dispatch = (async () => {
|
|
707
|
+
try {
|
|
708
|
+
await handler.call(this, event);
|
|
709
|
+
}
|
|
710
|
+
catch (err) {
|
|
711
|
+
this.logger.error('Bridge handler threw', {
|
|
712
|
+
eventType,
|
|
713
|
+
eventId: event.id,
|
|
714
|
+
error: formatError(err),
|
|
715
|
+
});
|
|
716
|
+
// Re-raise CronRecursionError + V4 errors so test spies + audit log
|
|
717
|
+
// surface them. EventBus.dispatchInProcess catches them harmlessly.
|
|
718
|
+
if (err instanceof CronRecursionError || err instanceof BridgeTeamLeadResolutionError) {
|
|
719
|
+
throw err;
|
|
720
|
+
}
|
|
721
|
+
}
|
|
722
|
+
})();
|
|
723
|
+
this.pendingDispatches.add(dispatch);
|
|
724
|
+
// Always remove from the in-flight set when the promise settles, regardless
|
|
725
|
+
// of fulfilled/rejected outcome, so flushPending() drains cleanly.
|
|
726
|
+
dispatch.finally(() => {
|
|
727
|
+
this.pendingDispatches.delete(dispatch);
|
|
728
|
+
}).catch(() => {
|
|
729
|
+
// Suppress unhandled-rejection warning — flushPending callers see the
|
|
730
|
+
// outcome via Promise.allSettled, and the handler's logger.error has
|
|
731
|
+
// already recorded the throw.
|
|
732
|
+
});
|
|
733
|
+
return dispatch;
|
|
734
|
+
}
|
|
735
|
+
}
|
|
736
|
+
//# sourceMappingURL=event-to-workitem-bridge.service.js.map
|