crewly 1.13.0 → 1.13.1
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/complete-body-shape.test.sh +113 -0
- package/config/skills/_common/no-dangling-symlinks.test.sh +63 -0
- package/config/skills/_common/task-pool-body-shape.test.sh +471 -0
- package/config/skills/agent/core/accept-task/execute.sh +30 -5
- package/config/skills/agent/core/block-task/execute.sh +59 -6
- package/config/skills/agent/core/complete-task/SKILL.md +37 -5
- package/config/skills/agent/core/complete-task/execute.sh +40 -9
- package/config/skills/agent/core/complete-task/execute.test.sh +213 -0
- package/config/skills/agent/core/create-task/SKILL.md +33 -0
- package/config/skills/agent/core/create-task/execute.sh +82 -23
- package/config/skills/agent/core/delegate-task/execute.sh +46 -0
- package/config/skills/agent/core/get-team-norms/SKILL.md +1 -1
- package/config/skills/agent/core/get-team-norms/execute.sh +73 -2
- package/config/skills/agent/core/handoff-task/execute.sh +8 -1
- package/config/skills/agent/core/report-status/SKILL.md +26 -0
- package/config/skills/agent/core/report-status/execute.sh +40 -5
- package/config/skills/orchestrator/delegate-task/SKILL.md +1 -1
- package/config/skills/orchestrator/delegate-task/execute.sh +45 -11
- package/config/skills/orchestrator/delegate-task/execute.test.sh +153 -1
- package/config/skills/team-leader/aggregate-results/SKILL.md +1 -1
- package/config/skills/team-leader/decompose-goal/execute.sh +11 -16
- package/config/skills/team-leader/delegate-task/SKILL.md +14 -1
- package/config/skills/team-leader/delegate-task/execute.sh +132 -43
- package/config/skills/team-leader/delegate-task/execute.test.sh +180 -27
- package/config/vitest-exclude.constants.ts +56 -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 +18 -3
- package/dist/backend/backend/src/controllers/monitoring/terminal.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/slack/slack.controller.js +18 -15
- package/dist/backend/backend/src/controllers/slack/slack.controller.js.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts +6 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.d.ts.map +1 -1
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js +19 -10
- package/dist/backend/backend/src/controllers/task-pool/task-pool.controller.js.map +1 -1
- package/dist/backend/backend/src/index.d.ts.map +1 -1
- package/dist/backend/backend/src/index.js +20 -0
- package/dist/backend/backend/src/index.js.map +1 -1
- package/dist/backend/backend/src/scripts/stamp-build.d.ts +25 -0
- package/dist/backend/backend/src/scripts/stamp-build.d.ts.map +1 -0
- package/dist/backend/backend/src/scripts/stamp-build.js +48 -0
- package/dist/backend/backend/src/scripts/stamp-build.js.map +1 -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 +25 -0
- package/dist/backend/backend/src/services/agent/agent-registration.service.js.map +1 -1
- 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 +9 -1
- 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/types.d.ts +42 -3
- 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 +22 -1
- package/dist/backend/backend/src/services/agent/crewly-agent/types.js.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts +15 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js +40 -0
- package/dist/backend/backend/src/services/event-bus/event-to-workitem-bridge.service.js.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts +28 -3
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js +65 -8
- package/dist/backend/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts +214 -4
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js +330 -48
- package/dist/backend/backend/src/services/reconciler/reconcile-rules.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts +13 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js +84 -5
- package/dist/backend/backend/src/services/reconciler/reconciler-data-provider.js.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts +14 -0
- package/dist/backend/backend/src/services/reconciler/reconciler.service.d.ts.map +1 -1
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js +77 -3
- package/dist/backend/backend/src/services/reconciler/reconciler.service.js.map +1 -1
- package/dist/backend/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
- 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 +268 -36
- package/dist/backend/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/escalation-router.service.js +2 -2
- package/dist/backend/backend/src/services/v3/escalation-router.service.js.map +1 -1
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -1
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js +39 -2
- package/dist/backend/backend/src/services/v3/request-sla.subscriber.js.map +1 -1
- package/dist/backend/backend/src/types/v2/index.d.ts +1 -1
- package/dist/backend/backend/src/types/v2/index.d.ts.map +1 -1
- package/dist/backend/backend/src/types/v2/index.js +1 -1
- package/dist/backend/backend/src/types/v2/index.js.map +1 -1
- package/dist/backend/backend/src/types/v2/work-item.types.d.ts +181 -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 +104 -17
- package/dist/backend/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/backend/backend/src/utils/build-provenance.d.ts +150 -0
- package/dist/backend/backend/src/utils/build-provenance.d.ts.map +1 -0
- package/dist/backend/backend/src/utils/build-provenance.js +211 -0
- package/dist/backend/backend/src/utils/build-provenance.js.map +1 -0
- package/dist/backend/build-info.json +4 -0
- package/dist/backend/config/vitest-exclude.constants.d.ts +53 -0
- package/dist/backend/config/vitest-exclude.constants.d.ts.map +1 -0
- package/dist/backend/config/vitest-exclude.constants.js +55 -0
- package/dist/backend/config/vitest-exclude.constants.js.map +1 -0
- package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts +538 -0
- package/dist/cli/backend/src/services/agent/active-work-briefing.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/active-work-briefing.service.js +802 -0
- package/dist/cli/backend/src/services/agent/active-work-briefing.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts +240 -0
- package/dist/cli/backend/src/services/agent/agent-heartbeat.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js +524 -0
- package/dist/cli/backend/src/services/agent/agent-heartbeat.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts +629 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.js +4548 -0
- package/dist/cli/backend/src/services/agent/agent-registration.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts +118 -0
- package/dist/cli/backend/src/services/agent/agent-suspend.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/agent-suspend.service.js +304 -0
- package/dist/cli/backend/src/services/agent/agent-suspend.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts +196 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js +504 -0
- package/dist/cli/backend/src/services/agent/auditor-scheduler.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts +97 -0
- package/dist/cli/backend/src/services/agent/claude-runtime.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/claude-runtime.service.js +334 -0
- package/dist/cli/backend/src/services/agent/claude-runtime.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts +47 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js +112 -0
- package/dist/cli/backend/src/services/agent/codex-runtime.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts +323 -0
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.js +1056 -0
- package/dist/cli/backend/src/services/agent/context-window-monitor.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts +198 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js +816 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/crewly-agent-external-runtime.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts +155 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js +272 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-log-buffer.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts +91 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js +120 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/in-process-runtime-registry.js.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts +536 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/types.js +282 -0
- package/dist/cli/backend/src/services/agent/crewly-agent/types.js.map +1 -0
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts +100 -0
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.js +149 -0
- package/dist/cli/backend/src/services/agent/disk-cleanup.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts +130 -0
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.js +758 -0
- package/dist/cli/backend/src/services/agent/gemini-runtime.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts +216 -0
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js +508 -0
- package/dist/cli/backend/src/services/agent/oauth-relogin-monitor.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts +113 -0
- package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js +161 -0
- package/dist/cli/backend/src/services/agent/pty-activity-tracker.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts +156 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js +641 -0
- package/dist/cli/backend/src/services/agent/runtime-agent.service.abstract.js.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts +295 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js +1166 -0
- package/dist/cli/backend/src/services/agent/runtime-exit-monitor.service.js.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts +71 -0
- package/dist/cli/backend/src/services/agent/runtime-service.factory.d.ts.map +1 -0
- package/dist/cli/backend/src/services/agent/runtime-service.factory.js +174 -0
- package/dist/cli/backend/src/services/agent/runtime-service.factory.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts +435 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.js +1331 -0
- package/dist/cli/backend/src/services/ai/prompt-builder.service.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts +33 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js +70 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/capability-overlay.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts +100 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js +296 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/communication.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts +59 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js +87 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/decision-rights.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts +67 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js +84 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/default-execution-loop.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts +35 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js +50 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/domain-sop.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts +41 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js +95 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/expert-profile.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts +28 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js +47 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/identity.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts +65 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js +79 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lazy-anti-patterns.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts +28 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js +55 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/learning-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts +44 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js +120 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/lifecycle.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts +46 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js +44 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/markdown-file-module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts +28 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js +46 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/memory-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts +60 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js +104 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/mission-context.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts +29 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js +50 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/project-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts +103 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js +383 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/prompt-assembly.service.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts +34 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js +96 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/recovery.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts +106 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js +167 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/request-contract.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts +35 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js +50 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/risk-policy.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts +91 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js +411 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/role-boundary.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts +62 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js +199 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/skills-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts +79 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js +118 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/sop-norm-distinction.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts +102 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js +229 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/soul.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts +61 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js +197 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-norms.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts +54 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js +149 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/team-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts +32 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js +69 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/user-profile-reference.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts +91 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js +136 -0
- package/dist/cli/backend/src/services/ai/prompt-modules/working-memory.module.js.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts +81 -0
- package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js +135 -0
- package/dist/cli/backend/src/services/ai/self-improvement/growth-areas.service.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts +737 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js +1213 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.service.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts +27 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js +57 -0
- package/dist/cli/backend/src/services/chat-v2/chat-v2.singleton.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/config.d.ts +100 -0
- package/dist/cli/backend/src/services/chat-v2/config.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/config.js +174 -0
- package/dist/cli/backend/src/services/chat-v2/config.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts +118 -0
- package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js +171 -0
- package/dist/cli/backend/src/services/chat-v2/legacy-dto.utils.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts +188 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js +317 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/channel.store.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts +119 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js +551 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/chat-db.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts +208 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js +405 -0
- package/dist/cli/backend/src/services/chat-v2/sqlite/message.store.js.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/types.d.ts +341 -0
- package/dist/cli/backend/src/services/chat-v2/types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/chat-v2/types.js +61 -0
- package/dist/cli/backend/src/services/chat-v2/types.js.map +1 -0
- package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts +37 -0
- package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.d.ts.map +1 -0
- package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js +63 -0
- package/dist/cli/backend/src/services/continuation/patterns/idle-patterns.js.map +1 -0
- package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts +335 -0
- package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.d.ts.map +1 -0
- package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js +421 -0
- package/dist/cli/backend/src/services/intent-task/intent-classifier.rules.js.map +1 -0
- package/dist/cli/backend/src/services/mcp-client.d.ts +233 -0
- package/dist/cli/backend/src/services/mcp-client.d.ts.map +1 -0
- package/dist/cli/backend/src/services/mcp-client.js +302 -0
- package/dist/cli/backend/src/services/mcp-client.js.map +1 -0
- package/dist/cli/backend/src/services/memory/mission-context.service.d.ts +168 -0
- package/dist/cli/backend/src/services/memory/mission-context.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/mission-context.service.js +365 -0
- package/dist/cli/backend/src/services/memory/mission-context.service.js.map +1 -0
- package/dist/cli/backend/src/services/memory/session-memory.service.d.ts +197 -0
- package/dist/cli/backend/src/services/memory/session-memory.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/session-memory.service.js +369 -0
- package/dist/cli/backend/src/services/memory/session-memory.service.js.map +1 -0
- package/dist/cli/backend/src/services/memory/user-profile.service.d.ts +77 -0
- package/dist/cli/backend/src/services/memory/user-profile.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/user-profile.service.js +171 -0
- package/dist/cli/backend/src/services/memory/user-profile.service.js.map +1 -0
- package/dist/cli/backend/src/services/memory/working-memory.service.d.ts +232 -0
- package/dist/cli/backend/src/services/memory/working-memory.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/memory/working-memory.service.js +417 -0
- package/dist/cli/backend/src/services/memory/working-memory.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts +84 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js +136 -0
- package/dist/cli/backend/src/services/messaging/sub-agent-message-queue.service.js.map +1 -0
- package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts +358 -0
- package/dist/cli/backend/src/services/messaging/thread-status-queue.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js +755 -0
- package/dist/cli/backend/src/services/messaging/thread-status-queue.service.js.map +1 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts +269 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js +391 -0
- package/dist/cli/backend/src/services/monitoring/token-usage.service.js.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts +132 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js +284 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.service.js.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts +70 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.d.ts.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js +121 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.singleton.js.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts +130 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js +48 -0
- package/dist/cli/backend/src/services/observability/agent-behavior-log.types.js.map +1 -0
- package/dist/cli/backend/src/services/observability/observability-db.d.ts +84 -0
- package/dist/cli/backend/src/services/observability/observability-db.d.ts.map +1 -0
- package/dist/cli/backend/src/services/observability/observability-db.js +165 -0
- package/dist/cli/backend/src/services/observability/observability-db.js.map +1 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts +124 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.service.js +256 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.service.js.map +1 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts +80 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.types.d.ts.map +1 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.types.js +16 -0
- package/dist/cli/backend/src/services/onboarding/content-approval.types.js.map +1 -0
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts +142 -0
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js +265 -0
- package/dist/cli/backend/src/services/orc/orc-delivery-enforcer.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts +231 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js +353 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-marker.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts +157 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js +483 -0
- package/dist/cli/backend/src/services/orchestrator/improvement-startup.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/index.d.ts +16 -0
- package/dist/cli/backend/src/services/orchestrator/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/index.js +16 -0
- package/dist/cli/backend/src/services/orchestrator/index.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts +117 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js +300 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-restart.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts +114 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js +195 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-setup.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts +94 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js +279 -0
- package/dist/cli/backend/src/services/orchestrator/orchestrator-status.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts +192 -0
- package/dist/cli/backend/src/services/orchestrator/safe-restart.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js +395 -0
- package/dist/cli/backend/src/services/orchestrator/safe-restart.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts +201 -0
- package/dist/cli/backend/src/services/orchestrator/self-improvement.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js +396 -0
- package/dist/cli/backend/src/services/orchestrator/self-improvement.service.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts +17 -0
- package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js +30 -0
- package/dist/cli/backend/src/services/orchestrator/slack-bridge-lazy.js.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts +249 -0
- package/dist/cli/backend/src/services/orchestrator/state-persistence.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js +569 -0
- package/dist/cli/backend/src/services/orchestrator/state-persistence.service.js.map +1 -0
- package/dist/cli/backend/src/services/session/index.d.ts +47 -0
- package/dist/cli/backend/src/services/session/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/index.js +50 -0
- package/dist/cli/backend/src/services/session/index.js.map +1 -0
- package/dist/cli/backend/src/services/session/log-rotation.service.d.ts +163 -0
- package/dist/cli/backend/src/services/session/log-rotation.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/log-rotation.service.js +362 -0
- package/dist/cli/backend/src/services/session/log-rotation.service.js.map +1 -0
- package/dist/cli/backend/src/services/session/pty/index.d.ts +11 -0
- package/dist/cli/backend/src/services/session/pty/index.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/pty/index.js +11 -0
- package/dist/cli/backend/src/services/session/pty/index.js.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts +445 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.js +804 -0
- package/dist/cli/backend/src/services/session/pty/pty-session-backend.js.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-session.d.ts +252 -0
- package/dist/cli/backend/src/services/session/pty/pty-session.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-session.js +611 -0
- package/dist/cli/backend/src/services/session/pty/pty-session.js.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts +275 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js +403 -0
- package/dist/cli/backend/src/services/session/pty/pty-terminal-buffer.js.map +1 -0
- package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts +86 -0
- package/dist/cli/backend/src/services/session/runtime-pid-registry.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js +214 -0
- package/dist/cli/backend/src/services/session/runtime-pid-registry.service.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-backend.factory.d.ts +127 -0
- package/dist/cli/backend/src/services/session/session-backend.factory.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/session-backend.factory.js +226 -0
- package/dist/cli/backend/src/services/session/session-backend.factory.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-backend.interface.d.ts +390 -0
- package/dist/cli/backend/src/services/session/session-backend.interface.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/session-backend.interface.js +19 -0
- package/dist/cli/backend/src/services/session/session-backend.interface.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts +350 -0
- package/dist/cli/backend/src/services/session/session-command-helper.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/session-command-helper.js +810 -0
- package/dist/cli/backend/src/services/session/session-command-helper.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-handoff.service.d.ts +307 -0
- package/dist/cli/backend/src/services/session/session-handoff.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/session-handoff.service.js +742 -0
- package/dist/cli/backend/src/services/session/session-handoff.service.js.map +1 -0
- package/dist/cli/backend/src/services/session/session-state-persistence.d.ts +231 -0
- package/dist/cli/backend/src/services/session/session-state-persistence.d.ts.map +1 -0
- package/dist/cli/backend/src/services/session/session-state-persistence.js +407 -0
- package/dist/cli/backend/src/services/session/session-state-persistence.js.map +1 -0
- package/dist/cli/backend/src/services/settings/role.service.d.ts +277 -0
- package/dist/cli/backend/src/services/settings/role.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/settings/role.service.js +728 -0
- package/dist/cli/backend/src/services/settings/role.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts +152 -0
- package/dist/cli/backend/src/services/slack/cross-machine-message.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/cross-machine-message.service.js +354 -0
- package/dist/cli/backend/src/services/slack/cross-machine-message.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-image.service.d.ts +114 -0
- package/dist/cli/backend/src/services/slack/slack-image.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-image.service.js +331 -0
- package/dist/cli/backend/src/services/slack/slack-image.service.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts +499 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js +1928 -0
- package/dist/cli/backend/src/services/slack/slack-orchestrator-bridge.js.map +1 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts +315 -0
- package/dist/cli/backend/src/services/slack/slack.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/slack/slack.service.js +1094 -0
- package/dist/cli/backend/src/services/slack/slack.service.js.map +1 -0
- package/dist/cli/backend/src/services/sop/sop.service.d.ts +364 -0
- package/dist/cli/backend/src/services/sop/sop.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/sop/sop.service.js +734 -0
- package/dist/cli/backend/src/services/sop/sop.service.js.map +1 -0
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts +152 -7
- package/dist/cli/backend/src/services/task-pool/task-pool.service.d.ts.map +1 -1
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js +268 -36
- package/dist/cli/backend/src/services/task-pool/task-pool.service.js.map +1 -1
- package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts +178 -0
- package/dist/cli/backend/src/services/v3/escalation-router.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/escalation-router.service.js +692 -0
- package/dist/cli/backend/src/services/v3/escalation-router.service.js.map +1 -0
- package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts +200 -0
- package/dist/cli/backend/src/services/v3/kr-tracking.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/kr-tracking.service.js +482 -0
- package/dist/cli/backend/src/services/v3/kr-tracking.service.js.map +1 -0
- package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts +117 -0
- package/dist/cli/backend/src/services/v3/mission-executor.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/mission-executor.service.js +321 -0
- package/dist/cli/backend/src/services/v3/mission-executor.service.js.map +1 -0
- package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts +610 -0
- package/dist/cli/backend/src/services/v3/request-sla.subscriber.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/request-sla.subscriber.js +1529 -0
- package/dist/cli/backend/src/services/v3/request-sla.subscriber.js.map +1 -0
- package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts +78 -0
- package/dist/cli/backend/src/services/v3/request-tracker.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/request-tracker.service.js +108 -0
- package/dist/cli/backend/src/services/v3/request-tracker.service.js.map +1 -0
- package/dist/cli/backend/src/services/v3/request.service.d.ts +295 -0
- package/dist/cli/backend/src/services/v3/request.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/request.service.js +534 -0
- package/dist/cli/backend/src/services/v3/request.service.js.map +1 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts +340 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.d.ts.map +1 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.js +1113 -0
- package/dist/cli/backend/src/services/v3/v3-data.service.js.map +1 -0
- package/dist/cli/backend/src/types/cross-machine.types.d.ts +103 -0
- package/dist/cli/backend/src/types/cross-machine.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/cross-machine.types.js +47 -0
- package/dist/cli/backend/src/types/cross-machine.types.js.map +1 -0
- package/dist/cli/backend/src/types/intent-task.types.d.ts +402 -0
- package/dist/cli/backend/src/types/intent-task.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/intent-task.types.js +403 -0
- package/dist/cli/backend/src/types/intent-task.types.js.map +1 -0
- package/dist/cli/backend/src/types/orchestrator-state.types.d.ts +531 -0
- package/dist/cli/backend/src/types/orchestrator-state.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/orchestrator-state.types.js +110 -0
- package/dist/cli/backend/src/types/orchestrator-state.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/index.d.ts +30 -0
- package/dist/cli/backend/src/types/v2/index.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/index.js +22 -0
- package/dist/cli/backend/src/types/v2/index.js.map +1 -0
- package/dist/cli/backend/src/types/v2/key-result.types.d.ts +279 -0
- package/dist/cli/backend/src/types/v2/key-result.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/key-result.types.js +264 -0
- package/dist/cli/backend/src/types/v2/key-result.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts +661 -0
- package/dist/cli/backend/src/types/v2/mission.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/mission.types.js +713 -0
- package/dist/cli/backend/src/types/v2/mission.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/reconcile.types.d.ts +204 -0
- package/dist/cli/backend/src/types/v2/reconcile.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/reconcile.types.js +121 -0
- package/dist/cli/backend/src/types/v2/reconcile.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/request.types.d.ts +192 -0
- package/dist/cli/backend/src/types/v2/request.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/request.types.js +179 -0
- package/dist/cli/backend/src/types/v2/request.types.js.map +1 -0
- package/dist/cli/backend/src/types/v2/work-item.types.d.ts +181 -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 +104 -17
- package/dist/cli/backend/src/types/v2/work-item.types.js.map +1 -1
- package/dist/cli/backend/src/types/v2/workspace.types.d.ts +157 -0
- package/dist/cli/backend/src/types/v2/workspace.types.d.ts.map +1 -0
- package/dist/cli/backend/src/types/v2/workspace.types.js +173 -0
- package/dist/cli/backend/src/types/v2/workspace.types.js.map +1 -0
- package/dist/cli/backend/src/utils/async.utils.d.ts +68 -0
- package/dist/cli/backend/src/utils/async.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/async.utils.js +80 -0
- package/dist/cli/backend/src/utils/async.utils.js.map +1 -0
- package/dist/cli/backend/src/utils/native-binding.utils.d.ts +128 -0
- package/dist/cli/backend/src/utils/native-binding.utils.d.ts.map +1 -0
- package/dist/cli/backend/src/utils/native-binding.utils.js +206 -0
- package/dist/cli/backend/src/utils/native-binding.utils.js.map +1 -0
- package/dist/cli/backend/src/websocket/chat.gateway.d.ts +142 -0
- package/dist/cli/backend/src/websocket/chat.gateway.d.ts.map +1 -0
- package/dist/cli/backend/src/websocket/chat.gateway.js +327 -0
- package/dist/cli/backend/src/websocket/chat.gateway.js.map +1 -0
- package/dist/cli/backend/src/websocket/terminal.gateway.d.ts +341 -0
- package/dist/cli/backend/src/websocket/terminal.gateway.d.ts.map +1 -0
- package/dist/cli/backend/src/websocket/terminal.gateway.js +933 -0
- package/dist/cli/backend/src/websocket/terminal.gateway.js.map +1 -0
- package/dist/cli/config/vitest-exclude.constants.d.ts +53 -0
- package/dist/cli/config/vitest-exclude.constants.d.ts.map +1 -0
- package/dist/cli/config/vitest-exclude.constants.js +55 -0
- package/dist/cli/config/vitest-exclude.constants.js.map +1 -0
- package/frontend/dist/assets/index-890d3f9d.js +4961 -0
- package/frontend/dist/index.html +1 -1
- package/package.json +2 -2
- package/packages/crewly-agent/src/runtime/agent-runner.service.test.ts +174 -30
- package/packages/crewly-agent/src/runtime/agent-runner.service.ts +121 -6
- package/packages/crewly-agent/src/runtime/types.ts +56 -5
- package/frontend/dist/assets/index-aa737fd3.js +0 -5254
|
@@ -0,0 +1,1529 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* RequestSlaSubscriber (INBOUND-1)
|
|
3
|
+
*
|
|
4
|
+
* Closes the user-facing reliability gap noted by Steve on 2026-04-27:
|
|
5
|
+
* inbound user messages on Slack/Chat-v2 land as Requests today
|
|
6
|
+
* (`slack-orchestrator-bridge.ts:377`), but no WorkItem is auto-created on
|
|
7
|
+
* the orchestrator's plate. The orc therefore has no SLA to track and can
|
|
8
|
+
* silently drop a user request when busy with other work.
|
|
9
|
+
*
|
|
10
|
+
* This subscriber listens to `request:created` and — when the Request was
|
|
11
|
+
* sourced from an inbound user channel (tags include `slack` or `chat-v2`)
|
|
12
|
+
* — auto-creates a `respond_to_user` WorkItem assigned to the orc with a
|
|
13
|
+
* 5-minute SLA deadline. If the orc has not transitioned the WI to a
|
|
14
|
+
* terminal status by 5 minutes, the subscriber emits `request:sla_breached`
|
|
15
|
+
* (CRITICAL — surfaces in the orc terminal). At 10 minutes the subscriber
|
|
16
|
+
* fires the *escalation* hook, which the production wiring uses to send a
|
|
17
|
+
* Slack DM nudge back to the user (so they're never blind to the miss).
|
|
18
|
+
*
|
|
19
|
+
* Auto-close paths:
|
|
20
|
+
* (a) {@link markResolvedByThread} — `slack-orchestrator-bridge` calls
|
|
21
|
+
* this when the orc replies in a thread, so the WI auto-transitions
|
|
22
|
+
* to `done` and the SLA timers no-op against a terminal status.
|
|
23
|
+
* (b) {@link handleWorkItemQueued} (INBOUND-1.f1) — when the orc decomposes
|
|
24
|
+
* a Request into other WorkItems via `taskPool.addToPool`, every new
|
|
25
|
+
* WI fires `workitem:queued`. The handler treats decomposition as
|
|
26
|
+
* "the orc has done the right thing" and resolves the tracked
|
|
27
|
+
* respond_to_user WI with reason `workitem_decompose`. Self-recursion
|
|
28
|
+
* is prevented by an id-shape guard
|
|
29
|
+
* (`request:${requestId}:respond_to_user`) — the respond_to_user WI's
|
|
30
|
+
* own enqueue cannot trigger its own resolution.
|
|
31
|
+
* (c) Timer self-check — every breach handler reads the WI status before
|
|
32
|
+
* publishing or escalating, so a manual orc completeItem() also
|
|
33
|
+
* silences the chain.
|
|
34
|
+
*
|
|
35
|
+
* Idempotency contract (Arch Veto V1):
|
|
36
|
+
* The respond_to_user WorkItem id is deterministic
|
|
37
|
+
* (`request:${requestId}:respond_to_user`); the underlying
|
|
38
|
+
* {@link TaskPoolService.addToPool} already short-circuits on duplicate
|
|
39
|
+
* id. A redelivered `request:created` event therefore fires the handler
|
|
40
|
+
* again, the bridge re-builds the same id, and addToPool no-ops — no
|
|
41
|
+
* separate idempotency store is needed.
|
|
42
|
+
*
|
|
43
|
+
* No new ingress event types (Arch Veto V7 spirit):
|
|
44
|
+
* The orc's revised framing on 2026-04-27 explicitly preferred reusing
|
|
45
|
+
* the existing Request creation path over adding new `inbound:*` event
|
|
46
|
+
* vocabulary. INBOUND-1 adds two events (`request:created`,
|
|
47
|
+
* `request:sla_breached`) that match the Request lifecycle the rest of
|
|
48
|
+
* the system already knows about — no parallel ingress entity.
|
|
49
|
+
*
|
|
50
|
+
* @module services/v3/request-sla.subscriber
|
|
51
|
+
*/
|
|
52
|
+
import { LoggerService } from '../core/logger.service.js';
|
|
53
|
+
import { ORCHESTRATOR_SESSION_NAME } from '../../constants.js';
|
|
54
|
+
import { formatError } from '../../utils/format-error.js';
|
|
55
|
+
import { DEFAULT_MAX_RETRIES, SLA_TERMINAL_WORK_ITEM_STATUSES, } from '../../types/v2/work-item.types.js';
|
|
56
|
+
import { TERMINAL_REQUEST_STATUSES, isValidRequestTransition, } from '../../types/v2/request.types.js';
|
|
57
|
+
// ---------------------------------------------------------------------------
|
|
58
|
+
// Module-level singleton accessor (DI for slack-orchestrator-bridge)
|
|
59
|
+
// ---------------------------------------------------------------------------
|
|
60
|
+
/**
|
|
61
|
+
* The currently-wired RequestSlaSubscriber instance, set by the backend
|
|
62
|
+
* boot path. The slack-orchestrator-bridge calls
|
|
63
|
+
* {@link getRequestSlaSubscriber} from a lazy import so we don't form a
|
|
64
|
+
* static cycle between the bridge and the subscriber at module load.
|
|
65
|
+
*/
|
|
66
|
+
let injectedSubscriber = null;
|
|
67
|
+
/**
|
|
68
|
+
* Wire the subscriber instance accessible via {@link getRequestSlaSubscriber}.
|
|
69
|
+
* Called once from boot before the slack listener can dispatch a reply.
|
|
70
|
+
*
|
|
71
|
+
* @param sub - The live subscriber, or null to clear (tests)
|
|
72
|
+
*/
|
|
73
|
+
export function setRequestSlaSubscriber(sub) {
|
|
74
|
+
injectedSubscriber = sub;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* Read the currently-wired subscriber, or null if boot has not finished yet.
|
|
78
|
+
* Returns null in test setups that don't wire one — callers must tolerate.
|
|
79
|
+
*/
|
|
80
|
+
export function getRequestSlaSubscriber() {
|
|
81
|
+
return injectedSubscriber;
|
|
82
|
+
}
|
|
83
|
+
// ---------------------------------------------------------------------------
|
|
84
|
+
// Constants
|
|
85
|
+
// ---------------------------------------------------------------------------
|
|
86
|
+
/**
|
|
87
|
+
* Default SLA breach threshold — orc must respond within this window or
|
|
88
|
+
* `request:sla_breached` fires with breachLevel=5.
|
|
89
|
+
*/
|
|
90
|
+
export const DEFAULT_SLA_MS = 5 * 60 * 1000;
|
|
91
|
+
/**
|
|
92
|
+
* Default escalation threshold — at this point the user-facing escalation
|
|
93
|
+
* hook fires (production: Slack DM back to user).
|
|
94
|
+
*/
|
|
95
|
+
export const DEFAULT_ESCALATION_MS = 10 * 60 * 1000;
|
|
96
|
+
/**
|
|
97
|
+
* Defensive retry delay for {@link RequestSlaSubscriber.markResolvedByThread}
|
|
98
|
+
* and {@link RequestSlaSubscriber.markResolvedByChatV2} on index miss.
|
|
99
|
+
*
|
|
100
|
+
* The Slack bridge fires `slackResolve('')` from the queue processor right
|
|
101
|
+
* after delivering a message — within ~10ms of `request:created`. The
|
|
102
|
+
* `handleRequestCreated` reordering covers the common case (timer/index
|
|
103
|
+
* setup is now synchronous, before the `taskPool.addToPool` await), but
|
|
104
|
+
* the leading `await requestService.getById(...)` still leaves a ~1ms
|
|
105
|
+
* window. This single delayed retry absorbs that residual race without
|
|
106
|
+
* adding latency to the happy path.
|
|
107
|
+
*
|
|
108
|
+
* 250ms is comfortably above any reasonable in-memory lookup + microtask
|
|
109
|
+
* scheduling latency, well below the 5min SLA window. Verified for the
|
|
110
|
+
* 2026-05-03 ESTestNode auto-nudge variant 2 incident.
|
|
111
|
+
*/
|
|
112
|
+
export const MARK_RESOLVED_RETRY_MS = 250;
|
|
113
|
+
/**
|
|
114
|
+
* Retry parameters for {@link RequestSlaSubscriber.findWorkItemWithRetry}.
|
|
115
|
+
* Cover the case where {@link RequestSlaSubscriber.markResolved} is called
|
|
116
|
+
* before {@link RequestSlaSubscriber.handleRequestCreated} has completed
|
|
117
|
+
* its `taskPool.addToPool(wi)` await — i.e. the WI's tracking entry exists
|
|
118
|
+
* in memory but the pool persistence is still in flight.
|
|
119
|
+
*
|
|
120
|
+
* 5 attempts × 50ms = 250ms worst-case wait, matching MARK_RESOLVED_RETRY_MS
|
|
121
|
+
* so both race windows close on the same upper bound.
|
|
122
|
+
*/
|
|
123
|
+
export const FIND_WI_MAX_ATTEMPTS = 5;
|
|
124
|
+
export const FIND_WI_RETRY_INTERVAL_MS = 50;
|
|
125
|
+
/**
|
|
126
|
+
* Tags we treat as "user-facing inbound channels" — Requests with any of
|
|
127
|
+
* these tags get an SLA-tracked respond_to_user WI. Slack is wired today;
|
|
128
|
+
* `chat-v2` is reserved for the channel-rail Phase E surface.
|
|
129
|
+
*/
|
|
130
|
+
export const DEFAULT_INBOUND_TAGS = ['slack', 'chat-v2'];
|
|
131
|
+
/**
|
|
132
|
+
* Event types the subscriber listens to.
|
|
133
|
+
*
|
|
134
|
+
* - `request:created` (INBOUND-1) — seed a respond_to_user WI for inbound
|
|
135
|
+
* user messages.
|
|
136
|
+
* - `workitem:queued` (INBOUND-1.f1) — auto-close path b: when the orc
|
|
137
|
+
* decomposes a Request into other WorkItems, the respond_to_user WI for
|
|
138
|
+
* that Request resolves automatically.
|
|
139
|
+
*
|
|
140
|
+
* Future iterations may add `request:cancelled` etc. for cleanup.
|
|
141
|
+
*/
|
|
142
|
+
export const REQUEST_SLA_SUBSCRIBED_EVENTS = [
|
|
143
|
+
'request:created',
|
|
144
|
+
'workitem:queued',
|
|
145
|
+
];
|
|
146
|
+
/**
|
|
147
|
+
* Build the deterministic respond_to_user WorkItem id for a Request. The
|
|
148
|
+
* SLA subscriber uses this both for creating the WI and as the id-shape
|
|
149
|
+
* guard against self-resolve recursion in {@link handleWorkItemQueued}.
|
|
150
|
+
*
|
|
151
|
+
* @param requestId - The Request id
|
|
152
|
+
* @returns The deterministic WI id
|
|
153
|
+
*/
|
|
154
|
+
export function respondToUserWorkItemId(requestId) {
|
|
155
|
+
return `request:${requestId}:respond_to_user`;
|
|
156
|
+
}
|
|
157
|
+
/**
|
|
158
|
+
* Pick the legal terminal status for resolving an SLA-tracked WI. The V3
|
|
159
|
+
* `WORK_ITEM_TRANSITIONS` matrix (see `types/v2/work-item.types.ts`)
|
|
160
|
+
* forbids `queued → done`, so the previous `markResolved` always-`'done'`
|
|
161
|
+
* path was throwing in production while the test fake silently accepted it.
|
|
162
|
+
*
|
|
163
|
+
* Mapping:
|
|
164
|
+
* - `running` → `done` (someone explicitly claimed; close cleanly).
|
|
165
|
+
* - `done_by_worker` → `verified` (the only edge `done_by_worker` permits
|
|
166
|
+
* toward terminal-success; `done_by_worker → cancelled` is illegal).
|
|
167
|
+
* - `proposed` → `accepted` then handled separately — but in practice
|
|
168
|
+
* the SLA WI never lands here, so we route to `cancelled` (legal).
|
|
169
|
+
* - everything else → `cancelled` (the V3 matrix permits `* → cancelled`
|
|
170
|
+
* from all of `queued`/`scheduled`/`accepted`/`blocked`/`escalated`).
|
|
171
|
+
*
|
|
172
|
+
* @param current - The WI's current (non-terminal) status.
|
|
173
|
+
* @returns The legal terminal status to transition into.
|
|
174
|
+
*/
|
|
175
|
+
export function pickResolveTarget(current) {
|
|
176
|
+
if (current === 'running')
|
|
177
|
+
return 'done';
|
|
178
|
+
if (current === 'done_by_worker')
|
|
179
|
+
return 'verified';
|
|
180
|
+
return 'cancelled';
|
|
181
|
+
}
|
|
182
|
+
/**
|
|
183
|
+
* Pick the legal terminal status for FAILING an SLA-tracked WI on
|
|
184
|
+
* escalation timeout (10-minute miss).
|
|
185
|
+
*
|
|
186
|
+
* Mapping:
|
|
187
|
+
* - `running` → `failed` (canonical fail edge).
|
|
188
|
+
* - `done_by_worker` → `rejected` (the only fail-shaped edge from
|
|
189
|
+
* `done_by_worker`; `done_by_worker → failed` is illegal).
|
|
190
|
+
* - everything else → `cancelled` (queued/scheduled/etc. cannot reach
|
|
191
|
+
* `failed` directly, so we route them to `cancelled` — matches the
|
|
192
|
+
* "we gave up tracking, nothing actually failed" semantic).
|
|
193
|
+
*
|
|
194
|
+
* @param current - The WI's current (non-terminal) status.
|
|
195
|
+
* @returns The legal terminal status to transition into.
|
|
196
|
+
*/
|
|
197
|
+
export function pickFailTarget(current) {
|
|
198
|
+
if (current === 'running')
|
|
199
|
+
return 'failed';
|
|
200
|
+
if (current === 'done_by_worker')
|
|
201
|
+
return 'rejected';
|
|
202
|
+
return 'cancelled';
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Compute the legal Request status path from the current state to `done`.
|
|
206
|
+
* Returns an empty array if the Request is already terminal (no work to
|
|
207
|
+
* do) — the call site is expected to guard for this.
|
|
208
|
+
*
|
|
209
|
+
* Per `REQUEST_TRANSITIONS` in `types/v2/request.types.ts`:
|
|
210
|
+
* - `open` → `done` (direct)
|
|
211
|
+
* - `running` → `done` (direct)
|
|
212
|
+
* - `waiting_confirmation` → `done` (direct)
|
|
213
|
+
* - `ready` → `running` → `done` (two-step; ready→done illegal)
|
|
214
|
+
* - `blocked` → `running` → `done` (two-step; blocked→done illegal)
|
|
215
|
+
*
|
|
216
|
+
* @param from - Current Request status (must be non-terminal).
|
|
217
|
+
* @returns Ordered array of statuses to transition through (excluding `from`).
|
|
218
|
+
*/
|
|
219
|
+
export function closeRequestPath(from) {
|
|
220
|
+
if (from === 'done' || from === 'cancelled')
|
|
221
|
+
return [];
|
|
222
|
+
if (isValidRequestTransition(from, 'done'))
|
|
223
|
+
return ['done'];
|
|
224
|
+
// ready / blocked: route via running.
|
|
225
|
+
if (isValidRequestTransition(from, 'running') && isValidRequestTransition('running', 'done')) {
|
|
226
|
+
return ['running', 'done'];
|
|
227
|
+
}
|
|
228
|
+
return [];
|
|
229
|
+
}
|
|
230
|
+
/**
|
|
231
|
+
* Terminal WorkItem statuses — the SLA timers no-op when the WI has reached
|
|
232
|
+
* any of these by the time the timer fires. Aliases
|
|
233
|
+
* {@link SLA_TERMINAL_WORK_ITEM_STATUSES} (work-item.types.ts) per Arch's
|
|
234
|
+
* N2 hoist on PR #357 — single source of truth for the broader 5-element
|
|
235
|
+
* "exited active queue" set, replacing the previously duplicated local
|
|
236
|
+
* constant. The local alias is retained so the existing call sites in
|
|
237
|
+
* this file stay terse and the diff stays minimal.
|
|
238
|
+
*/
|
|
239
|
+
const TERMINAL_WI_STATUSES = SLA_TERMINAL_WORK_ITEM_STATUSES;
|
|
240
|
+
/**
|
|
241
|
+
* Reason tags that represent a VERIFIED actual reply / decomposition by an
|
|
242
|
+
* agent and therefore permit the parent Request to cascade-close to `done`.
|
|
243
|
+
*
|
|
244
|
+
* Defense-in-depth gate for {@link RequestSlaSubscriber.maybeCloseRequest}
|
|
245
|
+
* (Steve 2026-04-30 incident): even if an upstream caller somehow invokes
|
|
246
|
+
* `markResolved` with a non-reply reason, the Request must NOT be flipped
|
|
247
|
+
* to `done` unless the reason is one of these verified paths.
|
|
248
|
+
*
|
|
249
|
+
* - `orc_reply` — slackResolve callback fired (real orc reply via
|
|
250
|
+
* reply-slack skill). Gated by the `fromOrcReply`
|
|
251
|
+
* flag on the orchestrator bridge so timeout
|
|
252
|
+
* placeholders cannot reach this branch.
|
|
253
|
+
* - `chatv2_reply` — chat-v2 controller persisted an agent-typed
|
|
254
|
+
* reply to the channel (real agent reply).
|
|
255
|
+
* - `workitem_decompose` — the orc decomposed the Request into other WIs;
|
|
256
|
+
* those WIs carry the actual work, so the
|
|
257
|
+
* respond_to_user tracker is silenced and the
|
|
258
|
+
* Request close is gated separately by the
|
|
259
|
+
* sibling-count check.
|
|
260
|
+
*
|
|
261
|
+
* Any other reason ({@link RequestSlaSubscriber.failOrphanRespondWi} fires
|
|
262
|
+
* `escalation_timeout`, callers MAY pass arbitrary diagnostic strings) is
|
|
263
|
+
* treated as "do NOT auto-close the parent Request".
|
|
264
|
+
*/
|
|
265
|
+
export const VERIFIED_REPLY_REASONS = new Set([
|
|
266
|
+
'orc_reply',
|
|
267
|
+
'chatv2_reply',
|
|
268
|
+
'workitem_decompose',
|
|
269
|
+
// Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch E):
|
|
270
|
+
// second-pass close-attempt after the orc_reply grace window has elapsed.
|
|
271
|
+
// The first attempt that defers via setTimeout always re-enters with this
|
|
272
|
+
// reason, so it must be in the verified set to pass the
|
|
273
|
+
// {@link RequestSlaSubscriber.maybeCloseRequest} defense gate.
|
|
274
|
+
'orc_reply_recheck',
|
|
275
|
+
// 2026-05-21 closie incident: orc replied directly AFTER decomposition
|
|
276
|
+
// already cleared the SLA indexes. The fallback path in
|
|
277
|
+
// {@link RequestSlaSubscriber.resolveOrphanedRequestByThread} /
|
|
278
|
+
// {@link RequestSlaSubscriber.resolveOrphanedRequestByChatV2}
|
|
279
|
+
// re-enters {@link RequestSlaSubscriber.maybeCloseRequest} with these
|
|
280
|
+
// tags; both must pass the verified-reply gate.
|
|
281
|
+
'orc_reply_after_decompose',
|
|
282
|
+
'chatv2_reply_after_decompose',
|
|
283
|
+
]);
|
|
284
|
+
/**
|
|
285
|
+
* Pipeline-#4 fix (Patch E) — grace window for the orc_reply cascade-close.
|
|
286
|
+
*
|
|
287
|
+
* When the orc replies to a Slack thread within {@link ORC_REPLY_GRACE_AGE_MS}
|
|
288
|
+
* of Request creation AND the Request has zero linked WorkItems, defer the
|
|
289
|
+
* cascade close by {@link ORC_REPLY_GRACE_MS} for one re-check. If the orc
|
|
290
|
+
* decomposed the Request during the window, the sibling-count gate inside
|
|
291
|
+
* {@link RequestSlaSubscriber.maybeCloseRequest} catches the new WIs on the
|
|
292
|
+
* second pass and the close is suppressed for normal lifecycle.
|
|
293
|
+
*
|
|
294
|
+
* Older Requests (creation-age ≥ grace) skip the deferral and close
|
|
295
|
+
* immediately to preserve legacy behaviour.
|
|
296
|
+
*/
|
|
297
|
+
const ORC_REPLY_GRACE_AGE_MS = 60_000; // 60s after Request creation
|
|
298
|
+
const ORC_REPLY_GRACE_MS = 30_000; // 30s second-pass deferral
|
|
299
|
+
// ---------------------------------------------------------------------------
|
|
300
|
+
// Helpers
|
|
301
|
+
// ---------------------------------------------------------------------------
|
|
302
|
+
/**
|
|
303
|
+
* Extract the Slack thread timestamp from a Request's
|
|
304
|
+
* `sourceConversationItemId`. The slack-orchestrator-bridge stamps these
|
|
305
|
+
* as `slack-${channelId}-${ts}` (see slack-orchestrator-bridge.ts:372),
|
|
306
|
+
* so the trailing dotted-decimal segment is the threadTs.
|
|
307
|
+
*
|
|
308
|
+
* @param sourceId - The Request's sourceConversationItemId
|
|
309
|
+
* @returns The threadTs, or null if the id doesn't match the Slack shape
|
|
310
|
+
*/
|
|
311
|
+
export function extractSlackThreadTs(sourceId) {
|
|
312
|
+
if (!sourceId || !sourceId.startsWith('slack-'))
|
|
313
|
+
return null;
|
|
314
|
+
// 2026-05-13 dogfood fix: thread-reply Requests use the encoding
|
|
315
|
+
// `slack-{channelId}-{threadRoot}-msg-{messageTs}` so the SLA index
|
|
316
|
+
// keys on the thread root (which is what orc replies to). Strip the
|
|
317
|
+
// optional `-msg-{ts}` suffix before extracting the trailing ts.
|
|
318
|
+
// Top-level message ids (`slack-{channelId}-{ts}`) are unchanged.
|
|
319
|
+
const stripped = sourceId.replace(/-msg-\d+\.\d+$/, '');
|
|
320
|
+
// slack-${channelId}-${ts}; channelId may not contain dashes, but ts is
|
|
321
|
+
// dotted-decimal. We split on '-' and take the last segment as ts.
|
|
322
|
+
const lastDash = stripped.lastIndexOf('-');
|
|
323
|
+
if (lastDash < 0 || lastDash === stripped.length - 1)
|
|
324
|
+
return null;
|
|
325
|
+
const ts = stripped.slice(lastDash + 1);
|
|
326
|
+
// ts looks like 1772899923.865659 — at minimum digits + '.'
|
|
327
|
+
if (!/^\d+\.\d+$/.test(ts))
|
|
328
|
+
return null;
|
|
329
|
+
return ts;
|
|
330
|
+
}
|
|
331
|
+
/**
|
|
332
|
+
* Extract the Slack channel id from a sourceConversationItemId.
|
|
333
|
+
*
|
|
334
|
+
* @param sourceId - The Request's sourceConversationItemId
|
|
335
|
+
* @returns The channelId, or null if the id doesn't match the Slack shape
|
|
336
|
+
*/
|
|
337
|
+
export function extractSlackChannelId(sourceId) {
|
|
338
|
+
if (!sourceId || !sourceId.startsWith('slack-'))
|
|
339
|
+
return null;
|
|
340
|
+
// Strip the optional `-msg-{ts}` suffix (thread-reply encoding) so the
|
|
341
|
+
// channelId is sliced from the threadRoot form, not from the messageTs.
|
|
342
|
+
const stripped = sourceId.replace(/-msg-\d+\.\d+$/, '');
|
|
343
|
+
const rest = stripped.slice('slack-'.length);
|
|
344
|
+
const lastDash = rest.lastIndexOf('-');
|
|
345
|
+
if (lastDash < 1)
|
|
346
|
+
return null;
|
|
347
|
+
return rest.slice(0, lastDash);
|
|
348
|
+
}
|
|
349
|
+
/**
|
|
350
|
+
* Inter-field delimiter inside a chat-v2 sourceConversationItemId.
|
|
351
|
+
* Picked as the double-underscore `__` because production channel +
|
|
352
|
+
* message ids are minted via `randomUUID()` (4 dashes per UUID) — a
|
|
353
|
+
* single-dash delimiter would collide with the embedded UUID dashes
|
|
354
|
+
* and corrupt the round-trip. UUIDs are hex-digits + dashes only and
|
|
355
|
+
* cannot contain `_`, so `__` is collision-free against any current
|
|
356
|
+
* or future hex-shaped id. See Arch on PR #364 / INBOUND-2.f1.
|
|
357
|
+
*/
|
|
358
|
+
const CHATV2_FIELD_DELIM = '__';
|
|
359
|
+
/**
|
|
360
|
+
* Extract the chat-v2 channel id from a Request's
|
|
361
|
+
* `sourceConversationItemId`. The chat-v2 controller (INBOUND-2) stamps
|
|
362
|
+
* these as `chatv2-${channelId}__${messageId}` — UUID-safe delimiter.
|
|
363
|
+
*
|
|
364
|
+
* @param sourceId - The Request's sourceConversationItemId
|
|
365
|
+
* @returns The channelId, or null if the id doesn't match the chat-v2 shape
|
|
366
|
+
*/
|
|
367
|
+
export function extractChatV2ChannelId(sourceId) {
|
|
368
|
+
if (!sourceId || !sourceId.startsWith('chatv2-'))
|
|
369
|
+
return null;
|
|
370
|
+
const rest = sourceId.slice('chatv2-'.length);
|
|
371
|
+
const sep = rest.indexOf(CHATV2_FIELD_DELIM);
|
|
372
|
+
if (sep < 1)
|
|
373
|
+
return null;
|
|
374
|
+
return rest.slice(0, sep);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Extract the chat-v2 message id from a `chatv2-${channelId}__${messageId}`
|
|
378
|
+
* sourceConversationItemId. The messageId acts as the auto-close lookup
|
|
379
|
+
* key analog to a Slack threadTs.
|
|
380
|
+
*
|
|
381
|
+
* @param sourceId - The Request's sourceConversationItemId
|
|
382
|
+
* @returns The messageId, or null if the id doesn't match the chat-v2 shape
|
|
383
|
+
*/
|
|
384
|
+
export function extractChatV2MessageId(sourceId) {
|
|
385
|
+
if (!sourceId || !sourceId.startsWith('chatv2-'))
|
|
386
|
+
return null;
|
|
387
|
+
const rest = sourceId.slice('chatv2-'.length);
|
|
388
|
+
const sep = rest.indexOf(CHATV2_FIELD_DELIM);
|
|
389
|
+
if (sep < 0)
|
|
390
|
+
return null;
|
|
391
|
+
const id = rest.slice(sep + CHATV2_FIELD_DELIM.length);
|
|
392
|
+
if (id.length === 0)
|
|
393
|
+
return null;
|
|
394
|
+
return id;
|
|
395
|
+
}
|
|
396
|
+
// ---------------------------------------------------------------------------
|
|
397
|
+
// Errors
|
|
398
|
+
// ---------------------------------------------------------------------------
|
|
399
|
+
/**
|
|
400
|
+
* Thrown when the subscriber cannot resolve a Request for an event. The
|
|
401
|
+
* Request was deleted between publish and dispatch — rare, but observable
|
|
402
|
+
* in tests, so we encode the case as a typed error.
|
|
403
|
+
*/
|
|
404
|
+
export class RequestNotFoundError extends Error {
|
|
405
|
+
constructor(message) {
|
|
406
|
+
super(message);
|
|
407
|
+
this.name = 'RequestNotFoundError';
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
/**
|
|
411
|
+
* RequestSlaSubscriber — the INBOUND-1 deliverable.
|
|
412
|
+
*
|
|
413
|
+
* @example
|
|
414
|
+
* ```typescript
|
|
415
|
+
* const sub = RequestSlaSubscriber.boot(eventBus, slackEscalationFn);
|
|
416
|
+
* sub.start();
|
|
417
|
+
* // … on shutdown:
|
|
418
|
+
* sub.stop();
|
|
419
|
+
* ```
|
|
420
|
+
*/
|
|
421
|
+
export class RequestSlaSubscriber {
|
|
422
|
+
eventBus;
|
|
423
|
+
taskPool;
|
|
424
|
+
requestService;
|
|
425
|
+
sendEscalationDm;
|
|
426
|
+
orchestratorSession;
|
|
427
|
+
slaMs;
|
|
428
|
+
escalationMs;
|
|
429
|
+
inboundTags;
|
|
430
|
+
logger;
|
|
431
|
+
unsubscribers = [];
|
|
432
|
+
started = false;
|
|
433
|
+
/** requestId → tracked record; primary index for breach handlers. */
|
|
434
|
+
trackedByRequest = new Map();
|
|
435
|
+
/** Slack threadTs → requestId; secondary index for {@link markResolvedByThread}. */
|
|
436
|
+
threadIndex = new Map();
|
|
437
|
+
/**
|
|
438
|
+
* chat-v2 channelId → requestId. INBOUND-2 secondary index used by
|
|
439
|
+
* {@link markResolvedByChatV2}. Last-write-wins when multiple inbound
|
|
440
|
+
* Requests pile up in the same channel — v1 polish accepts the simpler
|
|
441
|
+
* 1:1 semantics; the orphan handler still cleans the WI on escalation.
|
|
442
|
+
*/
|
|
443
|
+
chatV2Index = new Map();
|
|
444
|
+
/** In-flight async dispatch promises (test affordance). */
|
|
445
|
+
pendingDispatches = new Set();
|
|
446
|
+
constructor(deps) {
|
|
447
|
+
this.eventBus = deps.eventBus;
|
|
448
|
+
this.taskPool = deps.taskPool;
|
|
449
|
+
this.requestService = deps.requestService;
|
|
450
|
+
this.sendEscalationDm = deps.sendEscalationDm;
|
|
451
|
+
this.orchestratorSession = deps.orchestratorSession ?? ORCHESTRATOR_SESSION_NAME;
|
|
452
|
+
this.slaMs = deps.slaMs ?? DEFAULT_SLA_MS;
|
|
453
|
+
this.escalationMs = deps.escalationMs ?? DEFAULT_ESCALATION_MS;
|
|
454
|
+
this.inboundTags = new Set(deps.inboundTags ?? DEFAULT_INBOUND_TAGS);
|
|
455
|
+
this.logger =
|
|
456
|
+
deps.logger ?? LoggerService.getInstance().createComponentLogger('RequestSlaSubscriber');
|
|
457
|
+
}
|
|
458
|
+
/**
|
|
459
|
+
* Production wiring helper. Tests should use the constructor directly.
|
|
460
|
+
*
|
|
461
|
+
* @param eventBus - Live event bus
|
|
462
|
+
* @param requestService - Live RequestService
|
|
463
|
+
* @param taskPool - Live TaskPoolService
|
|
464
|
+
* @param sendEscalationDm - Slack DM callback for the 10-minute escalation
|
|
465
|
+
* @returns A subscriber ready to `start()`
|
|
466
|
+
*/
|
|
467
|
+
static boot(eventBus, requestService, taskPool, sendEscalationDm) {
|
|
468
|
+
return new RequestSlaSubscriber({
|
|
469
|
+
eventBus,
|
|
470
|
+
taskPool,
|
|
471
|
+
requestService,
|
|
472
|
+
sendEscalationDm,
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
/**
|
|
476
|
+
* Subscribe + register the in-process handlers. Idempotent.
|
|
477
|
+
*/
|
|
478
|
+
start() {
|
|
479
|
+
if (this.started)
|
|
480
|
+
return;
|
|
481
|
+
this.started = true;
|
|
482
|
+
for (const eventType of REQUEST_SLA_SUBSCRIBED_EVENTS) {
|
|
483
|
+
this.unsubscribers.push(this.eventBus.onInProcess(eventType, (e) => this.safeDispatch(eventType, e)));
|
|
484
|
+
}
|
|
485
|
+
this.logger.info('RequestSlaSubscriber subscribed', {
|
|
486
|
+
eventTypes: REQUEST_SLA_SUBSCRIBED_EVENTS,
|
|
487
|
+
slaMs: this.slaMs,
|
|
488
|
+
escalationMs: this.escalationMs,
|
|
489
|
+
orchestratorSession: this.orchestratorSession,
|
|
490
|
+
});
|
|
491
|
+
}
|
|
492
|
+
/**
|
|
493
|
+
* Number of inbound user Requests still awaiting an orchestrator response —
|
|
494
|
+
* i.e. live `respond_to_user` SLA trackers that have neither been answered
|
|
495
|
+
* nor breached-and-cleared. Powers the `/health` orchestrator-liveness signal
|
|
496
|
+
* (issue #686): `agents.active === 0` while this is `> 0` means inbound user
|
|
497
|
+
* messages are queued with no one to answer them (the silent 假死 symptom).
|
|
498
|
+
*
|
|
499
|
+
* @returns Count of pending tracked `respond_to_user` work items.
|
|
500
|
+
*/
|
|
501
|
+
getPendingUserRequestCount() {
|
|
502
|
+
return this.trackedByRequest.size;
|
|
503
|
+
}
|
|
504
|
+
/**
|
|
505
|
+
* Wait for in-flight async dispatches to settle. Test affordance.
|
|
506
|
+
*/
|
|
507
|
+
async flushPending() {
|
|
508
|
+
while (this.pendingDispatches.size > 0) {
|
|
509
|
+
const inFlight = Array.from(this.pendingDispatches);
|
|
510
|
+
await Promise.allSettled(inFlight);
|
|
511
|
+
}
|
|
512
|
+
}
|
|
513
|
+
/**
|
|
514
|
+
* Detach subscriptions + clear all SLA timers. Safe to call repeatedly.
|
|
515
|
+
*/
|
|
516
|
+
stop() {
|
|
517
|
+
for (const unsubscribe of this.unsubscribers) {
|
|
518
|
+
try {
|
|
519
|
+
unsubscribe();
|
|
520
|
+
}
|
|
521
|
+
catch (err) {
|
|
522
|
+
this.logger.warn('SLA unsubscribe threw', { error: formatError(err) });
|
|
523
|
+
}
|
|
524
|
+
}
|
|
525
|
+
this.unsubscribers = [];
|
|
526
|
+
for (const tracked of this.trackedByRequest.values()) {
|
|
527
|
+
clearTimeout(tracked.breachTimer);
|
|
528
|
+
clearTimeout(tracked.escalationTimer);
|
|
529
|
+
}
|
|
530
|
+
this.trackedByRequest.clear();
|
|
531
|
+
this.threadIndex.clear();
|
|
532
|
+
this.chatV2Index.clear();
|
|
533
|
+
this.started = false;
|
|
534
|
+
this.logger.info('RequestSlaSubscriber stopped');
|
|
535
|
+
}
|
|
536
|
+
/**
|
|
537
|
+
* Mark an in-flight respond_to_user WI as done because the orchestrator
|
|
538
|
+
* just replied to the matching Slack thread. Called by
|
|
539
|
+
* {@link slack-orchestrator-bridge.sendSlackResponse}.
|
|
540
|
+
*
|
|
541
|
+
* Best-effort: a non-Slack-shaped or unknown threadTs is a no-op.
|
|
542
|
+
*
|
|
543
|
+
* Defensive retry (Steve 2026-05-03 auto-nudge variant 2): the bridge
|
|
544
|
+
* may call this *before* {@link handleRequestCreated} has populated
|
|
545
|
+
* `threadIndex` (e.g. QueueProcessor fires `slackResolve('')` right
|
|
546
|
+
* after delivery, ~10ms before the SLA subscriber finishes its async
|
|
547
|
+
* `request:created` handler). On index-miss we schedule a single
|
|
548
|
+
* delayed retry to absorb that race without holding up the caller.
|
|
549
|
+
* The reordering inside {@link handleRequestCreated} is the primary
|
|
550
|
+
* fix; this retry is belt-and-suspenders to absorb any residual
|
|
551
|
+
* `await requestService.getById` skew.
|
|
552
|
+
*
|
|
553
|
+
* @param threadTs - The Slack message timestamp the orc replied to
|
|
554
|
+
*/
|
|
555
|
+
async markResolvedByThread(threadTs) {
|
|
556
|
+
if (!threadTs)
|
|
557
|
+
return;
|
|
558
|
+
const requestId = this.threadIndex.get(threadTs);
|
|
559
|
+
if (requestId) {
|
|
560
|
+
await this.markResolved(requestId, 'orc_reply');
|
|
561
|
+
return;
|
|
562
|
+
}
|
|
563
|
+
// Index miss — schedule a single retry after the
|
|
564
|
+
// `request:created` handler has had time to populate the index.
|
|
565
|
+
// 250ms covers in-memory getById + microtask scheduling + a margin.
|
|
566
|
+
const retry = setTimeout(async () => {
|
|
567
|
+
const retryRequestId = this.threadIndex.get(threadTs);
|
|
568
|
+
if (retryRequestId) {
|
|
569
|
+
void this.markResolved(retryRequestId, 'orc_reply');
|
|
570
|
+
return;
|
|
571
|
+
}
|
|
572
|
+
// Retry still missed. The most common reason isn't a race — it's
|
|
573
|
+
// that the Request was already decomposed (workitem_decompose),
|
|
574
|
+
// which clears `threadIndex` even though the Request is still
|
|
575
|
+
// open with non-terminal child WIs. Orc just replied directly
|
|
576
|
+
// anyway (e.g. the question turned out to be a one-shot answer),
|
|
577
|
+
// so those child WIs are now orphaned and would otherwise sit
|
|
578
|
+
// queued/blocked forever.
|
|
579
|
+
//
|
|
580
|
+
// 2026-05-21 closie incident: Request 2adc23a3 decomposed into
|
|
581
|
+
// Plan/Execute/Review, orc replied at 04:01 directly, but the 3
|
|
582
|
+
// children stayed queued/blocked for 8+ hours until manual
|
|
583
|
+
// cleanup. The status-check skill kept reporting "0/3 done".
|
|
584
|
+
try {
|
|
585
|
+
await this.resolveOrphanedRequestByThread(threadTs);
|
|
586
|
+
}
|
|
587
|
+
catch (err) {
|
|
588
|
+
this.logger.warn('Orphaned-Request fallback failed (non-fatal)', {
|
|
589
|
+
threadTs,
|
|
590
|
+
error: formatError(err),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
593
|
+
}, MARK_RESOLVED_RETRY_MS);
|
|
594
|
+
retry.unref?.();
|
|
595
|
+
}
|
|
596
|
+
/**
|
|
597
|
+
* INBOUND-2: mark the in-flight respond_to_user WI as done because an
|
|
598
|
+
* agent replied in the chat-v2 channel where the user's message arrived.
|
|
599
|
+
* Called by `chat-v2.controller.sendMessage` after a `senderType=agent`
|
|
600
|
+
* message persists to the channel.
|
|
601
|
+
*
|
|
602
|
+
* Best-effort: a non-tracked or unknown channelId is a no-op.
|
|
603
|
+
*
|
|
604
|
+
* @param channelId - The chat-v2 channel where the agent just replied
|
|
605
|
+
*/
|
|
606
|
+
async markResolvedByChatV2(channelId) {
|
|
607
|
+
if (!channelId)
|
|
608
|
+
return;
|
|
609
|
+
const requestId = this.chatV2Index.get(channelId);
|
|
610
|
+
if (requestId) {
|
|
611
|
+
await this.markResolved(requestId, 'chatv2_reply');
|
|
612
|
+
return;
|
|
613
|
+
}
|
|
614
|
+
// Mirror the markResolvedByThread retry. Same race shape applies on
|
|
615
|
+
// chat-v2: an agent reply may land before the `request:created`
|
|
616
|
+
// handler has populated `chatV2Index`.
|
|
617
|
+
const retry = setTimeout(async () => {
|
|
618
|
+
const retryRequestId = this.chatV2Index.get(channelId);
|
|
619
|
+
if (retryRequestId) {
|
|
620
|
+
void this.markResolved(retryRequestId, 'chatv2_reply');
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
// Retry still missed — same orphan-after-decompose path as the
|
|
624
|
+
// Slack case in {@link markResolvedByThread}. Look up the Request
|
|
625
|
+
// by source convo id directly and clean up.
|
|
626
|
+
try {
|
|
627
|
+
await this.resolveOrphanedRequestByChatV2(channelId);
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
this.logger.warn('Orphaned-Request fallback failed (non-fatal)', {
|
|
631
|
+
channelId,
|
|
632
|
+
error: formatError(err),
|
|
633
|
+
});
|
|
634
|
+
}
|
|
635
|
+
}, MARK_RESOLVED_RETRY_MS);
|
|
636
|
+
retry.unref?.();
|
|
637
|
+
}
|
|
638
|
+
/**
|
|
639
|
+
* Orphan-cleanup path for the Slack case in {@link markResolvedByThread}.
|
|
640
|
+
*
|
|
641
|
+
* The threadIndex / chatV2Index are populated when an SLA tracker is
|
|
642
|
+
* registered for the Request and cleared whenever {@link markResolved}
|
|
643
|
+
* runs — including the `workitem_decompose` resolve. After decomposition,
|
|
644
|
+
* the Request stays open with non-terminal child WIs while the SLA
|
|
645
|
+
* indexes are empty. If the orc then replies directly in the original
|
|
646
|
+
* thread, neither the primary `threadIndex` lookup nor the 250ms retry
|
|
647
|
+
* will find anything, and the orphan child WIs sit queued/blocked
|
|
648
|
+
* indefinitely (closie incident, 2026-05-21).
|
|
649
|
+
*
|
|
650
|
+
* This fallback authoritatively scans `RequestService.listAll()` for an
|
|
651
|
+
* open Request whose `sourceConversationItemId` references `threadTs`,
|
|
652
|
+
* then force-removes any non-terminal child WIs from the pool and
|
|
653
|
+
* cascades the close. We bypass the index because the index is the
|
|
654
|
+
* very thing that's stale.
|
|
655
|
+
*
|
|
656
|
+
* @param threadTs - The Slack message timestamp the orc replied to
|
|
657
|
+
*/
|
|
658
|
+
async resolveOrphanedRequestByThread(threadTs) {
|
|
659
|
+
const all = await this.requestService.listAll();
|
|
660
|
+
const match = all.find((r) => !TERMINAL_REQUEST_STATUSES.has(r.status) &&
|
|
661
|
+
extractSlackThreadTs(r.sourceConversationItemId) === threadTs);
|
|
662
|
+
if (!match) {
|
|
663
|
+
this.logger.debug('No open Request matches threadTs — nothing to clean up', { threadTs });
|
|
664
|
+
return;
|
|
665
|
+
}
|
|
666
|
+
await this.cancelOrphansAndCloseRequest(match.id, 'orc_reply_after_decompose');
|
|
667
|
+
}
|
|
668
|
+
/**
|
|
669
|
+
* Orphan-cleanup path for the chat-v2 case in
|
|
670
|
+
* {@link markResolvedByChatV2}. Mirrors {@link resolveOrphanedRequestByThread}
|
|
671
|
+
* but matches on `chatV2-` source id shape.
|
|
672
|
+
*
|
|
673
|
+
* @param channelId - The chat-v2 channel id where the agent replied
|
|
674
|
+
*/
|
|
675
|
+
async resolveOrphanedRequestByChatV2(channelId) {
|
|
676
|
+
const all = await this.requestService.listAll();
|
|
677
|
+
const match = all.find((r) => !TERMINAL_REQUEST_STATUSES.has(r.status) &&
|
|
678
|
+
extractChatV2ChannelId(r.sourceConversationItemId) === channelId);
|
|
679
|
+
if (!match) {
|
|
680
|
+
this.logger.debug('No open Request matches channelId — nothing to clean up', { channelId });
|
|
681
|
+
return;
|
|
682
|
+
}
|
|
683
|
+
await this.cancelOrphansAndCloseRequest(match.id, 'chatv2_reply_after_decompose');
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Cancel all non-terminal WorkItems belonging to a Request, then cascade
|
|
687
|
+
* the Request to `done`. Shared tail of
|
|
688
|
+
* {@link resolveOrphanedRequestByThread} and
|
|
689
|
+
* {@link resolveOrphanedRequestByChatV2}.
|
|
690
|
+
*
|
|
691
|
+
* Each WI is transitioned individually so a single failure (illegal
|
|
692
|
+
* transition, stale claim) does not block the others — the cascade
|
|
693
|
+
* close at the end only fires if at least one WI flipped to terminal
|
|
694
|
+
* (otherwise `countOtherActiveWorkItems` will keep the Request open
|
|
695
|
+
* via {@link maybeCloseRequest}'s sibling-count gate).
|
|
696
|
+
*
|
|
697
|
+
* @param requestId - The Request to clean up
|
|
698
|
+
* @param reason - Diagnostic tag recorded on each cancelled WI's
|
|
699
|
+
* metadata and on the Request's `result` field
|
|
700
|
+
*/
|
|
701
|
+
async cancelOrphansAndCloseRequest(requestId, reason) {
|
|
702
|
+
const all = await this.taskPool.getAllItems();
|
|
703
|
+
let cancelled = 0;
|
|
704
|
+
for (const wi of all) {
|
|
705
|
+
if (wi.requestId !== requestId)
|
|
706
|
+
continue;
|
|
707
|
+
if (TERMINAL_WI_STATUSES.has(wi.status))
|
|
708
|
+
continue;
|
|
709
|
+
try {
|
|
710
|
+
await this.taskPool.transitionStatus(wi.id, 'cancelled', 'system', (item) => {
|
|
711
|
+
item.metadata = {
|
|
712
|
+
...(item.metadata ?? {}),
|
|
713
|
+
slaResolvedReason: reason,
|
|
714
|
+
slaResolvedAt: new Date().toISOString(),
|
|
715
|
+
};
|
|
716
|
+
}, `SLA auto-resolved: ${reason}`);
|
|
717
|
+
cancelled += 1;
|
|
718
|
+
}
|
|
719
|
+
catch (err) {
|
|
720
|
+
// Most likely cause: a transition we can't satisfy in the state
|
|
721
|
+
// machine (e.g. a status we didn't anticipate). Log + skip — the
|
|
722
|
+
// cascade close's sibling-count gate will keep the Request open
|
|
723
|
+
// if any WIs remain non-terminal, which is the safer outcome.
|
|
724
|
+
this.logger.warn('Could not cancel orphan WI; leaving in current state', {
|
|
725
|
+
workItemId: wi.id,
|
|
726
|
+
status: wi.status,
|
|
727
|
+
error: formatError(err),
|
|
728
|
+
});
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
this.logger.info('Orphaned-Request cleanup: cancelled non-terminal child WIs', {
|
|
732
|
+
requestId,
|
|
733
|
+
reason,
|
|
734
|
+
cancelled,
|
|
735
|
+
});
|
|
736
|
+
await this.maybeCloseRequest(requestId, reason);
|
|
737
|
+
}
|
|
738
|
+
/**
|
|
739
|
+
* Mark an in-flight respond_to_user WI as resolved by Request id (e.g. the
|
|
740
|
+
* orc replied on Slack, or decomposed the Request into other WorkItems and
|
|
741
|
+
* we want to silence the SLA chain). After the WI transitions, we also
|
|
742
|
+
* cascade the close to the parent Request when this was the last
|
|
743
|
+
* non-terminal WI for it (Steve 2026-04-29: Requests stuck on "Active" in
|
|
744
|
+
* /tasks UI even after the team replied).
|
|
745
|
+
*
|
|
746
|
+
* Transition path is selected from the WI's current status to satisfy the
|
|
747
|
+
* V3 state machine — `transitionStatus` enforces `WORK_ITEM_TRANSITIONS`
|
|
748
|
+
* and `queued → done` is NOT a legal edge:
|
|
749
|
+
* - `queued` → `cancelled`: SLA tracker was a placeholder, never claimed.
|
|
750
|
+
* Semantic: "no longer needed because the orc handled this directly".
|
|
751
|
+
* - `running` → `done`: Someone explicitly claimed the SLA WI; close
|
|
752
|
+
* it as a normal completion.
|
|
753
|
+
* - terminal status: no-op (already settled).
|
|
754
|
+
*
|
|
755
|
+
* Before this fix, `markResolved` always called `transitionStatus(_, 'done')`
|
|
756
|
+
* which threw on the queued case, the catch swallowed it at warn level, the
|
|
757
|
+
* WI stayed `queued` forever, and the Request never closed — the
|
|
758
|
+
* user-reported "Active count never goes down" bug.
|
|
759
|
+
*
|
|
760
|
+
* @param requestId - The Request whose SLA chain should be silenced
|
|
761
|
+
* @param reason - Diagnostic tag (`orc_reply` / `chatv2_reply` /
|
|
762
|
+
* `workitem_decompose`) recorded in WI metadata + Request `result`.
|
|
763
|
+
*/
|
|
764
|
+
async markResolved(requestId, reason) {
|
|
765
|
+
const tracked = this.trackedByRequest.get(requestId);
|
|
766
|
+
if (!tracked)
|
|
767
|
+
return;
|
|
768
|
+
// Clear timers BEFORE the await so a concurrent breach can't fire after
|
|
769
|
+
// we've decided to resolve.
|
|
770
|
+
clearTimeout(tracked.breachTimer);
|
|
771
|
+
clearTimeout(tracked.escalationTimer);
|
|
772
|
+
this.trackedByRequest.delete(requestId);
|
|
773
|
+
if (tracked.threadTs)
|
|
774
|
+
this.threadIndex.delete(tracked.threadTs);
|
|
775
|
+
if (tracked.chatV2ChannelId)
|
|
776
|
+
this.chatV2Index.delete(tracked.chatV2ChannelId);
|
|
777
|
+
try {
|
|
778
|
+
// The bridge can call markResolved within ~10ms of `request:created`,
|
|
779
|
+
// which means `taskPool.addToPool(wi)` inside `handleRequestCreated`
|
|
780
|
+
// may not have completed yet. Brief retry on null lookup absorbs
|
|
781
|
+
// that window without blocking the happy path.
|
|
782
|
+
const wi = await this.findWorkItemWithRetry(tracked.workItemId);
|
|
783
|
+
if (wi && !TERMINAL_WI_STATUSES.has(wi.status)) {
|
|
784
|
+
const target = pickResolveTarget(wi.status);
|
|
785
|
+
// Surface the SLA reason as cancelReason when the target is
|
|
786
|
+
// 'cancelled' so the activity timeline shows WHY (instead of
|
|
787
|
+
// an opaque "WorkItem was cancelled.").
|
|
788
|
+
await this.taskPool.transitionStatus(tracked.workItemId, target, 'system', (item) => {
|
|
789
|
+
item.metadata = {
|
|
790
|
+
...(item.metadata ?? {}),
|
|
791
|
+
slaResolvedReason: reason,
|
|
792
|
+
slaResolvedAt: new Date().toISOString(),
|
|
793
|
+
};
|
|
794
|
+
}, target === 'cancelled' ? `SLA auto-resolved: ${reason}` : undefined);
|
|
795
|
+
this.logger.info('SLA WorkItem auto-resolved', {
|
|
796
|
+
workItemId: tracked.workItemId,
|
|
797
|
+
requestId,
|
|
798
|
+
reason,
|
|
799
|
+
fromStatus: wi.status,
|
|
800
|
+
toStatus: target,
|
|
801
|
+
});
|
|
802
|
+
}
|
|
803
|
+
else if (!wi) {
|
|
804
|
+
this.logger.warn('SLA auto-resolve: WorkItem never appeared in pool after retry — addToPool likely failed silently', {
|
|
805
|
+
workItemId: tracked.workItemId,
|
|
806
|
+
requestId,
|
|
807
|
+
reason,
|
|
808
|
+
});
|
|
809
|
+
}
|
|
810
|
+
// Cascade: close the parent Request when this was the last live WI.
|
|
811
|
+
// Runs even if the WI was already terminal — covers the case where
|
|
812
|
+
// the WI was closed out-of-band but the Request didn't get cascaded.
|
|
813
|
+
await this.maybeCloseRequest(requestId, reason);
|
|
814
|
+
}
|
|
815
|
+
catch (err) {
|
|
816
|
+
this.logger.warn('SLA auto-resolve threw', {
|
|
817
|
+
workItemId: tracked.workItemId,
|
|
818
|
+
requestId,
|
|
819
|
+
error: formatError(err),
|
|
820
|
+
});
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
/**
|
|
824
|
+
* Find a WorkItem in the task pool, retrying briefly on null. Covers the
|
|
825
|
+
* race where {@link markResolved} is called before
|
|
826
|
+
* {@link handleRequestCreated} has finished `taskPool.addToPool(wi)`
|
|
827
|
+
* (Steve 2026-05-03 auto-nudge variant 2).
|
|
828
|
+
*
|
|
829
|
+
* Up to {@link FIND_WI_MAX_ATTEMPTS} attempts spaced by
|
|
830
|
+
* {@link FIND_WI_RETRY_INTERVAL_MS}. Total worst-case wait stays well
|
|
831
|
+
* below the 5min SLA window. Resolves with `null` if the WI never
|
|
832
|
+
* appears (e.g. addToPool persistently failed); the caller logs and
|
|
833
|
+
* moves on.
|
|
834
|
+
*
|
|
835
|
+
* @param workItemId - The deterministic respond_to_user WI id
|
|
836
|
+
* @returns The WorkItem if found, or null after all retries exhausted
|
|
837
|
+
*/
|
|
838
|
+
async findWorkItemWithRetry(workItemId) {
|
|
839
|
+
for (let attempt = 0; attempt < FIND_WI_MAX_ATTEMPTS; attempt += 1) {
|
|
840
|
+
const wi = await this.taskPool.findWorkItem(workItemId);
|
|
841
|
+
if (wi)
|
|
842
|
+
return wi;
|
|
843
|
+
if (attempt === FIND_WI_MAX_ATTEMPTS - 1)
|
|
844
|
+
break;
|
|
845
|
+
await new Promise((res) => {
|
|
846
|
+
const t = setTimeout(res, FIND_WI_RETRY_INTERVAL_MS);
|
|
847
|
+
t.unref?.();
|
|
848
|
+
});
|
|
849
|
+
}
|
|
850
|
+
return null;
|
|
851
|
+
}
|
|
852
|
+
/**
|
|
853
|
+
* Cascade close the parent Request after an SLA-tracked WI resolves.
|
|
854
|
+
*
|
|
855
|
+
* The Request is moved to `done` only when ALL of:
|
|
856
|
+
* 1. `reason` is in {@link VERIFIED_REPLY_REASONS} (defense-in-depth
|
|
857
|
+
* against false-resolve paths — see Steve 2026-04-30 incident,
|
|
858
|
+
* where a `responseTimeoutMs` placeholder fired
|
|
859
|
+
* `markResolvedByThread` and cascaded the Request to `done`
|
|
860
|
+
* without an actual orc reply).
|
|
861
|
+
* 2. it exists and is not already terminal (`done`/`cancelled`), AND
|
|
862
|
+
* 3. no other non-terminal WIs remain for it (the orc may have
|
|
863
|
+
* decomposed the Request into other WIs that are still in flight —
|
|
864
|
+
* in that case we leave the Request alone and let the existing
|
|
865
|
+
* `cascadeRequestStatus` machinery in v3-data.service close it
|
|
866
|
+
* when those WIs finish).
|
|
867
|
+
*
|
|
868
|
+
* Picks the shortest legal transition path per `REQUEST_TRANSITIONS`:
|
|
869
|
+
* - `open` / `running` / `waiting_confirmation` → `done` (direct)
|
|
870
|
+
* - `ready` / `blocked` → `running` → `done`
|
|
871
|
+
*
|
|
872
|
+
* Errors are caught at the call site (markResolved); this method must
|
|
873
|
+
* never propagate, so a Request-update failure does not leak into the
|
|
874
|
+
* Slack-reply flow.
|
|
875
|
+
*
|
|
876
|
+
* @param requestId - The Request to close.
|
|
877
|
+
* @param reason - The same reason tag recorded on the WI; passed
|
|
878
|
+
* through to `Request.result` so the UI shows why it auto-closed.
|
|
879
|
+
*/
|
|
880
|
+
async maybeCloseRequest(requestId, reason) {
|
|
881
|
+
// Defense-in-depth: even if an upstream caller wires `markResolved` with
|
|
882
|
+
// a non-reply reason in the future, the cascade close is suppressed
|
|
883
|
+
// unless the reason is one we recognise as a verified actual reply or
|
|
884
|
+
// decomposition path. The primary fix lives in the orchestrator bridge
|
|
885
|
+
// (`fromOrcReply` flag); this gate is the second line of defense.
|
|
886
|
+
if (!VERIFIED_REPLY_REASONS.has(reason)) {
|
|
887
|
+
this.logger.debug('Request cascade close skipped — reason not in verified-reply set', {
|
|
888
|
+
requestId,
|
|
889
|
+
reason,
|
|
890
|
+
});
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
const request = await this.requestService.getById(requestId);
|
|
894
|
+
if (!request)
|
|
895
|
+
return;
|
|
896
|
+
if (TERMINAL_REQUEST_STATUSES.has(request.status))
|
|
897
|
+
return;
|
|
898
|
+
// Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch E):
|
|
899
|
+
// grace window for the orc_reply cascade. If the orc has replied within
|
|
900
|
+
// {@link ORC_REPLY_GRACE_AGE_MS} of Request creation AND no WorkItems are
|
|
901
|
+
// linked yet, defer the close by {@link ORC_REPLY_GRACE_MS} for one
|
|
902
|
+
// re-check pass. This gives a parallel decomposition (e.g. via
|
|
903
|
+
// break-down-request) time to land its WIs and trip the sibling-count
|
|
904
|
+
// gate below. Older Requests skip the grace and close immediately so
|
|
905
|
+
// legacy paths are unchanged.
|
|
906
|
+
//
|
|
907
|
+
// The recheck re-enters with reason='orc_reply_recheck' (in
|
|
908
|
+
// VERIFIED_REPLY_REASONS), so the second pass succeeds the gate and either
|
|
909
|
+
// closes (no siblings appeared) or is suppressed by the sibling-count
|
|
910
|
+
// gate (siblings appeared during the window).
|
|
911
|
+
if (reason === 'orc_reply') {
|
|
912
|
+
const ageMs = Date.now() - new Date(request.createdAt).getTime();
|
|
913
|
+
// `linkedSiblings` reflects FIRST-pass state; the recheck re-enters via
|
|
914
|
+
// setTimeout below and re-fetches `request` at the top of this method,
|
|
915
|
+
// so the second pass sees fresh `workItemIds` if decomposition landed
|
|
916
|
+
// during the grace window. Do not move the `linkedSiblings` calculation
|
|
917
|
+
// outside this block — its semantics are decision-local to the first
|
|
918
|
+
// pass.
|
|
919
|
+
const linkedSiblings = (request.workItemIds ?? []).length;
|
|
920
|
+
if (ageMs < ORC_REPLY_GRACE_AGE_MS && linkedSiblings === 0) {
|
|
921
|
+
this.logger.debug('Request close deferred — orc_reply grace window active', {
|
|
922
|
+
requestId,
|
|
923
|
+
ageMs,
|
|
924
|
+
graceMs: ORC_REPLY_GRACE_MS,
|
|
925
|
+
});
|
|
926
|
+
const t = setTimeout(() => {
|
|
927
|
+
void this.maybeCloseRequest(requestId, 'orc_reply_recheck').catch((err) => {
|
|
928
|
+
this.logger.warn('orc_reply_recheck cascade threw', {
|
|
929
|
+
requestId,
|
|
930
|
+
error: formatError(err),
|
|
931
|
+
});
|
|
932
|
+
});
|
|
933
|
+
}, ORC_REPLY_GRACE_MS);
|
|
934
|
+
// unref(): the recheck timer must not keep the process alive on
|
|
935
|
+
// graceful shutdown. Trade-off: pending rechecks at shutdown silently
|
|
936
|
+
// skip — acceptable for a long-running PM2 process where the
|
|
937
|
+
// observable window is bounded by SLA timers anyway. Tests use
|
|
938
|
+
// jest.advanceTimersByTime to assert the recheck path explicitly.
|
|
939
|
+
t.unref?.();
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
}
|
|
943
|
+
const otherActiveCount = await this.countOtherActiveWorkItems(requestId);
|
|
944
|
+
if (otherActiveCount > 0) {
|
|
945
|
+
this.logger.debug('Request kept open — other non-terminal WIs still in flight', {
|
|
946
|
+
requestId,
|
|
947
|
+
otherActiveCount,
|
|
948
|
+
reason,
|
|
949
|
+
});
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
const path = closeRequestPath(request.status);
|
|
953
|
+
if (path.length === 0) {
|
|
954
|
+
// Should be impossible given the TERMINAL guard above, but be defensive.
|
|
955
|
+
return;
|
|
956
|
+
}
|
|
957
|
+
try {
|
|
958
|
+
for (const next of path) {
|
|
959
|
+
await this.requestService.update(requestId, {
|
|
960
|
+
status: next,
|
|
961
|
+
...(next === 'done' ? { result: `Auto-closed by SLA: ${reason}` } : {}),
|
|
962
|
+
});
|
|
963
|
+
}
|
|
964
|
+
this.logger.info('Request auto-closed by SLA cascade', {
|
|
965
|
+
requestId,
|
|
966
|
+
from: request.status,
|
|
967
|
+
path,
|
|
968
|
+
reason,
|
|
969
|
+
});
|
|
970
|
+
}
|
|
971
|
+
catch (err) {
|
|
972
|
+
this.logger.warn('Request auto-close threw', {
|
|
973
|
+
requestId,
|
|
974
|
+
from: request.status,
|
|
975
|
+
path,
|
|
976
|
+
error: formatError(err),
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
/**
|
|
981
|
+
* Count WorkItems linked to the given Request that are NOT the SLA tracker
|
|
982
|
+
* AND are still non-terminal. Returns 0 when only the SLA tracker existed.
|
|
983
|
+
*
|
|
984
|
+
* @param requestId - The Request id to scan.
|
|
985
|
+
* @returns Count of other in-flight WorkItems.
|
|
986
|
+
*/
|
|
987
|
+
async countOtherActiveWorkItems(requestId) {
|
|
988
|
+
const slaWiId = respondToUserWorkItemId(requestId);
|
|
989
|
+
const all = await this.taskPool.getAllItems();
|
|
990
|
+
let count = 0;
|
|
991
|
+
for (const wi of all) {
|
|
992
|
+
if (wi.requestId !== requestId)
|
|
993
|
+
continue;
|
|
994
|
+
if (wi.id === slaWiId)
|
|
995
|
+
continue;
|
|
996
|
+
if (TERMINAL_WI_STATUSES.has(wi.status))
|
|
997
|
+
continue;
|
|
998
|
+
count += 1;
|
|
999
|
+
}
|
|
1000
|
+
return count;
|
|
1001
|
+
}
|
|
1002
|
+
/**
|
|
1003
|
+
* Snapshot of the tracked-WI count. Test affordance.
|
|
1004
|
+
*/
|
|
1005
|
+
get trackedCount() {
|
|
1006
|
+
return this.trackedByRequest.size;
|
|
1007
|
+
}
|
|
1008
|
+
// -------------------------------------------------------------------------
|
|
1009
|
+
// Internals
|
|
1010
|
+
// -------------------------------------------------------------------------
|
|
1011
|
+
/**
|
|
1012
|
+
* `request:created` handler. Filters by inbound tag, creates the
|
|
1013
|
+
* respond_to_user WI, and schedules the breach + escalation timers.
|
|
1014
|
+
*/
|
|
1015
|
+
handleRequestCreated = async (event) => {
|
|
1016
|
+
if (!event.requestId) {
|
|
1017
|
+
this.logger.debug('request:created event missing requestId', { eventId: event.id });
|
|
1018
|
+
return;
|
|
1019
|
+
}
|
|
1020
|
+
const request = await this.requestService.getById(event.requestId);
|
|
1021
|
+
if (!request) {
|
|
1022
|
+
// Persistence failed asynchronously, or the Request was deleted
|
|
1023
|
+
// between publish and dispatch. Log and bail.
|
|
1024
|
+
this.logger.warn('request:created references unknown Request', {
|
|
1025
|
+
requestId: event.requestId,
|
|
1026
|
+
});
|
|
1027
|
+
return;
|
|
1028
|
+
}
|
|
1029
|
+
if (!this.matchesInboundTag(request.tags)) {
|
|
1030
|
+
this.logger.debug('request:created skipping non-inbound source', {
|
|
1031
|
+
requestId: request.id,
|
|
1032
|
+
tags: request.tags,
|
|
1033
|
+
});
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const wiId = respondToUserWorkItemId(request.id);
|
|
1037
|
+
// Detect source kind from sourceConversationItemId shape. Slack ids
|
|
1038
|
+
// start with `slack-`; chat-v2 ids start with `chatv2-` (INBOUND-2).
|
|
1039
|
+
const threadTs = extractSlackThreadTs(request.sourceConversationItemId);
|
|
1040
|
+
const channelId = extractSlackChannelId(request.sourceConversationItemId);
|
|
1041
|
+
const chatV2ChannelId = extractChatV2ChannelId(request.sourceConversationItemId);
|
|
1042
|
+
const chatV2MessageId = extractChatV2MessageId(request.sourceConversationItemId);
|
|
1043
|
+
const source = threadTs
|
|
1044
|
+
? 'slack'
|
|
1045
|
+
: chatV2ChannelId
|
|
1046
|
+
? 'chat-v2'
|
|
1047
|
+
: 'unknown';
|
|
1048
|
+
// CRITICAL ORDERING (Steve 2026-05-03 auto-nudge variant 2):
|
|
1049
|
+
// Populate `trackedByRequest` + `threadIndex`/`chatV2Index` BEFORE the
|
|
1050
|
+
// `await taskPool.addToPool(wi)` below. The Slack bridge fires
|
|
1051
|
+
// `slackResolve('')` from `queue-processor.service.ts:690` immediately
|
|
1052
|
+
// when QueueProcessor delivers the message — within ~10ms of
|
|
1053
|
+
// `request:created` being published. If we await `addToPool` first,
|
|
1054
|
+
// the bridge's early `markResolvedByThread(threadTs)` lookup misses
|
|
1055
|
+
// the index, the SLA never closes, and the user sees the 5min/10min
|
|
1056
|
+
// `escalation_dm` "auto-nudge" cascade even though the orc replied
|
|
1057
|
+
// promptly.
|
|
1058
|
+
//
|
|
1059
|
+
// The timer + tracking setup is fully synchronous; only `addToPool`
|
|
1060
|
+
// yields the event loop, and now it does so AFTER the indices are
|
|
1061
|
+
// visible to a concurrent `markResolvedByThread`. If `addToPool`
|
|
1062
|
+
// fails downstream, the catch block below rolls back the in-memory
|
|
1063
|
+
// tracking so we don't leak timers.
|
|
1064
|
+
const breachTimer = setTimeout(() => {
|
|
1065
|
+
void this.handleBreach(request.id, /*level*/ 5);
|
|
1066
|
+
}, this.slaMs);
|
|
1067
|
+
breachTimer.unref?.();
|
|
1068
|
+
const escalationTimer = setTimeout(() => {
|
|
1069
|
+
void this.handleEscalation(request.id);
|
|
1070
|
+
}, this.escalationMs);
|
|
1071
|
+
escalationTimer.unref?.();
|
|
1072
|
+
this.trackedByRequest.set(request.id, {
|
|
1073
|
+
workItemId: wiId,
|
|
1074
|
+
requestId: request.id,
|
|
1075
|
+
source,
|
|
1076
|
+
threadTs,
|
|
1077
|
+
channelId,
|
|
1078
|
+
chatV2ChannelId,
|
|
1079
|
+
chatV2MessageId,
|
|
1080
|
+
breachTimer,
|
|
1081
|
+
escalationTimer,
|
|
1082
|
+
request,
|
|
1083
|
+
});
|
|
1084
|
+
if (threadTs)
|
|
1085
|
+
this.threadIndex.set(threadTs, request.id);
|
|
1086
|
+
if (chatV2ChannelId)
|
|
1087
|
+
this.chatV2Index.set(chatV2ChannelId, request.id);
|
|
1088
|
+
const wi = this.buildRespondWorkItem(request, wiId, {
|
|
1089
|
+
source,
|
|
1090
|
+
threadTs,
|
|
1091
|
+
channelId,
|
|
1092
|
+
chatV2ChannelId,
|
|
1093
|
+
chatV2MessageId,
|
|
1094
|
+
});
|
|
1095
|
+
// addToPool short-circuits on duplicate id (V1 dedup). On failure we
|
|
1096
|
+
// roll back the in-memory tracking populated above to keep the two
|
|
1097
|
+
// states consistent.
|
|
1098
|
+
try {
|
|
1099
|
+
await this.taskPool.addToPool(wi);
|
|
1100
|
+
}
|
|
1101
|
+
catch (err) {
|
|
1102
|
+
clearTimeout(breachTimer);
|
|
1103
|
+
clearTimeout(escalationTimer);
|
|
1104
|
+
this.trackedByRequest.delete(request.id);
|
|
1105
|
+
if (threadTs)
|
|
1106
|
+
this.threadIndex.delete(threadTs);
|
|
1107
|
+
if (chatV2ChannelId)
|
|
1108
|
+
this.chatV2Index.delete(chatV2ChannelId);
|
|
1109
|
+
this.logger.warn('addToPool failed — rolled back SLA tracking', {
|
|
1110
|
+
requestId: request.id,
|
|
1111
|
+
wiId,
|
|
1112
|
+
error: err instanceof Error ? err.message : String(err),
|
|
1113
|
+
});
|
|
1114
|
+
throw err;
|
|
1115
|
+
}
|
|
1116
|
+
this.logger.info('SLA respond_to_user WorkItem queued', {
|
|
1117
|
+
workItemId: wiId,
|
|
1118
|
+
requestId: request.id,
|
|
1119
|
+
source,
|
|
1120
|
+
threadTs,
|
|
1121
|
+
chatV2ChannelId,
|
|
1122
|
+
slaMs: this.slaMs,
|
|
1123
|
+
});
|
|
1124
|
+
};
|
|
1125
|
+
/**
|
|
1126
|
+
* `workitem:queued` handler (INBOUND-1.f1, auto-close path b).
|
|
1127
|
+
*
|
|
1128
|
+
* Treats decomposition of a Request into other WorkItems as "the orc has
|
|
1129
|
+
* done the right thing" and resolves the tracked respond_to_user WI for
|
|
1130
|
+
* that Request.
|
|
1131
|
+
*
|
|
1132
|
+
* Self-recursion guard: the respond_to_user WI itself fires
|
|
1133
|
+
* `workitem:queued` from {@link handleRequestCreated}'s `addToPool` call.
|
|
1134
|
+
* Without a guard this handler would call markResolved against its own
|
|
1135
|
+
* enqueue and prematurely close the SLA chain. The id-shape check
|
|
1136
|
+
* (`incomingId === respondToUserWorkItemId(requestId)`) is more reliable
|
|
1137
|
+
* than reading `wi.metadata.slaSource` — the metadata can in principle
|
|
1138
|
+
* be mutated, the id cannot.
|
|
1139
|
+
*
|
|
1140
|
+
* No-ops:
|
|
1141
|
+
* - event missing `requestId` (orphan WI, can't correlate)
|
|
1142
|
+
* - event missing `workItemId` (malformed publisher)
|
|
1143
|
+
* - the respond_to_user WI's own enqueue (id-shape match)
|
|
1144
|
+
* - no tracked respond_to_user WI for the requestId (already resolved
|
|
1145
|
+
* or never tracked because the source Request wasn't inbound-tagged)
|
|
1146
|
+
*
|
|
1147
|
+
* @param event - The `workitem:queued` event from TaskPoolService.addToPool
|
|
1148
|
+
*/
|
|
1149
|
+
handleWorkItemQueued = async (event) => {
|
|
1150
|
+
const requestId = event.requestId;
|
|
1151
|
+
const incomingWorkItemId = event.workItemId;
|
|
1152
|
+
if (!requestId) {
|
|
1153
|
+
// Per the f1 spec: undefined requestId = no auto-close. Most enqueues
|
|
1154
|
+
// fall here (queue mutations not derived from a Request).
|
|
1155
|
+
this.logger.debug('workitem:queued event missing requestId — auto-close no-op', {
|
|
1156
|
+
eventId: event.id,
|
|
1157
|
+
workItemId: incomingWorkItemId,
|
|
1158
|
+
});
|
|
1159
|
+
return;
|
|
1160
|
+
}
|
|
1161
|
+
if (!incomingWorkItemId) {
|
|
1162
|
+
this.logger.warn('workitem:queued event missing workItemId — malformed', {
|
|
1163
|
+
eventId: event.id,
|
|
1164
|
+
requestId,
|
|
1165
|
+
});
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
// Self-recursion guard. The respond_to_user WI's own enqueue must NOT
|
|
1169
|
+
// trigger its own resolution.
|
|
1170
|
+
if (incomingWorkItemId === respondToUserWorkItemId(requestId)) {
|
|
1171
|
+
this.logger.debug('workitem:queued is the respond_to_user WI itself — skip', {
|
|
1172
|
+
workItemId: incomingWorkItemId,
|
|
1173
|
+
requestId,
|
|
1174
|
+
});
|
|
1175
|
+
return;
|
|
1176
|
+
}
|
|
1177
|
+
// Pipeline-#4 fix (spec 2026-05-05-request-decompose-pipeline-gap.md, Patch A):
|
|
1178
|
+
// bidirectional link pool-entry → Request.workItemIds[]. Idempotent —
|
|
1179
|
+
// linkWorkItem already short-circuits on duplicate (request.service.ts).
|
|
1180
|
+
//
|
|
1181
|
+
// Runs BEFORE the trackedByRequest gate so it serves BOTH SLA-tracked
|
|
1182
|
+
// (Slack/chat-v2) AND directly-POSTed Requests. The only other site that
|
|
1183
|
+
// calls linkWorkItem is V3DataService.onTaskDelegated, which subscribes
|
|
1184
|
+
// to v3:task_delegated — an event /api/task-pool/add does not emit. So
|
|
1185
|
+
// WorkItems created via the orc's normal materialisation path
|
|
1186
|
+
// (break-down-request, delegate-task) were never linked. Wiring it here
|
|
1187
|
+
// is the architecturally-correct producer side.
|
|
1188
|
+
//
|
|
1189
|
+
// Failure-isolated: a link failure must NOT block markResolved (the SLA
|
|
1190
|
+
// correctness path — Steve 2026-04-30 incident). Logged at warn.
|
|
1191
|
+
try {
|
|
1192
|
+
await this.requestService.linkWorkItem(requestId, incomingWorkItemId);
|
|
1193
|
+
}
|
|
1194
|
+
catch (err) {
|
|
1195
|
+
this.logger.warn('linkWorkItem from workitem:queued failed (non-fatal)', {
|
|
1196
|
+
requestId,
|
|
1197
|
+
workItemId: incomingWorkItemId,
|
|
1198
|
+
error: formatError(err),
|
|
1199
|
+
});
|
|
1200
|
+
}
|
|
1201
|
+
// Only act when we're actively tracking this Request — otherwise the
|
|
1202
|
+
// queue mutation is for a Request we never SLA-tracked (no inbound tag,
|
|
1203
|
+
// already resolved, etc.).
|
|
1204
|
+
if (!this.trackedByRequest.has(requestId)) {
|
|
1205
|
+
this.logger.debug('workitem:queued for untracked Request — skip', {
|
|
1206
|
+
workItemId: incomingWorkItemId,
|
|
1207
|
+
requestId,
|
|
1208
|
+
});
|
|
1209
|
+
return;
|
|
1210
|
+
}
|
|
1211
|
+
await this.markResolved(requestId, 'workitem_decompose');
|
|
1212
|
+
};
|
|
1213
|
+
/**
|
|
1214
|
+
* 5-minute breach handler. Re-checks the WI status and emits
|
|
1215
|
+
* `request:sla_breached` if the WI is still non-terminal.
|
|
1216
|
+
*
|
|
1217
|
+
* @param requestId - The Request whose breach is firing
|
|
1218
|
+
* @param level - Breach level: 5 (first SLA) or 10 (escalation)
|
|
1219
|
+
*/
|
|
1220
|
+
async handleBreach(requestId, level) {
|
|
1221
|
+
const tracked = this.trackedByRequest.get(requestId);
|
|
1222
|
+
if (!tracked)
|
|
1223
|
+
return;
|
|
1224
|
+
try {
|
|
1225
|
+
const wi = await this.taskPool.findWorkItem(tracked.workItemId);
|
|
1226
|
+
if (!wi || TERMINAL_WI_STATUSES.has(wi.status)) {
|
|
1227
|
+
// Auto-resolved before the timer fired — clean up tracking.
|
|
1228
|
+
this.cleanupTracked(requestId);
|
|
1229
|
+
return;
|
|
1230
|
+
}
|
|
1231
|
+
this.eventBus.publish({
|
|
1232
|
+
id: `request:sla_breached:${requestId}:${level}`,
|
|
1233
|
+
type: 'request:sla_breached',
|
|
1234
|
+
timestamp: new Date().toISOString(),
|
|
1235
|
+
teamId: '',
|
|
1236
|
+
teamName: '',
|
|
1237
|
+
memberId: '',
|
|
1238
|
+
memberName: '',
|
|
1239
|
+
sessionName: this.orchestratorSession,
|
|
1240
|
+
previousValue: 'in_sla',
|
|
1241
|
+
newValue: `breached_${level}m`,
|
|
1242
|
+
changedField: 'taskStatus',
|
|
1243
|
+
requestId,
|
|
1244
|
+
workItemId: tracked.workItemId,
|
|
1245
|
+
});
|
|
1246
|
+
this.logger.warn('SLA breach', {
|
|
1247
|
+
requestId,
|
|
1248
|
+
workItemId: tracked.workItemId,
|
|
1249
|
+
level,
|
|
1250
|
+
});
|
|
1251
|
+
}
|
|
1252
|
+
catch (err) {
|
|
1253
|
+
this.logger.error('SLA breach handler threw', {
|
|
1254
|
+
requestId,
|
|
1255
|
+
error: formatError(err),
|
|
1256
|
+
});
|
|
1257
|
+
}
|
|
1258
|
+
}
|
|
1259
|
+
/**
|
|
1260
|
+
* 10-minute escalation handler. Emits the level-10 breach event and —
|
|
1261
|
+
* if a Slack DM callback is wired — sends the user a "still working on
|
|
1262
|
+
* it" nudge so they're never blind to the miss. After the DM (or DM-skip),
|
|
1263
|
+
* transitions the orphaned respond_to_user WI to `'failed'` with
|
|
1264
|
+
* `slaResolvedReason: 'escalation_timeout'` so the orc queue does not keep
|
|
1265
|
+
* a stale `queued` WI forever (Arch N3 on PR #357).
|
|
1266
|
+
*/
|
|
1267
|
+
async handleEscalation(requestId) {
|
|
1268
|
+
const tracked = this.trackedByRequest.get(requestId);
|
|
1269
|
+
if (!tracked)
|
|
1270
|
+
return;
|
|
1271
|
+
// Re-emit the breach event at level=10 so the orc terminal sees the
|
|
1272
|
+
// escalation arc explicitly.
|
|
1273
|
+
await this.handleBreach(requestId, 10);
|
|
1274
|
+
// Re-fetch in case the breach handler cleaned up.
|
|
1275
|
+
const stillTracked = this.trackedByRequest.get(requestId);
|
|
1276
|
+
if (!stillTracked)
|
|
1277
|
+
return;
|
|
1278
|
+
// Capture the WI id BEFORE cleanupTracked() drops the record so we can
|
|
1279
|
+
// still transition the orphan WI to 'failed' afterwards.
|
|
1280
|
+
const wiId = stillTracked.workItemId;
|
|
1281
|
+
// INBOUND-2: chat-v2 source has no DM-back analog yet. Log the
|
|
1282
|
+
// escalation, clean up tracking, and close the orphan WI. A follow-up
|
|
1283
|
+
// ticket can wire a chat-v2 nudge (e.g. agent-side reply via
|
|
1284
|
+
// reply-channel).
|
|
1285
|
+
if (stillTracked.source === 'chat-v2') {
|
|
1286
|
+
this.logger.warn('SLA escalation reached 10min on chat-v2 — no chat-v2 nudge hook wired', {
|
|
1287
|
+
requestId,
|
|
1288
|
+
chatV2ChannelId: stillTracked.chatV2ChannelId,
|
|
1289
|
+
});
|
|
1290
|
+
this.cleanupTracked(requestId);
|
|
1291
|
+
await this.failOrphanRespondWi(wiId, requestId);
|
|
1292
|
+
return;
|
|
1293
|
+
}
|
|
1294
|
+
if (!this.sendEscalationDm) {
|
|
1295
|
+
this.logger.warn('SLA escalation reached 10min — no Slack DM hook wired', {
|
|
1296
|
+
requestId,
|
|
1297
|
+
});
|
|
1298
|
+
this.cleanupTracked(requestId);
|
|
1299
|
+
await this.failOrphanRespondWi(wiId, requestId);
|
|
1300
|
+
return;
|
|
1301
|
+
}
|
|
1302
|
+
if (!stillTracked.channelId || !stillTracked.threadTs) {
|
|
1303
|
+
this.logger.warn('SLA escalation missing Slack thread context — skipping DM', {
|
|
1304
|
+
requestId,
|
|
1305
|
+
});
|
|
1306
|
+
this.cleanupTracked(requestId);
|
|
1307
|
+
await this.failOrphanRespondWi(wiId, requestId);
|
|
1308
|
+
return;
|
|
1309
|
+
}
|
|
1310
|
+
try {
|
|
1311
|
+
const wi = await this.taskPool.findWorkItem(stillTracked.workItemId);
|
|
1312
|
+
const messageText = ":hourglass: It's been a few minutes — I'm still on this. " +
|
|
1313
|
+
'I will reply as soon as I have an answer. (auto-nudge)';
|
|
1314
|
+
await this.sendEscalationDm({
|
|
1315
|
+
channelId: stillTracked.channelId,
|
|
1316
|
+
threadTs: stillTracked.threadTs,
|
|
1317
|
+
messageText,
|
|
1318
|
+
request: stillTracked.request,
|
|
1319
|
+
workItem: wi,
|
|
1320
|
+
});
|
|
1321
|
+
this.logger.info('SLA escalation Slack DM sent', {
|
|
1322
|
+
requestId,
|
|
1323
|
+
channelId: stillTracked.channelId,
|
|
1324
|
+
});
|
|
1325
|
+
}
|
|
1326
|
+
catch (err) {
|
|
1327
|
+
this.logger.error('SLA escalation DM failed', {
|
|
1328
|
+
requestId,
|
|
1329
|
+
error: formatError(err),
|
|
1330
|
+
});
|
|
1331
|
+
}
|
|
1332
|
+
finally {
|
|
1333
|
+
// Escalation is the terminal hook in v1 — drop tracking + close the
|
|
1334
|
+
// orphan WI either way (DM success or failure).
|
|
1335
|
+
this.cleanupTracked(requestId);
|
|
1336
|
+
await this.failOrphanRespondWi(wiId, requestId);
|
|
1337
|
+
}
|
|
1338
|
+
}
|
|
1339
|
+
/**
|
|
1340
|
+
* Transition an escalated respond_to_user WI to `'failed'` with
|
|
1341
|
+
* `slaResolvedReason: 'escalation_timeout'` so the orc queue does not
|
|
1342
|
+
* keep a stale `queued` WI forever after a 10-min escalation. No-op if
|
|
1343
|
+
* the WI is already terminal (e.g. user gave up + an out-of-band cleanup
|
|
1344
|
+
* already closed it).
|
|
1345
|
+
*
|
|
1346
|
+
* Mirrors {@link markResolved}'s terminal-status guard. Errors are
|
|
1347
|
+
* logged but never propagated — the SLA chain is already terminal at
|
|
1348
|
+
* this point and we do not want to mask the original DM-path outcome.
|
|
1349
|
+
*
|
|
1350
|
+
* @param workItemId - The respond_to_user WI id to close.
|
|
1351
|
+
* @param requestId - The originating Request id (logging context only).
|
|
1352
|
+
*/
|
|
1353
|
+
async failOrphanRespondWi(workItemId, requestId) {
|
|
1354
|
+
try {
|
|
1355
|
+
const wi = await this.taskPool.findWorkItem(workItemId);
|
|
1356
|
+
if (!wi)
|
|
1357
|
+
return;
|
|
1358
|
+
if (TERMINAL_WI_STATUSES.has(wi.status)) {
|
|
1359
|
+
// Already terminal — nothing to do.
|
|
1360
|
+
return;
|
|
1361
|
+
}
|
|
1362
|
+
// Same state-machine constraint as markResolved: `queued → failed`
|
|
1363
|
+
// is illegal per WORK_ITEM_TRANSITIONS. Route queued WIs to
|
|
1364
|
+
// `cancelled`; running WIs go to `failed` as before.
|
|
1365
|
+
// Capture BEFORE the transition: `transitionStatus` mutates the stored
|
|
1366
|
+
// record in place, so reading `wi.status` afterwards yields the status we
|
|
1367
|
+
// just wrote, not the one we came from. The audit reason and the log
|
|
1368
|
+
// below both need the origin.
|
|
1369
|
+
const fromStatus = wi.status;
|
|
1370
|
+
const target = pickFailTarget(fromStatus);
|
|
1371
|
+
await this.taskPool.transitionStatus(workItemId, target, 'system', (item) => {
|
|
1372
|
+
item.metadata = {
|
|
1373
|
+
...(item.metadata ?? {}),
|
|
1374
|
+
slaResolvedReason: 'escalation_timeout',
|
|
1375
|
+
slaResolvedAt: new Date().toISOString(),
|
|
1376
|
+
};
|
|
1377
|
+
}, target === 'cancelled' ? 'SLA escalation timeout' : undefined);
|
|
1378
|
+
// The item is terminal now, so its claim must not stay active.
|
|
1379
|
+
//
|
|
1380
|
+
// This path cannot delegate to `failItem` — the code that normally
|
|
1381
|
+
// releases the claim — because `failItem` always transitions to
|
|
1382
|
+
// `failed`, whereas `pickFailTarget` yields `cancelled`, `failed` OR
|
|
1383
|
+
// `rejected` depending on the origin status. `disposeFailedWorkItem`
|
|
1384
|
+
// does not cover it either: that owns disposition and touches no
|
|
1385
|
+
// claims. So the release is made explicitly here.
|
|
1386
|
+
//
|
|
1387
|
+
// Ordering is deliberate on both sides:
|
|
1388
|
+
// - AFTER the transition, so the claim is released only once the item
|
|
1389
|
+
// actually reached a terminal state. Releasing first would, on a
|
|
1390
|
+
// failed transition, free the claim on an item that is still
|
|
1391
|
+
// `running` and still being worked — a worse outcome than the leak.
|
|
1392
|
+
// - BEFORE disposition, because the funnel may requeue the item; a
|
|
1393
|
+
// requeued item carrying an active claim cannot be claimed by
|
|
1394
|
+
// anyone, which is the very stranding this fix exists to prevent.
|
|
1395
|
+
await this.taskPool.releaseClaim(workItemId, `sla escalation timeout (from ${fromStatus})`);
|
|
1396
|
+
this.logger.info('SLA escalation orphan WI auto-closed', {
|
|
1397
|
+
workItemId,
|
|
1398
|
+
requestId,
|
|
1399
|
+
fromStatus,
|
|
1400
|
+
toStatus: target,
|
|
1401
|
+
});
|
|
1402
|
+
// `cancelled` is strictly terminal and needs nothing further. `failed`
|
|
1403
|
+
// and `rejected` do: their only outbound edge is `→ queued`, so without
|
|
1404
|
+
// a disposition the item stops here permanently. This path writes them
|
|
1405
|
+
// via `transitionStatus` directly rather than `failItem`/`verifyItem`,
|
|
1406
|
+
// so neither the `task:failed` retry/escalate branch in
|
|
1407
|
+
// `V3DataService.onTaskFailed` nor the `task:rejected` bridge successor
|
|
1408
|
+
// ever fires for it — it was the reproducible stranding path. Route it
|
|
1409
|
+
// through the funnel so the item is retried, succeeded, or deliberately
|
|
1410
|
+
// closed with an audit record.
|
|
1411
|
+
if (target === 'failed' || target === 'rejected') {
|
|
1412
|
+
await this.taskPool.disposeFailedWorkItem(workItemId, {
|
|
1413
|
+
reason: `SLA escalation timeout (from ${fromStatus})`,
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
}
|
|
1417
|
+
catch (err) {
|
|
1418
|
+
this.logger.warn('SLA escalation orphan-fail threw', {
|
|
1419
|
+
workItemId,
|
|
1420
|
+
requestId,
|
|
1421
|
+
error: formatError(err),
|
|
1422
|
+
});
|
|
1423
|
+
}
|
|
1424
|
+
}
|
|
1425
|
+
/**
|
|
1426
|
+
* Drop tracking + clear timers for a Request. Used both on auto-resolve
|
|
1427
|
+
* and on terminal escalation — once we reach 10min the SLA chain is done.
|
|
1428
|
+
*/
|
|
1429
|
+
cleanupTracked(requestId) {
|
|
1430
|
+
const tracked = this.trackedByRequest.get(requestId);
|
|
1431
|
+
if (!tracked)
|
|
1432
|
+
return;
|
|
1433
|
+
clearTimeout(tracked.breachTimer);
|
|
1434
|
+
clearTimeout(tracked.escalationTimer);
|
|
1435
|
+
this.trackedByRequest.delete(requestId);
|
|
1436
|
+
if (tracked.threadTs)
|
|
1437
|
+
this.threadIndex.delete(tracked.threadTs);
|
|
1438
|
+
if (tracked.chatV2ChannelId)
|
|
1439
|
+
this.chatV2Index.delete(tracked.chatV2ChannelId);
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Check whether a Request's tags include any of the configured inbound
|
|
1443
|
+
* channel tags.
|
|
1444
|
+
*/
|
|
1445
|
+
matchesInboundTag(tags) {
|
|
1446
|
+
for (const t of tags) {
|
|
1447
|
+
if (this.inboundTags.has(t))
|
|
1448
|
+
return true;
|
|
1449
|
+
}
|
|
1450
|
+
return false;
|
|
1451
|
+
}
|
|
1452
|
+
/**
|
|
1453
|
+
* Build the respond_to_user WorkItem with the standard metadata invariants.
|
|
1454
|
+
*
|
|
1455
|
+
* INBOUND-2: metadata embeds slack-* OR chatV2-* fields based on the
|
|
1456
|
+
* source surface so downstream consumers (status panes, escalation
|
|
1457
|
+
* hooks) can branch without re-parsing `sourceConversationItemId`.
|
|
1458
|
+
*/
|
|
1459
|
+
buildRespondWorkItem(request, wiId, sourceContext) {
|
|
1460
|
+
const now = new Date().toISOString();
|
|
1461
|
+
const slaDeadline = new Date(Date.now() + this.slaMs).toISOString();
|
|
1462
|
+
const escalationDeadline = new Date(Date.now() + this.escalationMs).toISOString();
|
|
1463
|
+
return {
|
|
1464
|
+
id: wiId,
|
|
1465
|
+
type: 'review',
|
|
1466
|
+
owner: 'orchestrator',
|
|
1467
|
+
target: this.orchestratorSession,
|
|
1468
|
+
title: `Respond to user: ${request.title.slice(0, 60)}`,
|
|
1469
|
+
description: `Inbound user message arrived as Request ${request.id}.\n\n` +
|
|
1470
|
+
`Original message:\n${request.description.slice(0, 400)}`,
|
|
1471
|
+
status: 'queued',
|
|
1472
|
+
createdAt: now,
|
|
1473
|
+
retryCount: 0,
|
|
1474
|
+
maxRetries: DEFAULT_MAX_RETRIES,
|
|
1475
|
+
requestId: request.id,
|
|
1476
|
+
missionId: request.missionId,
|
|
1477
|
+
inputTokens: 0,
|
|
1478
|
+
outputTokens: 0,
|
|
1479
|
+
cost: 0,
|
|
1480
|
+
metadata: {
|
|
1481
|
+
idempotencyKey: wiId,
|
|
1482
|
+
triggerSource: 'event',
|
|
1483
|
+
slaSource: 'inbound-1',
|
|
1484
|
+
slaDeadline,
|
|
1485
|
+
slaEscalationDeadline: escalationDeadline,
|
|
1486
|
+
slaBreachLevel: 0,
|
|
1487
|
+
inboundTag: request.tags.find((t) => this.inboundTags.has(t)),
|
|
1488
|
+
inboundSource: sourceContext.source,
|
|
1489
|
+
slackThreadTs: sourceContext.threadTs,
|
|
1490
|
+
slackChannelId: sourceContext.channelId,
|
|
1491
|
+
chatV2ChannelId: sourceContext.chatV2ChannelId,
|
|
1492
|
+
chatV2MessageId: sourceContext.chatV2MessageId,
|
|
1493
|
+
},
|
|
1494
|
+
};
|
|
1495
|
+
}
|
|
1496
|
+
/**
|
|
1497
|
+
* Wrap a dispatch so a thrown handler is logged and isolated. Mirrors
|
|
1498
|
+
* EventToWorkItemBridge.safeDispatch.
|
|
1499
|
+
*/
|
|
1500
|
+
safeDispatch(eventType, event) {
|
|
1501
|
+
const dispatch = (async () => {
|
|
1502
|
+
try {
|
|
1503
|
+
if (eventType === 'request:created') {
|
|
1504
|
+
await this.handleRequestCreated(event);
|
|
1505
|
+
}
|
|
1506
|
+
else if (eventType === 'workitem:queued') {
|
|
1507
|
+
await this.handleWorkItemQueued(event);
|
|
1508
|
+
}
|
|
1509
|
+
}
|
|
1510
|
+
catch (err) {
|
|
1511
|
+
this.logger.error('SLA subscriber handler threw', {
|
|
1512
|
+
eventType,
|
|
1513
|
+
eventId: event.id,
|
|
1514
|
+
error: formatError(err),
|
|
1515
|
+
});
|
|
1516
|
+
}
|
|
1517
|
+
})();
|
|
1518
|
+
this.pendingDispatches.add(dispatch);
|
|
1519
|
+
dispatch
|
|
1520
|
+
.finally(() => {
|
|
1521
|
+
this.pendingDispatches.delete(dispatch);
|
|
1522
|
+
})
|
|
1523
|
+
.catch(() => {
|
|
1524
|
+
// suppress unhandled-rejection — flushPending owners use allSettled.
|
|
1525
|
+
});
|
|
1526
|
+
return dispatch;
|
|
1527
|
+
}
|
|
1528
|
+
}
|
|
1529
|
+
//# sourceMappingURL=request-sla.subscriber.js.map
|